Allow approval after draft revisions

This commit is contained in:
Daniel Maddern 2026-08-29 00:14:42 +07:00
parent 829a0361d4
commit ca045c02d7
2 changed files with 32 additions and 2 deletions

View file

@ -648,9 +648,9 @@ class BookStateService:
)
if not validation["valid"]:
raise ValueError("book state is not approval-valid: " + "; ".join(validation["errors"]))
parent_id = _value(locked, "parent_id")
current_id = _value(work, "current_book_state_id")
if parent_id != current_id:
approved_base_id = self._approved_base_id(locked)
if approved_base_id != current_id:
raise ValueError("book state parent is stale relative to the work's approved state")
if force and not notes.strip():
raise ValueError("forced approval requires notes")
@ -684,6 +684,18 @@ class BookStateService:
self._write_artifacts(locked)
return locked
@staticmethod
def _approved_base_id(state: BookStateVersion) -> Any:
ancestor_id = state.parent_id
while ancestor_id:
ancestor = BookStateVersion.objects.select_for_update().only(
"id", "parent_id", "status"
).get(pk=ancestor_id)
if ancestor.status == BookStateStatus.APPROVED:
return ancestor.id
ancestor_id = ancestor.parent_id
return None
@transaction.atomic
def reject(
self, state: BookStateVersion, *, actor: str = "", notes: str = ""

View file

@ -230,6 +230,24 @@ def test_approval_requires_all_reviews_and_rejects_stale_parent(work: Work) -> N
service.approve(child)
def test_approval_allows_review_revisions_before_first_approved_state(work: Work) -> None:
service = BookStateService(FakeReviewRouter())
first = service.create(work=work, content=book_content(1))
second_content = deepcopy(first.content)
second_content["open_questions"] = ["What must the pilot verify?"]
second = service.revise(first, content=second_content)
third_content = deepcopy(second.content)
third_content["open_questions"] = ["What evidence resolves the review finding?"]
third = service.revise(second, content=third_content)
make_review_ready(third)
approved = service.approve(third, actor="editor")
work.refresh_from_db()
assert approved.status == BookStateStatus.APPROVED
assert work.current_book_state == approved
def test_review_routes_structure_and_continuity_to_distinct_capabilities(work: Work) -> None:
router = FakeReviewRouter()
service = BookStateService(router)