feat(gamification): clear auto_completed on uncomplete

uncomplete_task now clears auto_completed alongside done / done_at on
both the target row and the cascaded-parent reopen. Keeps the flag
accurate so a later re-completion via a different path is counted
correctly by the Phase 8 daily-count query.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-24 20:18:33 +01:00
parent b992967e50
commit 839754f4ee

View File

@@ -487,7 +487,7 @@ pub async fn uncomplete_task(pool: &SqlitePool, id: &str) -> Result<()> {
.await
.map_err(|e| KonError::StorageError(format!("Begin transaction failed: {e}")))?;
sqlx::query("UPDATE tasks SET done = 0, done_at = NULL WHERE id = ?")
sqlx::query("UPDATE tasks SET done = 0, done_at = NULL, auto_completed = 0 WHERE id = ?")
.bind(id)
.execute(&mut *tx)
.await
@@ -507,7 +507,10 @@ pub async fn uncomplete_task(pool: &SqlitePool, id: &str) -> Result<()> {
.flatten();
if let Some(pid) = parent_id {
sqlx::query("UPDATE tasks SET done = 0, done_at = NULL WHERE id = ? AND done = 1")
sqlx::query(
"UPDATE tasks SET done = 0, done_at = NULL, auto_completed = 0 \
WHERE id = ? AND done = 1",
)
.bind(&pid)
.execute(&mut *tx)
.await
@@ -2077,4 +2080,48 @@ mod tests {
assert_eq!(s1_auto, 0, "subtask completion is manual");
assert_eq!(s2_auto, 0, "subtask completion is manual");
}
#[tokio::test]
async fn uncomplete_clears_auto_completed_on_parent() {
let pool = test_pool().await;
insert_task(
&pool,
"parent",
"Parent task",
"inbox",
None,
None,
None,
None,
)
.await
.unwrap();
insert_subtask(&pool, "s1", "Only step", "parent")
.await
.unwrap();
// Cascade closes the parent with auto_completed = 1.
complete_subtask_and_check_parent(&pool, "s1")
.await
.unwrap();
// Uncompleting the subtask reopens the parent (existing invariant)
// and must also clear auto_completed on the parent so a later
// manual re-completion is counted cleanly.
uncomplete_task(&pool, "s1").await.unwrap();
let parent_done: i64 = sqlx::query_scalar("SELECT done FROM tasks WHERE id = 'parent'")
.fetch_one(&pool)
.await
.unwrap();
let parent_auto: i64 =
sqlx::query_scalar("SELECT auto_completed FROM tasks WHERE id = 'parent'")
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(parent_done, 0, "parent reopens when child is uncompleted");
assert_eq!(parent_auto, 0, "auto_completed must clear on reopen");
}
}