TODO/DONE auto-mark done when all subtasks done

This commit is contained in:
2026-07-20 17:35:20 -03:00
parent cc397e9982
commit ca57a4044e
6 changed files with 99 additions and 2 deletions

View File

@@ -3,6 +3,7 @@
namespace Kanboard\Plugin\OrganonTweaks\Helper;
use Kanboard\Core\Base;
use Kanboard\Model\SubtaskModel;
/**
* Done/Todo badge helper.
@@ -22,6 +23,8 @@ class OrganonDoneHelper extends Base
{
const DONE_KEY = 'organon_done';
const CLOSES_KEY = 'organon_tweaks_done_closes_task';
const AUTOSUB_KEY = 'organon_tweaks_done_autosubtasks';
const ALLDONE_KEY = 'organon_subtasks_alldone';
/**
* Is this task Done? (mode-aware: closed status in close-mode, else the metadata marker)
@@ -47,4 +50,63 @@ class OrganonDoneHelper extends Base
{
return (int) $this->configModel->get(self::CLOSES_KEY, 0) === 1;
}
/**
* Mark a task Done in the current mode. Idempotent -- a no-op if it is already Done. Close-mode
* closes the task; marker-mode sets the DONE_KEY metadata.
*
* @param int $task_id
*/
public function markDone($task_id)
{
$task = $this->taskFinderModel->getById((int) $task_id);
if (empty($task) || $this->isDone($task)) {
return;
}
if ($this->closesTask()) {
$this->taskStatusModel->close((int) $task_id);
} else {
$this->taskMetadataModel->save((int) $task_id, array(self::DONE_KEY => 'on'));
}
}
/**
* Recompute whether the task's subtasks are ALL done and, only on the up-transition
* (was-not-all-done -> now-all-done), auto-mark the task Done. The last-seen all-done state is kept
* in ALLDONE_KEY ('on'/'off') to detect that edge -- so a manual unmark is honored (nothing
* re-fires) and title edits / refreshes never re-mark. Never auto-unmarks.
*
* @param int $task_id
* @param int $exclude_id a subtask id to exclude from the counts (the row being deleted --
* EVENT_DELETE fires BEFORE the row is removed, so it still counts)
*/
public function syncSubtasksDone($task_id, $exclude_id = 0)
{
$task_id = (int) $task_id;
$exclude_id = (int) $exclude_id;
$totalQuery = $this->db->table(SubtaskModel::TABLE)->eq('task_id', $task_id);
$doneQuery = $this->db->table(SubtaskModel::TABLE)->eq('task_id', $task_id)->eq('status', SubtaskModel::STATUS_DONE);
if ($exclude_id > 0) {
$totalQuery->neq('id', $exclude_id);
$doneQuery->neq('id', $exclude_id);
}
$total = $totalQuery->count();
$done = $doneQuery->count();
$allDone = $total > 0 && $done === $total;
$prev = $this->taskMetadataModel->get($task_id, self::ALLDONE_KEY, 'off') === 'on';
// Save the new state BEFORE marking: in close-mode markDone() -> close() -> closeAll() re-fires
// subtask events into this method; with the marker already 'on', that re-entry sees no
// transition and is a no-op (no loop).
$this->taskMetadataModel->save($task_id, array(self::ALLDONE_KEY => $allDone ? 'on' : 'off'));
if ($allDone && ! $prev) {
$this->markDone($task_id);
}
}
}