автоматическое проставление версии правил при создании консолидация, если таких создается версия консолидации создается с NULL

This commit is contained in:
PotapovaA 2026-10-23 12:10:35 +03:00
parent 22facb3ddf
commit 232fecec39
4 changed files with 69 additions and 2 deletions

View File

@ -2,6 +2,7 @@
import uuid
from collections.abc import Collection
from datetime import date
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
@ -44,6 +45,16 @@ async def current_open_version_no(session: AsyncSession) -> int | None:
return await session.scalar(_current_open_version_stmt())
async def latest_effective_version_no(session: AsyncSession, as_of: date) -> int | None:
"""Максимальный номер версии правил, действующей на дату создания"""
return await session.scalar(
select(func.max(EliminationRule.version_no)).where(
EliminationRule.valid_from <= as_of,
(EliminationRule.valid_to.is_(None) | (EliminationRule.valid_to >= as_of)),
)
)
def is_valid_for_version(rule: EliminationRule | None, version_no: int | None) -> bool:
"""Проверить версию правила §4 либо отсутствие `valid_to`."""
return rule is not None and (rule.version_no == version_no if version_no is not None else rule.valid_to is None)

View File

@ -16,7 +16,7 @@ from collections.abc import Sequence
from sqlalchemy.ext.asyncio import AsyncSession
from src.api.dependencies.audit import RequestAudit
from src.common.clock import utc_now
from src.common.clock import moscow_date, utc_now
from src.common.exceptions import ConflictError, DomainValidationError, NotFoundError
from src.domain import checklist as checklist_domain
from src.domain.consolidation_periods import quarters_through, reporting_date
@ -40,6 +40,7 @@ from src.models.enums import (
)
from src.repositories import consolidations as repo
from src.repositories import directories as directories_repo
from src.repositories import elimination_rules as elimination_rules_repo
from src.repositories.users import UserWithAccess
from src.schemas.consolidations import (
ChecklistCellOut,
@ -347,6 +348,9 @@ class ConsolidationService:
scenario=ConsolidationScenario.CREATE,
approach=ConsolidationApproach.BASIC,
goodwill_enabled=False,
elimination_rules_version=await elimination_rules_repo.latest_effective_version_no(
self._session, moscow_date(now)
),
initiator_id=user.id,
initiator_office_id=user.regional_office_id,
is_in_work=True,

View File

@ -4,13 +4,16 @@
import uuid
from collections.abc import Sequence
from datetime import timedelta
from typing import Any
import pytest
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from src.common.clock import moscow_date, utc_now
from src.models.access import AppUser
from src.models.accounting import EliminationRule
from src.models.audit_ex import AuditEvent
from src.models.consolidation import (
Consolidation,
@ -139,6 +142,7 @@ async def test_create_consolidation_rb_q3_2024(auth_client: AuthClientFactory, s
assert str(consolidation.id) == card["consolidation_id"]
version = (await seeded.execute(select(ConsolidationVersion))).scalar_one()
assert (version.initiator_id, version.created_by, version.is_in_work) == (user.id, user.id, True)
assert version.elimination_rules_version == 1
assert await _count(seeded, ConsolidationLock) == 0
assert await _count(seeded, ConsolidationMember) == len(entities)
assert await _count(seeded, ConsolidationStatusHistory) == 1
@ -162,6 +166,44 @@ async def test_create_consolidation_rb_q3_2024(auth_client: AuthClientFactory, s
}
@pytest.mark.integration
async def test_create_fixes_rules_for_creation_date_not_reporting_period(
auth_client: AuthClientFactory, seeded: AsyncSession
) -> None:
group, gsk, _ = await _scenario(seeded)
today = moscow_date(utc_now())
source = (await seeded.scalars(select(EliminationRule).where(EliminationRule.rule_no == 1))).one()
next_rule = EliminationRule(
statement_type=source.statement_type,
rule_no=source.rule_no,
article_1_line=source.article_1_line,
article_2_line=source.article_2_line,
side_1=source.side_1,
measure=source.measure,
elimination_mode=source.elimination_mode,
excise_adjust=source.excise_adjust,
version_no=2,
valid_from=today + timedelta(days=1),
)
seeded.add(next_rule)
await seeded.flush()
client = await auth_client(KA_RF)
first = await client.post(CONSOLIDATIONS, json=_body(group, gsk, year=2024))
assert first.status_code == HTTP_CREATED, first.text
first_version = await seeded.get(ConsolidationVersion, uuid.UUID(first.json()["id"]))
assert first_version is not None
assert first_version.elimination_rules_version == 1
next_rule.valid_from = today
second = await client.post(CONSOLIDATIONS, json=_body(group, gsk, year=2023))
assert second.status_code == HTTP_CREATED, second.text
second_version = await seeded.get(ConsolidationVersion, uuid.UUID(second.json()["id"]))
assert second_version is not None
assert second_version.elimination_rules_version == next_rule.version_no
assert first_version.elimination_rules_version == 1
@pytest.mark.integration
async def test_create_reporting_date_for_q4_is_next_year(auth_client: AuthClientFactory, seeded: AsyncSession) -> None:
group, gsk, _ = await _scenario(seeded)

View File

@ -15,7 +15,13 @@ Q3 → 01.10.year, Q4 → 01.01.year+1; ASSUMPTION(OQ-40)), `consolidation_versi
`is_in_work = true`), `consolidation_member` периметра (`is_head_entity = false`), первую запись
`consolidation_status_history` (`from_status` NULL → `INITIATED`), подхват подтверждённых документов ЮЛ периметра (см.
`documents-api` «Подхват документов») и одно событие `audit_event(CONSOLIDATION_CREATE)`, у которого
`after_value.members` — отсортированный список ЮЛ периметра; ответ 201 с карточкой версии. Периметр SHALL быть: без
`after_value.members` — отсортированный список ЮЛ периметра; ответ 201 с карточкой версии.
При создании версии система SHALL автоматически записать в `consolidation_version.elimination_rules_version` номер
самой актуальной версии правил: максимальный `version_no` среди правил, действующих на московскую календарную дату
создания версии (`valid_from <= дата` и `valid_to IS NULL` либо `valid_to >= дата`; OQ-174, docs PR #146).
Пользователь не выбирает версию правил. Отчётные год, квартал и `reporting_date` не влияют на определение номера.
Если на дату создания нет действующих правил, поле остаётся NULL.
Периметр SHALL быть: без
`legal_entity_ids` (поле отсутствует или `null`) — текущие члены ГСК; с `legal_entity_ids` — ровно эти ЮЛ после
схлопывания дублей (порядок первого вхождения), каждое SHALL принадлежать ГК консолидации
(`legal_entity.company_group_id`), иначе 422 `ENTITY_NOT_IN_GROUP` с перечнем `legal_entity_ids` чужих в порядке
@ -33,6 +39,10 @@ SHALL оставаться NULL (фиксируется при верифика
- **THEN** 201; версия №1 `INITIATED`, `reporting_date = 2024-10-01`, два `consolidation_member`, история с одной записью,
`audit_event(CONSOLIDATION_CREATE)` с `consolidation_version_id`; активной блокировки нет
#### Scenario: Правила действуют на дату создания версии
- **WHEN** в день создания консолидации за 2024 год действует версия правил 2, а версия 1 действовала в отчётном периоде
- **THEN** `consolidation_version.elimination_rules_version = 2`; номер не меняется при последующей публикации правил
#### Scenario: Выбранный периметр шире ГСК
- **WHEN** в ГК три ЮЛ, в ГСК два из них, создаётся консолидация с `legal_entity_ids` из всех трёх ЮЛ ГК
- **THEN** 201; в `members` три ЮЛ; `after_value.members` события `CONSOLIDATION_CREATE` — три ЮЛ; состав ГСК не изменился