Add multi-line progress alert rendering and support for department, customer, and product names
- Introduce `renderDanishPeriodSummary` to display detailed progress (daily, weekly, monthly) with optional targets.
- Add `commaNamesFromObjects` helper to extract and format names for departments, customers, and products.
- Update alert composition logic to differentiate between single-line and multi-line rendering.
- Extend `progress_alert_format` to support new tokens `{department_names}`, `{customer_names}`, and `{product_names}`.
This commit is contained in:
@@ -24,6 +24,11 @@ class goals_progress_alert_renderer
|
||||
$timeframe = ($tfStart && $tfEnd) ? "$tfStart → $tfEnd" : '';
|
||||
$deptList = implode(',', $criteria->departments?->listIDs() ?? []);
|
||||
|
||||
// Prepare readable names for fields
|
||||
$deptNames = self::commaNamesFromObjects($criteria->departments?->list() ?? [], 'department');
|
||||
$customerNames = self::commaNamesFromObjects($criteria->users?->list() ?? [], 'user');
|
||||
$productNames = self::commaNamesFromObjects($criteria->products?->list() ?? [], 'product');
|
||||
|
||||
$prefix = match ($criteria->progress_alert_style) {
|
||||
Style::DEPARTMENT_COMPARE => '[Dept compare] ',
|
||||
Style::COLLECTIVE => '[Collective] ',
|
||||
@@ -32,24 +37,39 @@ class goals_progress_alert_renderer
|
||||
};
|
||||
|
||||
$body = match ($criteria->progress_alert_progress_type) {
|
||||
// New DK multi-line formats for COUNT_ONLY and ALL
|
||||
PType::COUNT_ONLY => self::renderDanishPeriodSummary($criteria, includeTargets: false,
|
||||
header: [
|
||||
$deptNames !== '' ? "Afdeling: $deptNames" : null,
|
||||
$customerNames !== '' ? "Kunde: $customerNames" : null,
|
||||
$productNames !== '' ? "Produkt: $productNames" : null,
|
||||
]),
|
||||
PType::ALL => self::renderDanishPeriodSummary($criteria, includeTargets: $target > 0,
|
||||
header: [
|
||||
$deptNames !== '' ? "Afdeling: $deptNames" : null,
|
||||
$customerNames !== '' ? "Kunde: $customerNames" : null,
|
||||
$productNames !== '' ? "Produkt: $productNames" : null,
|
||||
]),
|
||||
// Legacy single-line fallbacks
|
||||
PType::PERCENTAGE_ONLY => sprintf('Progress: %s%%', number_format($percent, 2)),
|
||||
PType::COUNT_ONLY => sprintf('Progress: %d', $count),
|
||||
PType::COUNT_AND_TARGET => sprintf('Progress: %d / %d', $count, $target),
|
||||
PType::ALL => sprintf('Progress: %s%% (%d / %d)', number_format($percent, 2), $count, $target),
|
||||
default => sprintf('Progress: %s%%', number_format($percent, 2)),
|
||||
};
|
||||
|
||||
$parts = array_filter([
|
||||
$prefix . $label,
|
||||
$body,
|
||||
$timeframe,
|
||||
$deptList ? "Depts: $deptList" : null,
|
||||
]);
|
||||
|
||||
$composed = implode(' | ', $parts);
|
||||
// Keep old composition only for legacy one-line body strings
|
||||
$composed = $body;
|
||||
if (!str_contains($body, "\n")) {
|
||||
$parts = array_filter([
|
||||
$prefix . $label,
|
||||
$body,
|
||||
$timeframe,
|
||||
$deptList ? "Depts: $deptList" : null,
|
||||
]);
|
||||
$composed = implode(' | ', $parts);
|
||||
}
|
||||
|
||||
// If a custom template string is provided, use it as a printf-style template
|
||||
// Supported tokens: {label}, {percent}, {count}, {target}, {timeframe}, {departments}, {prefix}
|
||||
// Supported tokens: {label}, {percent}, {count}, {target}, {timeframe}, {departments}, {prefix}, {body}, {department_names}, {customer_names}, {product_names}
|
||||
if (is_string($criteria->progress_alert_format) && $criteria->progress_alert_format !== '') {
|
||||
$tmpl = $criteria->progress_alert_format;
|
||||
$map = [
|
||||
@@ -61,6 +81,9 @@ class goals_progress_alert_renderer
|
||||
'{departments}' => $deptList,
|
||||
'{prefix}' => $prefix,
|
||||
'{body}' => $body,
|
||||
'{department_names}' => $deptNames,
|
||||
'{customer_names}' => $customerNames,
|
||||
'{product_names}' => $productNames,
|
||||
];
|
||||
$composed = strtr($tmpl, $map);
|
||||
}
|
||||
@@ -86,4 +109,101 @@ class goals_progress_alert_renderer
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static function renderDanishPeriodSummary(goals_criteria $criteria, bool $includeTargets, array $header = []): string
|
||||
{
|
||||
// Build period windows
|
||||
$now = new \DateTimeImmutable('now');
|
||||
$yesterdayStart = $now->modify('-1 day')->setTime(0, 0, 0);
|
||||
$yesterdayEnd = $now->modify('-1 day')->setTime(23, 59, 59);
|
||||
|
||||
// Week start (Monday) to now
|
||||
$weekStart = (new \DateTimeImmutable('monday this week'))->setTime(0, 0, 0);
|
||||
if ($now->format('N') === '1') { // if Monday, "monday this week" is today, ok
|
||||
$weekStart = $now->setTime(0, 0, 0);
|
||||
}
|
||||
|
||||
// Month start to now
|
||||
$monthStart = $now->setDate((int)$now->format('Y'), (int)$now->format('m'), 1)->setTime(0, 0, 0);
|
||||
|
||||
$yCount = self::progressForWindow($criteria, $yesterdayStart, $yesterdayEnd);
|
||||
$wCount = self::progressForWindow($criteria, $weekStart, $now);
|
||||
$mCount = self::progressForWindow($criteria, $monthStart, $now);
|
||||
|
||||
$target = (int)($criteria->target ?? 0);
|
||||
|
||||
$lines = array_values(array_filter($header));
|
||||
if ($includeTargets && $target > 0) {
|
||||
$yPct = number_format($yCount > 0 ? ($yCount / max(1, $target)) * 100 : 0, 0);
|
||||
$wPct = number_format($wCount > 0 ? ($wCount / max(1, $target)) * 100 : 0, 0);
|
||||
$mPct = number_format($mCount > 0 ? ($mCount / max(1, $target)) * 100 : 0, 0);
|
||||
$lines[] = sprintf('Igår: %d ud af %d (%s%%)', $yCount, $target, $yPct);
|
||||
$lines[] = sprintf('Ugen total: %d ud af %d (%s%%)', $wCount, $target, $wPct);
|
||||
$lines[] = sprintf('Måneden total: %d ud af %d (%s%%)', $mCount, $target, $mPct);
|
||||
} else {
|
||||
$lines[] = sprintf('Igår: %d', $yCount);
|
||||
$lines[] = sprintf('Ugen total: %d', $wCount);
|
||||
$lines[] = sprintf('Måneden total: %d', $mCount);
|
||||
}
|
||||
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
private static function progressForWindow(goals_criteria $criteria, \DateTimeInterface $start, \DateTimeInterface $end): int
|
||||
{
|
||||
// Clone criteria and adjust timeframe
|
||||
$clone = clone $criteria;
|
||||
$clone->start = (clone \DateTime::createFromInterface($start));
|
||||
$clone->end = (clone \DateTime::createFromInterface($end));
|
||||
return self::getProgress($clone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to extract comma-separated names from domain objects using common patterns.
|
||||
* Fallback to IDs or customer numbers when necessary.
|
||||
* @param array $objects
|
||||
* @param string $type one of: department|user|product
|
||||
*/
|
||||
private static function commaNamesFromObjects(array $objects, string $type): string
|
||||
{
|
||||
$names = [];
|
||||
foreach ($objects as $obj) {
|
||||
try {
|
||||
switch ($type) {
|
||||
case 'department':
|
||||
// departments_o has ->name object_property
|
||||
if (isset($obj->name)) {
|
||||
$val = (string)$obj->name->value();
|
||||
if ($val !== '') { $names[] = $val; break; }
|
||||
}
|
||||
// fallback to id
|
||||
if (isset($obj->id)) { $names[] = (string)$obj->id; }
|
||||
break;
|
||||
case 'user':
|
||||
// users_o may have display_name or economic customer name cache
|
||||
if (isset($obj->display_name)) {
|
||||
$val = (string)$obj->display_name->value();
|
||||
if ($val !== '') { $names[] = $val; break; }
|
||||
}
|
||||
if (method_exists($obj, 'getCustomerName') && isset($obj->customer_number)) {
|
||||
$num = (int)$obj->customer_number->value();
|
||||
$name = $obj->getCustomerName($num);
|
||||
if (is_string($name) && $name !== '') { $names[] = $name; break; }
|
||||
}
|
||||
if (isset($obj->customer_number)) { $names[] = (string)$obj->customer_number->value(); }
|
||||
break;
|
||||
case 'product':
|
||||
if (isset($obj->name)) {
|
||||
$val = (string)$obj->name->value();
|
||||
if ($val !== '') { $names[] = $val; break; }
|
||||
}
|
||||
if (isset($obj->id)) { $names[] = (string)$obj->id; }
|
||||
break;
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// ignore and continue
|
||||
}
|
||||
}
|
||||
return implode(', ', $names);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user