diff --git a/api/src/services/consolidation_documents.py b/api/src/services/consolidation_documents.py index 51098b0..b178b40 100644 --- a/api/src/services/consolidation_documents.py +++ b/api/src/services/consolidation_documents.py @@ -197,18 +197,22 @@ async def unbind_entity(session: AsyncSession, *, version_id: uuid.UUID, entity_ return _rowcount(result) -async def on_document_confirmed( +async def on_document_confirmed( # noqa: PLR0913 - автор и комментарий перехода задаёт вызывающий (OQ-157) session: AsyncSession, audit: AuditService, *, document: Document, - user_id: uuid.UUID, + changed_by: uuid.UUID | None, now: datetime.datetime, + comment: str | None = None, ) -> list[uuid.UUID]: - """Хук подтверждения (spec «Подтверждение документа»): привязывает уже `CONFIRMED` документ ко всем версиям «в - работе», которым он подходит, заменяя привязанный документ того же ключа (R-1); версия из `INITIATED` / - `DOCUMENTS_PROCESSING` переходит в `DATA_CORRECTION` (`changed_by = user_id`, D-6). Возвращает id версий, - получивших привязку (по возрастанию). + """Хук подтверждения (spec «Подтверждение документа», osv-result-mapping «Автоподтверждение чистого документа»): + привязывает уже `CONFIRMED` документ ко всем версиям «в работе», которым он подходит, заменяя привязанный документ + того же ключа (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 ГК (создание версии ∥ подтверждение) и `FOR UPDATE` надмножества версий ГК (правка периметра ∥ подтверждение, R-2). @@ -227,15 +231,23 @@ async def on_document_confirmed( continue await _bind(session, version.id, document) if version.status in MOVES_TO_CORRECTION: - await transition( - session, - audit, - version=version, - to=ConsolidationStatus.DATA_CORRECTION, - changed_by=user_id, - comment=None, - now=now, - ) + 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( + session, + audit, + version=version, + to=ConsolidationStatus.DATA_CORRECTION, + changed_by=changed_by, + comment=comment, + now=now, + ) bound.append(version.id) return bound diff --git a/api/src/services/documents.py b/api/src/services/documents.py index 38c4c37..aae4ec0 100644 --- a/api/src/services/documents.py +++ b/api/src/services/documents.py @@ -273,7 +273,7 @@ class DocumentService: meta.reviewed_at = now await self._session.flush() 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( action=AuditAction.DOCUMENT_CONFIRM, diff --git a/api/tests/test_consolidation_documents.py b/api/tests/test_consolidation_documents.py index d3c091c..38b2765 100644 --- a/api/tests/test_consolidation_documents.py +++ b/api/tests/test_consolidation_documents.py @@ -473,7 +473,7 @@ async def test_on_document_confirmed_returns_bound_versions(seeded: AsyncSession user = await _user(seeded) 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]) @@ -481,6 +481,49 @@ async def test_on_document_confirmed_returns_bound_versions(seeded: AsyncSession 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 await session.flush() 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 )