Fix overdue check to compare real timestamps, not whole calendar days

WorkflowRunner::isOverdue() took Card::getDaysUntilDue(), which Deck
computes by truncating both "now" and the due date to midnight before
diffing. A card due earlier today therefore reported 0 days until due -
not overdue - until the calendar date rolled over to the next day, so
a card due "this morning" sat unmoved for up to 24h while one due
"yesterday morning" moved immediately. isOverdue() now takes the due
date and $now directly and compares them as real timestamps, matching
what Deck's own UI shows.

Bump to 34.0.2.
This commit is contained in:
Patrick Niebeling
2026-08-25 13:48:02 +02:00
parent b203ba35f8
commit c8d8905d94
4 changed files with 35 additions and 10 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ This app lets every user configure their own Deck automation workflows from Pers
A background job periodically evaluates all enabled workflows and moves cards whose due date has passed, using Deck's own internal PHP classes only (no HTTP/OCS calls). A background job periodically evaluates all enabled workflows and moves cards whose due date has passed, using Deck's own internal PHP classes only (no HTTP/OCS calls).
]]></description> ]]></description>
<version>34.0.1</version> <version>34.0.2</version>
<licence>agpl</licence> <licence>agpl</licence>
<author mail="patrick@niebel.ing">Patrick Niebeling</author> <author mail="patrick@niebel.ing">Patrick Niebeling</author>
<namespace>WorkflowDeckAutomation</namespace> <namespace>WorkflowDeckAutomation</namespace>
+15 -3
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace OCA\WorkflowDeckAutomation\Service; namespace OCA\WorkflowDeckAutomation\Service;
use DateTimeInterface;
use OCA\Deck\Db\Card; use OCA\Deck\Db\Card;
use OCA\WorkflowDeckAutomation\Db\Workflow; use OCA\WorkflowDeckAutomation\Db\Workflow;
use OCA\WorkflowDeckAutomation\Db\WorkflowMapper; use OCA\WorkflowDeckAutomation\Db\WorkflowMapper;
@@ -165,8 +166,10 @@ class WorkflowRunner {
// and the workflow wants a mail — this is decoration, not a check. // and the workflow wants a mail — this is decoration, not a check.
$targets = null; $targets = null;
$now = $this->timeFactory->getDateTime();
foreach ($cards as $card) { foreach ($cards as $card) {
if (!self::isOverdue($card->getDaysUntilDue())) { if (!self::isOverdue($card->getDuedate(), $now)) {
continue; continue;
} }
@@ -319,8 +322,17 @@ class WorkflowRunner {
} }
} }
public static function isOverdue(?int $daysUntilDue): bool { /**
return $daysUntilDue !== null && $daysUntilDue < 0; * Compares the card's actual due timestamp against $now instead of Deck's own
* Card::getDaysUntilDue(), which truncates both sides to midnight before diffing
* (DateInterval's %a is a whole-day count) and therefore reports 0 - not overdue -
* for any card whose due time has already passed today. That silently held back
* every card for up to 24h after it actually fell due; comparing the raw datetime
* instead matches what Deck's own UI shows (e.g. "vor 2 Stunden") and has no
* day-rounding step to get wrong.
*/
public static function isOverdue(?DateTimeInterface $duedate, DateTimeInterface $now): bool {
return $duedate !== null && $duedate < $now;
} }
/** /**
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "workflow_deck_automation", "name": "workflow_deck_automation",
"version": "34.0.1", "version": "34.0.2",
"private": true, "private": true,
"type": "module", "type": "module",
"description": "Automates moving overdue Deck cards between stacks, configurable per user.", "description": "Automates moving overdue Deck cards between stacks, configurable per user.",
@@ -4,22 +4,35 @@ declare(strict_types=1);
namespace OCA\WorkflowDeckAutomation\Tests\Unit\Service; namespace OCA\WorkflowDeckAutomation\Tests\Unit\Service;
use DateTimeImmutable;
use OCA\WorkflowDeckAutomation\Service\WorkflowRunner; use OCA\WorkflowDeckAutomation\Service\WorkflowRunner;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class WorkflowRunnerFilterTest extends TestCase { class WorkflowRunnerFilterTest extends TestCase {
public function testIsOverdueReturnsFalseWithoutDueDate(): void { public function testIsOverdueReturnsFalseWithoutDueDate(): void {
$this->assertFalse(WorkflowRunner::isOverdue(null)); $this->assertFalse(WorkflowRunner::isOverdue(null, new DateTimeImmutable('2026-01-15 12:00:00')));
} }
public function testIsOverdueReturnsFalseForFutureDueDate(): void { public function testIsOverdueReturnsFalseForFutureDueDate(): void {
$this->assertFalse(WorkflowRunner::isOverdue(0)); $now = new DateTimeImmutable('2026-01-15 12:00:00');
$this->assertFalse(WorkflowRunner::isOverdue(3)); $this->assertFalse(WorkflowRunner::isOverdue($now->modify('+1 hour'), $now));
$this->assertFalse(WorkflowRunner::isOverdue($now->modify('+3 days'), $now));
} }
public function testIsOverdueReturnsTrueForPastDueDate(): void { public function testIsOverdueReturnsTrueForPastDueDate(): void {
$this->assertTrue(WorkflowRunner::isOverdue(-1)); $now = new DateTimeImmutable('2026-01-15 12:00:00');
$this->assertTrue(WorkflowRunner::isOverdue(-30)); $this->assertTrue(WorkflowRunner::isOverdue($now->modify('-1 hour'), $now));
$this->assertTrue(WorkflowRunner::isOverdue($now->modify('-30 days'), $now));
}
public function testIsOverdueReturnsTrueForADueTimeEarlierTheSameDay(): void {
// Regression: comparing whole calendar days (as Deck's own
// Card::getDaysUntilDue() does) reports a card due earlier today as
// "0 days until due", not overdue, until the calendar date rolls
// over - holding every card back for up to 24h after it actually
// fell due. isOverdue() must compare the real timestamps instead.
$now = new DateTimeImmutable('2026-01-15 12:00:00');
$this->assertTrue(WorkflowRunner::isOverdue(new DateTimeImmutable('2026-01-15 06:00:00'), $now));
} }
public function testNoFiltersMatchesAnyCard(): void { public function testNoFiltersMatchesAnyCard(): void {