diff --git a/backend/app/services/board_lifecycle.py b/backend/app/services/board_lifecycle.py index f70a4840..a7ec2d74 100644 --- a/backend/app/services/board_lifecycle.py +++ b/backend/app/services/board_lifecycle.py @@ -23,6 +23,7 @@ from app.models.board_webhooks import BoardWebhook from app.models.organization_board_access import OrganizationBoardAccess from app.models.organization_invite_board_access import OrganizationInviteBoardAccess from app.models.tag_assignments import TagAssignment +from app.models.task_custom_fields import BoardTaskCustomField, TaskCustomFieldValue from app.models.task_dependencies import TaskDependency from app.models.task_fingerprints import TaskFingerprint from app.models.tasks import Task @@ -84,6 +85,12 @@ async def delete_board(session: AsyncSession, *, board: Board) -> OkResponse: col(TagAssignment.task_id).in_(task_ids), commit=False, ) + await crud.delete_where( + session, + TaskCustomFieldValue, + col(TaskCustomFieldValue.task_id).in_(task_ids), + commit=False, + ) # Keep teardown ordered around FK/reference chains so dependent rows are gone # before deleting their parent task/agent/board records. await crud.delete_where( @@ -129,6 +136,11 @@ async def delete_board(session: AsyncSession, *, board: Board) -> OkResponse: OrganizationInviteBoardAccess, col(OrganizationInviteBoardAccess.board_id) == board.id, ) + await crud.delete_where( + session, + BoardTaskCustomField, + col(BoardTaskCustomField.board_id) == board.id, + ) # Tasks reference agents and have dependent records. # Delete tasks before agents. diff --git a/backend/tests/test_boards_delete.py b/backend/tests/test_boards_delete.py index bd5809fd..228cdb69 100644 --- a/backend/tests/test_boards_delete.py +++ b/backend/tests/test_boards_delete.py @@ -64,13 +64,14 @@ async def test_delete_board_cleans_org_board_access_rows() -> None: deleted_table_names = [statement.table.name for statement in session.executed] assert "organization_board_access" in deleted_table_names assert "organization_invite_board_access" in deleted_table_names + assert "board_task_custom_fields" in deleted_table_names assert board in session.deleted assert session.committed == 1 @pytest.mark.asyncio async def test_delete_board_cleans_tag_assignments_before_tasks() -> None: - """Deleting a board should remove task-tag links before deleting tasks.""" + """Deleting a board should remove task-linked rows before deleting tasks.""" session: Any = _FakeSession(exec_results=[[], [uuid4()]]) board = Board( id=uuid4(), @@ -87,7 +88,11 @@ async def test_delete_board_cleans_tag_assignments_before_tasks() -> None: deleted_table_names = [statement.table.name for statement in session.executed] assert "tag_assignments" in deleted_table_names + assert "task_custom_field_values" in deleted_table_names assert deleted_table_names.index("tag_assignments") < deleted_table_names.index("tasks") + assert deleted_table_names.index("task_custom_field_values") < deleted_table_names.index( + "tasks" + ) @pytest.mark.asyncio