refactor(consolidations): on_document_confirmed принимает автора и комментарий перехода (#64)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-08 16:53:28 +04:00
parent d9324f2a38
commit ff65f2108b
3 changed files with 73 additions and 18 deletions

View File

@ -197,18 +197,22 @@ async def unbind_entity(session: AsyncSession, *, version_id: uuid.UUID, entity_
return _rowcount(result) return _rowcount(result)
async def on_document_confirmed( async def on_document_confirmed( # noqa: PLR0913 - автор и комментарий перехода задаёт вызывающий (OQ-157)
session: AsyncSession, session: AsyncSession,
audit: AuditService, audit: AuditService,
*, *,
document: Document, document: Document,
user_id: uuid.UUID, changed_by: uuid.UUID | None,
now: datetime.datetime, now: datetime.datetime,
comment: str | None = None,
) -> list[uuid.UUID]: ) -> list[uuid.UUID]:
"""Хук подтверждения (spec «Подтверждение документа»): привязывает уже `CONFIRMED` документ ко всем версиям «в """Хук подтверждения (spec «Подтверждение документа», osv-result-mapping «Автоподтверждение чистого документа»):
работе», которым он подходит, заменяя привязанный документ того же ключа (R-1); версия из `INITIATED` / привязывает уже `CONFIRMED` документ ко всем версиям «в работе», которым он подходит, заменяя привязанный документ
`DOCUMENTS_PROCESSING` переходит в `DATA_CORRECTION` (`changed_by = user_id`, D-6). Возвращает id версий, того же ключа (R-1); версия из `INITIATED` / `DOCUMENTS_PROCESSING` переходит в `DATA_CORRECTION` с `changed_by` и
получивших привязку (по возрастанию). `comment` вызывающего (ручное подтверждение — пользователь и `None`, автоподтверждение — загрузивший файл и
«автоподтверждение документа …», D-2). `changed_by is None` (файл без `uploaded_by`) — привязка создаётся, переход
пропускается с записью в лог (`consolidation_status_history.changed_by` NOT NULL). Возвращает id версий, получивших
привязку (по возрастанию).
Вызывать после блокировки файла и документа (D-9): версии блокирует `repo.lock_versions_for_entity` — advisory ГК Вызывать после блокировки файла и документа (D-9): версии блокирует `repo.lock_versions_for_entity` — advisory ГК
(создание версии ∥ подтверждение) и `FOR UPDATE` надмножества версий ГК (правка периметра ∥ подтверждение, R-2). (создание версии ∥ подтверждение) и `FOR UPDATE` надмножества версий ГК (правка периметра ∥ подтверждение, R-2).
@ -227,13 +231,21 @@ async def on_document_confirmed(
continue continue
await _bind(session, version.id, document) await _bind(session, version.id, document)
if version.status in MOVES_TO_CORRECTION: if version.status in MOVES_TO_CORRECTION:
if changed_by is None:
logger.info(
"Переход версии по подтверждению пропущен: нет автора (changed_by обязателен). "
"version_id={version_id}, document_id={document_id}",
version_id=version.id,
document_id=document.id,
)
else:
await transition( await transition(
session, session,
audit, audit,
version=version, version=version,
to=ConsolidationStatus.DATA_CORRECTION, to=ConsolidationStatus.DATA_CORRECTION,
changed_by=user_id, changed_by=changed_by,
comment=None, comment=comment,
now=now, now=now,
) )
bound.append(version.id) bound.append(version.id)

View File

@ -273,7 +273,7 @@ class DocumentService:
meta.reviewed_at = now meta.reviewed_at = now
await self._session.flush() await self._session.flush()
await consolidation_documents.on_document_confirmed( await consolidation_documents.on_document_confirmed(
self._session, self._audit.service, document=document, user_id=self._audit.user_id, now=now self._session, self._audit.service, document=document, changed_by=self._audit.user_id, now=now
) )
await self._audit.record( await self._audit.record(
action=AuditAction.DOCUMENT_CONFIRM, action=AuditAction.DOCUMENT_CONFIRM,

View File

@ -473,7 +473,7 @@ async def test_on_document_confirmed_returns_bound_versions(seeded: AsyncSession
user = await _user(seeded) user = await _user(seeded)
bound = await consolidation_documents.on_document_confirmed( bound = await consolidation_documents.on_document_confirmed(
seeded, AuditService(seeded), document=document, user_id=user.id, now=utc_now() seeded, AuditService(seeded), document=document, changed_by=user.id, now=utc_now()
) )
assert bound == sorted([q2.id, q3.id]) assert bound == sorted([q2.id, q3.id])
@ -481,6 +481,49 @@ async def test_on_document_confirmed_returns_bound_versions(seeded: AsyncSession
assert await _bound(seeded, q4.id) == set() assert await _bound(seeded, q4.id) == set()
@pytest.mark.integration
async def test_on_document_confirmed_without_changed_by_binds_but_keeps_status(seeded: AsyncSession) -> None:
"""Автоподтверждение файла без `uploaded_by` (OQ-157, D-2): привязка есть, перехода версии нет."""
group, entities = await _group(seeded)
version = await _version(seeded, group, entities)
document = await _confirmed(seeded, entities[0], quarter=Q2)
history = len(await _history(seeded, version.id))
bound = await consolidation_documents.on_document_confirmed(
seeded, AuditService(seeded), document=document, changed_by=None, now=utc_now()
)
assert bound == [version.id]
assert document.id in await _bound(seeded, version.id)
assert await _status(seeded, version.id) is ConsolidationStatus.INITIATED
assert len(await _history(seeded, version.id)) == history
@pytest.mark.integration
async def test_on_document_confirmed_writes_comment_to_history(seeded: AsyncSession) -> None:
"""Комментарий перехода от вызывающего (автоподтверждение) попадает в историю статусов."""
group, entities = await _group(seeded)
version = await _version(seeded, group, entities)
user = await _user(seeded)
document = await _confirmed(seeded, entities[0], quarter=Q2)
await consolidation_documents.on_document_confirmed(
seeded,
AuditService(seeded),
document=document,
changed_by=user.id,
comment="автоподтверждение документа OSV 60",
now=utc_now(),
)
last = (await _history(seeded, version.id))[-1]
assert (last.to_status, last.changed_by, last.comment) == (
ConsolidationStatus.DATA_CORRECTION,
user.id,
"автоподтверждение документа OSV 60",
)
# --- подхват ---------------------------------------------------------------------------------------------------------- # --- подхват ----------------------------------------------------------------------------------------------------------
@ -700,7 +743,7 @@ async def _confirm_in(session: AsyncSession, data: dict[str, Any]) -> list[uuid.
document.confirmed_at = now document.confirmed_at = now
await session.flush() await session.flush()
return await consolidation_documents.on_document_confirmed( return await consolidation_documents.on_document_confirmed(
session, AuditService(session), document=document, user_id=data["user_id"], now=now session, AuditService(session), document=document, changed_by=data["user_id"], now=now
) )