Add support for per-department custom daily targets

- Introduced `department_daily_targets` field in criteria, renderer, and API to define daily target overrides for departments.
- Enhanced daily target logic to honor per-department overrides while maintaining backward compatibility.
- Updated SMS, email, and Slack renderers for consistent enforcement of text length limits with fallback to non-mbstring functions.
- Refactored order item update logic to improve database interaction safety by using setters.
This commit is contained in:
Jeppe Bundgaard
2026-02-25 13:03:04 +01:00
parent 5cc311ae32
commit ffc5d6bc21
7 changed files with 167 additions and 16 deletions
@@ -77,6 +77,11 @@ class goals_criteria implements goals_criteria_i
* @var string|null $progress_alert_time_of_day
*/
public ?string $progress_alert_time_of_day = null;
/**
* Optional per-department custom daily targets (department_id => daily_target)
* @var array<int,int>
*/
public array $department_daily_targets = [];
/**
* Constructor
*/
@@ -255,6 +260,67 @@ class goals_criteria implements goals_criteria_i
$criteria->progress_alert_time_of_day = trim((string)$data['progressAlertTimeOfDay']);
}
// Parse department_daily_targets (assoc map or array of objects)
if (isset($data['department_daily_targets'])) {
$map = [];
if (is_array($data['department_daily_targets'])) {
$src = $data['department_daily_targets'];
// Case A: assoc map { "12": 3, "15": 5 }
$assocLike = array_keys($src) !== range(0, count($src) - 1);
if ($assocLike) {
foreach ($src as $k => $v) {
if (is_numeric($k) && is_numeric($v)) {
$dk = (int)$k; $dv = (int)$v;
if ($dk > 0 && $dv >= 0) { $map[$dk] = $dv; }
}
}
} else {
// Case B: list of objects [{id:12, daily_target:3}] or [{department_id:12, daily_target:3}]
foreach ($src as $item) {
if (!is_array($item) && !is_object($item)) { continue; }
$arr = (array)$item;
$dk = null;
if (isset($arr['id']) && is_numeric($arr['id'])) { $dk = (int)$arr['id']; }
elseif (isset($arr['department_id']) && is_numeric($arr['department_id'])) { $dk = (int)$arr['department_id']; }
$dv = null;
if (isset($arr['daily_target']) && is_numeric($arr['daily_target'])) { $dv = (int)$arr['daily_target']; }
elseif (isset($arr['dailyTarget']) && is_numeric($arr['dailyTarget'])) { $dv = (int)$arr['dailyTarget']; }
if ($dk !== null && $dk > 0 && $dv !== null && $dv >= 0) {
$map[$dk] = $dv;
}
}
}
}
$criteria->department_daily_targets = $map;
} elseif (isset($data['departmentDailyTargets']) && is_array($data['departmentDailyTargets'])) {
$map = [];
$src = $data['departmentDailyTargets'];
$assocLike = array_keys($src) !== range(0, count($src) - 1);
if ($assocLike) {
foreach ($src as $k => $v) {
if (is_numeric($k) && is_numeric($v)) {
$dk = (int)$k; $dv = (int)$v;
if ($dk > 0 && $dv >= 0) { $map[$dk] = $dv; }
}
}
} else {
foreach ($src as $item) {
if (!is_array($item) && !is_object($item)) { continue; }
$arr = (array)$item;
$dk = null;
if (isset($arr['id']) && is_numeric($arr['id'])) { $dk = (int)$arr['id']; }
elseif (isset($arr['department_id']) && is_numeric($arr['department_id'])) { $dk = (int)$arr['department_id']; }
$dv = null;
if (isset($arr['daily_target']) && is_numeric($arr['daily_target'])) { $dv = (int)$arr['daily_target']; }
elseif (isset($arr['dailyTarget']) && is_numeric($arr['dailyTarget'])) { $dv = (int)$arr['dailyTarget']; }
if ($dk !== null && $dk > 0 && $dv !== null && $dv >= 0) {
$map[$dk] = $dv;
}
}
}
$criteria->department_daily_targets = $map;
}
// Parse timeframe with validation
if (isset($data['start']) && is_string($data['start'])) {
try {
@@ -412,6 +478,20 @@ class goals_criteria implements goals_criteria_i
'progress_alert_format' => $this->progress_alert_format,
'progress_alert_weekdays' => array_map(fn($e) => ($e instanceof goals_criteria_progress_alert_weekday) ? $e->name : (string)$e, $this->progress_alert_weekdays ?? []),
'progress_alert_time_of_day' => $this->progress_alert_time_of_day,
'department_daily_targets' => (function(){
$map = [];
if (is_array($this->department_daily_targets)) {
// Only include targets for departments present in criteria
$deptIds = $this->departments?->listIDs() ?? [];
foreach ($this->department_daily_targets as $k => $v) {
$dk = (int)$k; $dv = (int)$v;
if (in_array($dk, $deptIds, true) && $dk > 0 && $dv >= 0) {
$map[$dk] = $dv;
}
}
}
return $map;
})(),
];
}
@@ -508,6 +588,18 @@ class goals_criteria implements goals_criteria_i
public function validateAndSanitize(): void
{
// Normalize custom daily targets map: ints and non-negative; filter to selected departments
if (!is_array($this->department_daily_targets)) { $this->department_daily_targets = []; }
$normalized = [];
$deptIds = $this->departments?->listIDs() ?? [];
foreach ($this->department_daily_targets as $k => $v) {
if (!is_numeric($k) || !is_numeric($v)) { continue; }
$dk = (int)$k; $dv = (int)$v;
if ($dk > 0 && $dv >= 0 && in_array($dk, $deptIds, true)) {
$normalized[$dk] = $dv;
}
}
$this->department_daily_targets = $normalized;
// Ensure target is non-negative
if ($this->target < 0) {
$this->target = 0;