From 010bfd365357f663733fb0e3664ac1fdb82e1fc5 Mon Sep 17 00:00:00 2001 From: bboysoul Date: Wed, 9 Sep 2026 11:01:36 +0800 Subject: [PATCH] fix: reset subtasks for recurring tasks --- backend/main.py | 14 +++++++++++++ tests/test_mvp_backend.py | 41 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/backend/main.py b/backend/main.py index ec4ab70..2e349d8 100644 --- a/backend/main.py +++ b/backend/main.py @@ -640,6 +640,7 @@ async def update_task( data = payload.model_dump(exclude_unset=True) expected_version = data.pop("version") recurrence = None + reset_subtasks = False if data.get("completed") is True: recurrence = await db.scalar(select(RecurrenceTemplate).where( RecurrenceTemplate.task_id == task_id, RecurrenceTemplate.user_id == user.id @@ -656,6 +657,7 @@ async def update_task( data["completed"] = False data["due_at"] = next_items[0] recurrence.starts_at = next_items[0] + reset_subtasks = True if "list_id" in data: await _owned_list(db, user.id, data["list_id"]) if task.parent_id: @@ -685,6 +687,18 @@ async def update_task( if exists_id: raise HTTPException(status_code=409, detail="任务已被更新,请刷新后重试") raise HTTPException(status_code=404, detail="任务不存在") + if reset_subtasks: + reset_at = utcnow() + await db.execute( + update(Task) + .where( + Task.parent_id == task.id, + Task.user_id == user.id, + Task.deleted_at.is_(None), + Task.completed.is_(True), + ) + .values(completed=False, version=Task.version + 1, updated_at=reset_at) + ) if "list_id" in data and task.parent_id is None: await db.execute( update(Task) diff --git a/tests/test_mvp_backend.py b/tests/test_mvp_backend.py index 1446024..5b663e3 100644 --- a/tests/test_mvp_backend.py +++ b/tests/test_mvp_backend.py @@ -134,6 +134,47 @@ def test_completing_repeating_task_advances_due_date_instead_of_closing_it(clien assert recurrence["starts_at"].replace("Z", "") == "2026-09-08T09:00:00" +def test_completing_repeating_task_resets_completed_subtasks_for_next_occurrence(client): + inbox = boot(client) + parent = client.post( + "/api/v1/tasks", + json={ + "title": "每日清理", + "list_id": inbox["id"], + "due_at": "2026-09-07T09:00:00Z", + "rrule": "FREQ=DAILY", + }, + ).json() + first = client.post( + "/api/v1/tasks", + json={"title": "清理下载", "list_id": inbox["id"], "parent_id": parent["id"]}, + ).json() + second = client.post( + "/api/v1/tasks", + json={"title": "清理缓存", "list_id": inbox["id"], "parent_id": parent["id"]}, + ).json() + for child in (first, second): + response = client.patch( + f"/api/v1/tasks/{child['id']}", + json={"completed": True, "version": child["version"]}, + ) + assert response.status_code == 200 + + completed = client.patch( + f"/api/v1/tasks/{parent['id']}", + json={"completed": True, "version": parent["version"]}, + ) + + assert completed.status_code == 200 + assert completed.json()["completed"] is False + assert completed.json()["due_at"].replace("Z", "") == "2026-09-08T09:00:00" + assert [child["completed"] for child in completed.json()["subtasks"]] == [False, False] + listed = client.get("/api/v1/tasks", params={"list_id": inbox["id"], "page": 1}).json()["items"] + repeated = next(task for task in listed if task["id"] == parent["id"]) + assert [child["completed"] for child in repeated["subtasks"]] == [False, False] + + + def test_recurrence_mutations_keep_exact_timestamp_validation(client): inbox = boot(client) task = client.post(