Skip to content
Merged

1.6.0 #105

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
2e0c3e6
Add default reminder email template
mapiolca Dec 3, 2025
6cfed68
Comment out NOREQUIREUSER and NOREQUIREMENU checks
mapiolca Dec 3, 2025
9685460
Remove multi-entity management checks from SQL
mapiolca Dec 3, 2025
f4efd58
Improve reminder template compatibility
mapiolca Dec 3, 2025
149e600
Insert reminder email template via install SQL
mapiolca Dec 3, 2025
5e1facc
Handle email template columns across schemas
mapiolca Dec 3, 2025
f0f6081
Remove comment from llx_timesheet_week.sql
mapiolca Dec 3, 2025
b1f05b1
Update llx_timesheet_week.sql
mapiolca Dec 3, 2025
61984b3
Rename email_cc and email_bcc to email_tocc and email_tobcc
mapiolca Dec 3, 2025
116eabc
Remove duplicate subject field from SQL query
mapiolca Dec 3, 2025
a206e52
Change condition from code to label in SQL query
mapiolca Dec 3, 2025
221a93b
Sécuriser les mises à jour de schéma timesheet
mapiolca Dec 3, 2025
3273dfc
Rendre l'upgrade model_pdf compatible MySQL
mapiolca Dec 3, 2025
d45d641
Remove weekly reminder email template functions
mapiolca Dec 4, 2025
2724a64
Create data.sql
mapiolca Dec 4, 2025
a60b2fb
Update modTimesheetWeek.class.php
mapiolca Dec 4, 2025
d87ff5c
Update data.sql
mapiolca Dec 4, 2025
de6bce3
Update data.sql
mapiolca Dec 4, 2025
843a8c4
Update data.sql
mapiolca Dec 4, 2025
95ec4f2
Update data.sql
mapiolca Dec 4, 2025
18f0da7
Update data.sql
mapiolca Dec 4, 2025
1df17ec
Update data.sql
mapiolca Dec 4, 2025
ea36dcb
Update data.sql
mapiolca Dec 4, 2025
4642391
Update data.sql
mapiolca Dec 4, 2025
1cd66bf
Update data.sql
mapiolca Dec 4, 2025
92d64d7
Update data.sql
mapiolca Dec 4, 2025
6068ad5
Update setup.php
mapiolca Dec 4, 2025
ef46fcd
Update setup.php
mapiolca Dec 4, 2025
7a56c5f
Update setup.php
mapiolca Dec 4, 2025
fa29c46
Update setup.php
mapiolca Dec 4, 2025
0722eb7
Update setup.php
mapiolca Dec 4, 2025
ccc5ff6
Update setup.php
mapiolca Dec 4, 2025
d7c01b8
Update setup.php
mapiolca Dec 4, 2025
8a66b6d
Update setup.php
mapiolca Dec 4, 2025
f6cc4ec
Update setup.php
mapiolca Dec 4, 2025
a67d553
Update setup.php
mapiolca Dec 4, 2025
cf2f49e
Update setup.php
mapiolca Dec 4, 2025
986b058
Update setup.php
mapiolca Dec 4, 2025
0cdf9d5
Update setup.php
mapiolca Dec 4, 2025
f9141c2
Update setup.php
mapiolca Dec 4, 2025
ba1017d
Update setup.php
mapiolca Dec 4, 2025
49c8fe5
Update setup.php
mapiolca Dec 4, 2025
077bafc
Update setup.php
mapiolca Dec 4, 2025
7e1e4a2
Fix reminder form submission
mapiolca Dec 4, 2025
9a5a034
Update setup.php
mapiolca Dec 4, 2025
1c02ecb
Update setup.php
mapiolca Dec 4, 2025
5c32147
Update setup.php
mapiolca Dec 4, 2025
acd5e3a
Merge pull request #101 from mapiolca/2025-12-04-investigate-saveremi…
mapiolca Dec 4, 2025
624b5cd
Update timesheetweek_reminder.class.php
mapiolca Dec 4, 2025
ae298ff
Update timesheetweek_reminder.class.php
mapiolca Dec 4, 2025
7128da6
Update timesheetweek_reminder.class.php
mapiolca Dec 4, 2025
65e3c4c
Update timesheetweek_reminder.class.php
mapiolca Dec 4, 2025
777f098
Use CEmailTemplate apifetch for reminders
mapiolca Dec 4, 2025
5f64614
Handle cron reminder without explicit db argument
mapiolca Dec 4, 2025
b8d0640
Fix reminder globals and cron activation
mapiolca Dec 4, 2025
1460794
Update modTimesheetWeek.class.php
mapiolca Dec 4, 2025
ab45046
Update modTimesheetWeek.class.php
mapiolca Dec 4, 2025
c351d4d
Update modTimesheetWeek.class.php
mapiolca Dec 4, 2025
94c328e
Update setup.php
mapiolca Dec 5, 2025
6a9aa6b
Update modTimesheetWeek.class.php
mapiolca Dec 5, 2025
b7b366e
Fix reminder cron email template loading
mapiolca Dec 5, 2025
bee5c75
Allow forced reminder cron execution
mapiolca Dec 5, 2025
47030d0
Handle forced reminder triggers and HTML content
mapiolca Dec 5, 2025
a83d48d
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
fb613e3
Merge pull request #103 from mapiolca/2025-12-05-fix-email-not-sendin…
mapiolca Dec 5, 2025
c809d8f
Merge pull request #102 from mapiolca/2025-12-04-fix-timesheet-week-r…
mapiolca Dec 5, 2025
f8b6370
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
0f9e92b
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
703a81f
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
aa5927f
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
285d839
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
46fd9ad
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
53c3860
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
78e8e2a
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
8e66ac1
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
afcef97
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
550ab23
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
b5aee60
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
0729a02
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
3f3e3be
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
ccab5b5
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
000d05b
Update setup.php
mapiolca Dec 5, 2025
55c4d7b
Update setup.php
mapiolca Dec 5, 2025
402f2ec
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
edad799
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
3c01561
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
afbef8e
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
89bf6af
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
588295a
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
03570c7
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
f6f082f
Update setup.php
mapiolca Dec 5, 2025
4c7df2f
Update setup.php
mapiolca Dec 5, 2025
db79e2c
Update setup.php
mapiolca Dec 5, 2025
66efcda
Update timesheetweek.lang
mapiolca Dec 5, 2025
9500284
Update timesheetweek.lang
mapiolca Dec 5, 2025
0fb1441
Update data.sql
mapiolca Dec 5, 2025
0c63a1f
Avoid overriding global user during reminder run
mapiolca Dec 5, 2025
d29f8f1
Update timesheetweek_reminder.class.php
mapiolca Dec 5, 2025
64d8f19
Merge pull request #104 from mapiolca/2025-12-05-investigate-sendtest…
mapiolca Dec 5, 2025
0b1ba51
Update data.sql
mapiolca Dec 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG MODULE TIMESHEETWEEK FOR [DOLIBARR ERP CRM](https://www.dolibarr.org)

## 1.6.0 (20/06/2026)
- Ajoute un rappel hebdomadaire automatique par email configurable (activation, jour, heure, modèle) et une tâche planifiée dédiée avec bouton de test administrateur.
/ Adds a configurable automatic weekly email reminder (enablement, day, time, template) plus a dedicated scheduled task and admin test button.

## 1.5.0 (01/12/2025)
- Ajoute une action de masse pour générer un PDF unique fusionnant les feuilles sélectionnées en utilisant le modèle par défaut. / Adds a mass action to generate a single merged PDF for selected timesheets using the default template.

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ TimesheetWeek ajoute une gestion hebdomadaire des feuilles de temps fidèle à l
- Redirection automatique vers la feuille existante en cas de tentative de doublon afin d'éviter les saisies multiples.
- Suivi des compteurs hebdomadaires de zones et de paniers directement sur les feuilles et recalcul automatique à chaque enregistrement.
- Saisie dédiée pour les salariés en forfait jour grâce à des sélecteurs Journée/Matin/Après-midi convertissant automatiquement les heures.
- Rappel hebdomadaire automatique par email configurable (activation, jour, heure, modèle) avec tâche planifiée dédiée et bouton d'envoi de test administrateur.
- Affichage des compteurs dans la liste hebdomadaire et ajout du libellé « Zone » sur chaque sélecteur quotidien pour clarifier la saisie.
- Ligne de total en bas de la liste hebdomadaire pour additionner heures, zones, paniers et afficher la colonne de date de validation.
- Création rapide d'une feuille d'heures via le raccourci « Ajouter » du menu supérieur.
Expand Down Expand Up @@ -51,6 +52,7 @@ TimesheetWeek delivers weekly timesheet management that follows Dolibarr design
- Automatic redirect to the existing timesheet when a duplicate creation is attempted.
- Weekly counters for zones and meal allowances with automatic recomputation on each save.
- Dedicated input for daily rate employees with Full day/Morning/Afternoon selectors that automatically convert hours.
- Configurable automatic weekly email reminder (enablement, weekday, time, template) with a dedicated scheduled task and admin test send button.
- Counter display inside the weekly list plus a « Zone » caption on each daily selector for better input guidance.
- Total row at the bottom of the weekly list to sum hours, zones, meals and expose the validation date column.
- Quick creation shortcut available from the top-right « Add » menu.
Expand Down
207 changes: 168 additions & 39 deletions admin/setup.php

Large diffs are not rendered by default.

313 changes: 313 additions & 0 deletions class/timesheetweek_reminder.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
<?php
/* Copyright (C) 2025 Pierre Ardoin <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/*
if (!defined('NOREQUIREUSER')) {
define('NOREQUIREUSER', 1);
}
if (!defined('NOREQUIREDB')) {
define('NOREQUIREDB', 1);
}
if (!defined('NOREQUIRESOC')) {
define('NOREQUIRESOC', 1);
}
if (!defined('NOREQUIRETRAN')) {
define('NOREQUIRETRAN', 1);
}
if (!defined('NOREQUIRESUBPERMS')) {
define('NOREQUIRESUBPERMS', 1);
}
if (!defined('NOREQUIREMENU')) {
define('NOREQUIREMENU', 1);
}
*/
require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/functions.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';

dol_include_once('/timesheetweek/class/timesheetweek.class.php');


/**
* Cron helper used to send weekly reminders.
*/
class TimesheetweekReminder extends CommonObject
{
public $db;
public $error;
public $errors = array();
public $output;

public function __construct(DoliDB $db)
{
$this->db = $db;
}
/**
* Run cron job to send weekly reminder emails.
*
* @param DoliDB $db Database handler
* @param int $limit Optional limit for recipients
* @param int $forcerun Force execution (1) or use normal scheduling (0)
* @param array $targetUserIds Limit execution to specific user ids when provided
* @return int <0 if KO, >=0 if OK (number of emails sent)
*/
public function run($dbInstance = null, $limit = 0, $forcerun = 0, array $targetUserIds = array()) //$dbInstance = null,
{
global $db, $conf, $user, $langs;

/*
$db = $dbInstance;
if (empty($db) && !empty($GLOBALS['db'])) {
$db = $GLOBALS['db'];
}
*/
if (empty($db)) {
dol_syslog($langs->transnoentitiesnoconv('ErrorNoDatabase'), LOG_ERR);
return -1;
}

$langs->loadLangs(array('timesheetweek@timesheetweek'));

$forceExecution = !empty($forcerun);
if (!$forceExecution) {
$forceExecution = ((int) GETPOST('forcerun', 'int') > 0);
}
if (!$forceExecution) {
$action = GETPOST('action', 'aZ09');
$confirm = GETPOST('confirm', 'alpha');
if ($action === 'confirm_execute' && $confirm === 'yes') {
$forceExecution = true;
}
}

$emailTemplateClassFile = '';
if (is_readable(DOL_DOCUMENT_ROOT.'/core/class/cemailtemplate.class.php')) {
$emailTemplateClassFile = '/core/class/cemailtemplate.class.php';
} elseif (is_readable(DOL_DOCUMENT_ROOT.'/core/class/emailtemplate.class.php')) {
$emailTemplateClassFile = '/core/class/emailtemplate.class.php';
}

if (!empty($emailTemplateClassFile)) {
dol_include_once($emailTemplateClassFile);
}

if (!class_exists('CEmailTemplate') && !class_exists('EmailTemplate')) {
dol_syslog($langs->trans('ErrorFailedToLoadEmailTemplateClass'), LOG_ERR);
return -1;
}

dol_syslog(__METHOD__, LOG_DEBUG);

$reminderEnabled = getDolGlobalInt('TIMESHEETWEEK_REMINDER_ENABLED', 0, $conf->entity);
if (empty($reminderEnabled) && empty($forceExecution)) {
dol_syslog('TimesheetweekReminder: reminder disabled', LOG_INFO);
return 0;
}

$reminderWeekday = getDolGlobalInt('TIMESHEETWEEK_REMINDER_WEEKDAY', 1, $conf->entity);
if ($reminderWeekday < 1 || $reminderWeekday > 7) {
dol_syslog($langs->trans('TimesheetWeekReminderWeekdayInvalid'), LOG_ERR);
return -1;
}

$reminderHour = getDolGlobalString('TIMESHEETWEEK_REMINDER_HOUR', '18:00', $conf->entity);
if (!preg_match('/^(?:[01]\\d|2[0-3]):[0-5]\\d$/', $reminderHour)) {
dol_syslog($langs->trans('TimesheetWeekReminderHourInvalid'), LOG_ERR);
return -1;
}

$templateId = getDolGlobalInt('TIMESHEETWEEK_REMINDER_EMAIL_TEMPLATE', 0, $conf->entity);
if (empty($templateId)) {
dol_syslog($langs->trans('TimesheetWeekReminderTemplateMissing'), LOG_WARNING);
return 0;
}

$timezoneCode = !empty($conf->timezone) ? $conf->timezone : 'UTC';
$now = dol_now();
$nowArray = dol_getdate($now, true, $timezoneCode);
$currentWeekday = (int) $nowArray['wday'];
$currentWeekdayIso = ($currentWeekday === 0) ? 7 : $currentWeekday;
$currentMinutes = ((int) $nowArray['hours'] * 60) + (int) $nowArray['minutes'];

list($targetHour, $targetMinute) = explode(':', $reminderHour);
$targetMinutes = ((int) $targetHour * 60) + (int) $targetMinute;
$windowMinutes = 5;
$lowerBound = max(0, $targetMinutes - $windowMinutes);
$upperBound = min(120, $targetMinutes + $windowMinutes);

if (empty($forceExecution)) {
if ($currentWeekdayIso !== $reminderWeekday) {
dol_syslog('TimesheetweekReminder: not the configured day, skipping execution', LOG_DEBUG);
return 0;
}
if ($currentMinutes < $lowerBound || $currentMinutes > $upperBound) {
dol_syslog('TimesheetweekReminder: outside configured time window, skipping execution', LOG_DEBUG);
return 0;
}
}

$emailTemplate = null;
$templateFetch = 0;
if (class_exists('CEmailTemplate')) {
$emailTemplate = new CEmailTemplate($db);
if (method_exists($emailTemplate, 'apifetch')) {
$templateFetch = $emailTemplate->apifetch($templateId);
} else {
$templateFetch = $emailTemplate->fetch($templateId);
}
} elseif (class_exists('EmailTemplate')) {
$emailTemplate = new EmailTemplate($db);
$templateFetch = $emailTemplate->fetch($templateId);
}

if (empty($emailTemplate)) {
dol_syslog($langs->trans('TimesheetWeekReminderTemplateMissing'), LOG_ERR);
return -1;
}

if ($templateFetch <= 0) {
dol_syslog($langs->trans('TimesheetWeekReminderTemplateMissing'), LOG_WARNING);
return 0;
}

$subject = !empty($emailTemplate->topic) ? $emailTemplate->topic : $emailTemplate->label;
$body = !empty($emailTemplate->content) ? $emailTemplate->content : '';

if (empty($subject) || empty($body)) {
dol_syslog($langs->trans('TimesheetWeekReminderTemplateMissing'), LOG_WARNING);
return 0;
}

$from = getDolGlobalString('MAIN_MAIL_EMAIL_FROM', '');
if (empty($from)) {
$from = getDolGlobalString('MAIN_INFO_SOCIETE_MAIL', '');
}

$substitutions = getCommonSubstitutionArray($langs, 0, null, null, null);
complete_substitutions_array($substitutions, $langs, null);

$eligibleRights = array(
45000301, // read own
45000302, // read child
45000303, // read all
45000304, // write own
45000305, // write child
45000306, // write all
45000310, // validate generic
45000311, // validate own
45000312, // validate child
45000313, // validate all
45000314, // seal
45000315, // unseal
);

$entityFilter = getEntity('user');
$sql = 'SELECT DISTINCT u.rowid, u.lastname, u.firstname, u.email';
$sql .= ' FROM '.MAIN_DB_PREFIX."user AS u";
$sql .= ' INNER JOIN '.MAIN_DB_PREFIX."user_rights AS ur ON ur.fk_user = u.rowid AND ur.entity IN (".$entityFilter.')';
$sql .= " WHERE u.statut = 1 AND u.email IS NOT NULL AND u.email <> ''";
$sql .= ' AND u.entity IN ('.$entityFilter.')';
$sql .= ' AND ur.fk_id IN ('.implode(',', array_map('intval', $eligibleRights)).')';
if (!empty($targetUserIds)) {
$sql .= ' AND u.rowid IN ('.implode(',', array_map('intval', $targetUserIds)).')';
}
$sql .= ' ORDER BY u.rowid ASC';
if ($limit > 0) {
$sql .= $db->plimit((int) $limit);
}

$resql = $db->query($sql);
if (!$resql) {
dol_syslog($db->lasterror(), LOG_ERR);
return -1;
}

$emailsSent = 0;
$errors = 0;

while ($obj = $db->fetch_object($resql)) {
$recipient = trim($obj->email);
if (empty($recipient)) {
continue;
}

$recipientUser = new User($db);
$fetchUser = $recipientUser->fetch($obj->rowid);
if ($fetchUser < 0) {
dol_syslog($recipientUser->error, LOG_ERR);
$errors++;
continue;
}

$userSubstitutions = $substitutions;
$userSubstitutions['__USER_FIRSTNAME__'] = $recipientUser->firstname;
$userSubstitutions['__USER_LASTNAME__'] = $recipientUser->lastname;
$userSubstitutions['__USER_FULLNAME__'] = dolGetFirstLastname($recipientUser->firstname, $recipientUser->lastname);
$userSubstitutions['__USER_EMAIL__'] = $recipient;
complete_substitutions_array($userSubstitutions, $langs, null, $recipientUser);

$preparedSubject = make_substitutions($subject, $userSubstitutions);
$preparedBody = make_substitutions($body, $userSubstitutions);

$preparedSubject = dol_string_nohtmltag(html_entity_decode($preparedSubject, ENT_QUOTES, 'UTF-8'));
$preparedBodyHtml = html_entity_decode($preparedBody, ENT_QUOTES, 'UTF-8');
$isHtmlBody = (!empty($preparedBodyHtml) && preg_match('/<[^>]+>/', $preparedBodyHtml)) ? 1 : 0;
$preparedBodyFinal = $isHtmlBody ? $preparedBodyHtml : dol_string_nohtmltag($preparedBodyHtml);

$mail = new CMailFile($preparedSubject, $recipient, $from, $preparedBodyFinal, array(), array(), array(), '', '', 0, $isHtmlBody, '', '', '', 'utf-8');
$resultSend = $mail->sendfile();
if ($resultSend) {
$emailsSent++;
dol_syslog($langs->trans('TimesheetWeekReminderSendSuccess', $recipient), LOG_INFO);
} else {
dol_syslog($langs->trans('TimesheetWeekReminderSendFailed', $recipient), LOG_ERR);
$errors++;
}
}

$db->free($resql);

/* if ($errors > 0) {
return -$errors;
}*/

// return $emailsSent;

if ($errors) {
$this->error = $langs->trans('TimesheetWeekReminderSendFailed', $errors);
dol_syslog(__METHOD__." end - ".$this->error, LOG_ERR);
return 1;
}else{
$this->output = $langs->trans('TimesheetWeekReminderSendSuccess', $emailsSent);
dol_syslog(__METHOD__." end - ".$this->output, LOG_INFO);
return 0;
}
}

/**
* Send a reminder test email to the current user using the configured template.
*
* @param DoliDB $db Database handler
* @param User $user Current user
* @return int <0 if KO, >=0 if OK (number of emails sent)
*/
public function sendTest($db, User $user)
{
return self::run($db, 1, 1, array((int) $user->id));
}
}
Loading