Add advanced target duration configurations and parsing logic to Goals module. Update Workfeed with new CompanyID config. Extend OpenAPI spec with detailed schema mappings and examples.
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
namespace goals\classes;
|
||||
|
||||
use classes\db;
|
||||
use goals\helpers\goals_criteria_target_duration;
|
||||
use goals\helpers\goals_criteria_type;
|
||||
use goals\helpers\goals_criteria_progress_alert_frequency;
|
||||
use goals\helpers\goals_criteria_progress_alert_destination;
|
||||
@@ -87,6 +88,18 @@ class goals_criteria implements goals_criteria_i
|
||||
* @var array<int,float>
|
||||
*/
|
||||
public array $department_weekly_targets;
|
||||
/**
|
||||
* Optional advanced target duration mode.
|
||||
* When null, legacy target behavior is preserved.
|
||||
* @var goals_criteria_target_duration|null
|
||||
*/
|
||||
public ?goals_criteria_target_duration $target_duration = null;
|
||||
/**
|
||||
* Optional cadence amount for advanced target duration modes.
|
||||
* Used for WEEKS, MONTHS, YEARS. Ignored for ENTIRE_DURATION.
|
||||
* @var int|null
|
||||
*/
|
||||
public ?int $target_duration_every = null;
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
@@ -103,6 +116,8 @@ class goals_criteria implements goals_criteria_i
|
||||
$this->progress_alert_style = goals_criteria_progress_alert_style::NONE;
|
||||
$this->department_daily_targets = [];
|
||||
$this->department_weekly_targets = [];
|
||||
$this->target_duration = null;
|
||||
$this->target_duration_every = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,6 +149,31 @@ class goals_criteria implements goals_criteria_i
|
||||
}
|
||||
}
|
||||
|
||||
// Parse advanced target duration mode (ignore unknowns)
|
||||
$rawTargetDuration = null;
|
||||
if (isset($data['target_duration']) && is_string($data['target_duration'])) {
|
||||
$rawTargetDuration = $data['target_duration'];
|
||||
} elseif (isset($data['targetDuration']) && is_string($data['targetDuration'])) {
|
||||
$rawTargetDuration = $data['targetDuration'];
|
||||
}
|
||||
if (is_string($rawTargetDuration)) {
|
||||
$duration = goals_criteria_target_duration::tryFrom($rawTargetDuration);
|
||||
if ($duration !== null) {
|
||||
$criteria->target_duration = $duration;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse advanced target duration cadence value (>=1)
|
||||
$rawTargetDurationEvery = null;
|
||||
if (isset($data['target_duration_every'])) {
|
||||
$rawTargetDurationEvery = $data['target_duration_every'];
|
||||
} elseif (isset($data['targetDurationEvery'])) {
|
||||
$rawTargetDurationEvery = $data['targetDurationEvery'];
|
||||
}
|
||||
if (is_numeric($rawTargetDurationEvery)) {
|
||||
$criteria->target_duration_every = max(1, (int)$rawTargetDurationEvery);
|
||||
}
|
||||
|
||||
// Sanitize label: trim, strip tags, collapse whitespace, max length 255
|
||||
if (isset($data['label']) && is_string($data['label'])) {
|
||||
$label = trim($data['label']);
|
||||
@@ -277,6 +317,16 @@ class goals_criteria implements goals_criteria_i
|
||||
}
|
||||
}
|
||||
$criteria->department_daily_targets = $targets;
|
||||
} elseif (isset($data['departmentDailyTargets']) && is_array($data['departmentDailyTargets'])) {
|
||||
$targets = [];
|
||||
foreach ($data['departmentDailyTargets'] as $deptId => $target) {
|
||||
if (is_numeric($deptId) && is_numeric($target)) {
|
||||
$id = (int)$deptId;
|
||||
$t = max(0.0, (float)$target);
|
||||
$targets[$id] = $t;
|
||||
}
|
||||
}
|
||||
$criteria->department_daily_targets = $targets;
|
||||
}
|
||||
// Parse department weekly targets (expects object with department_id => target)
|
||||
if (isset($data['department_weekly_targets']) && is_array($data['department_weekly_targets'])) {
|
||||
@@ -447,6 +497,8 @@ class goals_criteria implements goals_criteria_i
|
||||
return [
|
||||
'type' => $this->type?->name ?? goals_criteria_type::NONE->name,
|
||||
'target' => $this->target ?? 0,
|
||||
'target_duration' => $this->target_duration?->name,
|
||||
'target_duration_every' => $this->target_duration_every,
|
||||
'label' => (string)$this->label,
|
||||
'start' => ($this->start instanceof \DateTimeInterface) ? $this->start->format(DATE_ATOM) : null,
|
||||
'end' => ($this->end instanceof \DateTimeInterface) ? $this->end->format(DATE_ATOM) : null,
|
||||
@@ -608,6 +660,11 @@ class goals_criteria implements goals_criteria_i
|
||||
return $this->getProgress();
|
||||
}
|
||||
|
||||
public function usesAdvancedTargetDuration(): bool
|
||||
{
|
||||
return $this->target_duration instanceof goals_criteria_target_duration;
|
||||
}
|
||||
|
||||
public function validateAndSanitize(): void
|
||||
{
|
||||
// Normalize custom daily targets map: ints and non-negative; filter to selected departments
|
||||
@@ -636,6 +693,20 @@ class goals_criteria implements goals_criteria_i
|
||||
$this->target = 0;
|
||||
}
|
||||
|
||||
// Normalize advanced target duration values
|
||||
if ($this->target_duration !== null && !in_array($this->target_duration, goals_criteria_target_duration::cases(), true)) {
|
||||
$this->target_duration = null;
|
||||
}
|
||||
if ($this->target_duration === goals_criteria_target_duration::ENTIRE_DURATION) {
|
||||
$this->target_duration_every = null;
|
||||
} elseif ($this->target_duration !== null) {
|
||||
$every = (int)($this->target_duration_every ?? 1);
|
||||
$this->target_duration_every = max(1, $every);
|
||||
} else {
|
||||
// Strict legacy compatibility branch marker: null duration means old behavior.
|
||||
$this->target_duration_every = null;
|
||||
}
|
||||
|
||||
// Ensure timeframe is valid
|
||||
if (($this->start instanceof \DateTimeInterface) && ($this->end instanceof \DateTimeInterface)) {
|
||||
if ($this->end < $this->start) {
|
||||
@@ -751,6 +822,21 @@ class goals_criteria implements goals_criteria_i
|
||||
{
|
||||
$results = [];
|
||||
$department_ids = $this->departments->listIDs();
|
||||
if ($this->usesAdvancedTargetDuration()) {
|
||||
foreach ($department_ids as $dept_id) {
|
||||
$deptId = (int)$dept_id;
|
||||
$results[$deptId] = [
|
||||
'all' => $this->getAdvancedProgressDetailsForDepartment($deptId, null, $department_ids),
|
||||
'today' => $this->getAdvancedProgressDetailsForDepartment($deptId, 'today', $department_ids),
|
||||
'week' => $this->getAdvancedProgressDetailsForDepartment($deptId, 'week', $department_ids),
|
||||
'month' => $this->getAdvancedProgressDetailsForDepartment($deptId, 'month', $department_ids),
|
||||
'year' => $this->getAdvancedProgressDetailsForDepartment($deptId, 'year', $department_ids),
|
||||
'to_date' => $this->getAdvancedProgressDetailsForDepartment($deptId, 'to_date', $department_ids),
|
||||
];
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
foreach ($department_ids as $dept_id) {
|
||||
$dept_criteria = clone $this;
|
||||
$dept_criteria->departments->set([(new \objects\departments_o())->select($dept_id)]);
|
||||
@@ -787,6 +873,25 @@ class goals_criteria implements goals_criteria_i
|
||||
$criteria->setTimeframeByName($timeframe);
|
||||
}
|
||||
|
||||
if ($this->usesAdvancedTargetDuration()) {
|
||||
$target = 0.0;
|
||||
if (($criteria->start instanceof \DateTimeInterface) && ($criteria->end instanceof \DateTimeInterface)) {
|
||||
$target = $this->calculateTargetForRange(
|
||||
$criteria->start,
|
||||
$criteria->end,
|
||||
null,
|
||||
$this->departments->listIDs()
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
'count' => $criteria->getProgress(),
|
||||
'target' => $target,
|
||||
'date_from' => ($criteria->start instanceof \DateTimeInterface) ? $criteria->start->format(DATE_ATOM) : null,
|
||||
'date_end' => ($criteria->end instanceof \DateTimeInterface) ? $criteria->end->format(DATE_ATOM) : null,
|
||||
];
|
||||
}
|
||||
|
||||
$target = 0.0;
|
||||
$active_dept_ids = $criteria->departments->listIDs();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user