автоматическое проставление версии правил при создании консолидация, если таких создается версия консолидации создается с NULL
This commit is contained in:
parent
22facb3ddf
commit
232fecec39
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from collections.abc import Collection
|
from collections.abc import Collection
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
from sqlalchemy import func, select
|
from sqlalchemy import func, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
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())
|
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:
|
def is_valid_for_version(rule: EliminationRule | None, version_no: int | None) -> bool:
|
||||||
"""Проверить версию правила §4 либо отсутствие `valid_to`."""
|
"""Проверить версию правила §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)
|
return rule is not None and (rule.version_no == version_no if version_no is not None else rule.valid_to is None)
|
||||||
|
|||||||
@ -16,7 +16,7 @@ from collections.abc import Sequence
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from src.api.dependencies.audit import RequestAudit
|
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.common.exceptions import ConflictError, DomainValidationError, NotFoundError
|
||||||
from src.domain import checklist as checklist_domain
|
from src.domain import checklist as checklist_domain
|
||||||
from src.domain.consolidation_periods import quarters_through, reporting_date
|
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 consolidations as repo
|
||||||
from src.repositories import directories as directories_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.repositories.users import UserWithAccess
|
||||||
from src.schemas.consolidations import (
|
from src.schemas.consolidations import (
|
||||||
ChecklistCellOut,
|
ChecklistCellOut,
|
||||||
@ -347,6 +348,9 @@ class ConsolidationService:
|
|||||||
scenario=ConsolidationScenario.CREATE,
|
scenario=ConsolidationScenario.CREATE,
|
||||||
approach=ConsolidationApproach.BASIC,
|
approach=ConsolidationApproach.BASIC,
|
||||||
goodwill_enabled=False,
|
goodwill_enabled=False,
|
||||||
|
elimination_rules_version=await elimination_rules_repo.latest_effective_version_no(
|
||||||
|
self._session, moscow_date(now)
|
||||||
|
),
|
||||||
initiator_id=user.id,
|
initiator_id=user.id,
|
||||||
initiator_office_id=user.regional_office_id,
|
initiator_office_id=user.regional_office_id,
|
||||||
is_in_work=True,
|
is_in_work=True,
|
||||||
|
|||||||
@ -4,13 +4,16 @@
|
|||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
|
from datetime import timedelta
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from sqlalchemy import func, select
|
from sqlalchemy import func, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from src.common.clock import moscow_date, utc_now
|
||||||
from src.models.access import AppUser
|
from src.models.access import AppUser
|
||||||
|
from src.models.accounting import EliminationRule
|
||||||
from src.models.audit_ex import AuditEvent
|
from src.models.audit_ex import AuditEvent
|
||||||
from src.models.consolidation import (
|
from src.models.consolidation import (
|
||||||
Consolidation,
|
Consolidation,
|
||||||
@ -139,6 +142,7 @@ async def test_create_consolidation_rb_q3_2024(auth_client: AuthClientFactory, s
|
|||||||
assert str(consolidation.id) == card["consolidation_id"]
|
assert str(consolidation.id) == card["consolidation_id"]
|
||||||
version = (await seeded.execute(select(ConsolidationVersion))).scalar_one()
|
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.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, ConsolidationLock) == 0
|
||||||
assert await _count(seeded, ConsolidationMember) == len(entities)
|
assert await _count(seeded, ConsolidationMember) == len(entities)
|
||||||
assert await _count(seeded, ConsolidationStatusHistory) == 1
|
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
|
@pytest.mark.integration
|
||||||
async def test_create_reporting_date_for_q4_is_next_year(auth_client: AuthClientFactory, seeded: AsyncSession) -> None:
|
async def test_create_reporting_date_for_q4_is_next_year(auth_client: AuthClientFactory, seeded: AsyncSession) -> None:
|
||||||
group, gsk, _ = await _scenario(seeded)
|
group, gsk, _ = await _scenario(seeded)
|
||||||
|
|||||||
@ -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`), первую запись
|
`is_in_work = true`), `consolidation_member` периметра (`is_head_entity = false`), первую запись
|
||||||
`consolidation_status_history` (`from_status` NULL → `INITIATED`), подхват подтверждённых документов ЮЛ периметра (см.
|
`consolidation_status_history` (`from_status` NULL → `INITIATED`), подхват подтверждённых документов ЮЛ периметра (см.
|
||||||
`documents-api` «Подхват документов») и одно событие `audit_event(CONSOLIDATION_CREATE)`, у которого
|
`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` — ровно эти ЮЛ после
|
`legal_entity_ids` (поле отсутствует или `null`) — текущие члены ГСК; с `legal_entity_ids` — ровно эти ЮЛ после
|
||||||
схлопывания дублей (порядок первого вхождения), каждое SHALL принадлежать ГК консолидации
|
схлопывания дублей (порядок первого вхождения), каждое SHALL принадлежать ГК консолидации
|
||||||
(`legal_entity.company_group_id`), иначе 422 `ENTITY_NOT_IN_GROUP` с перечнем `legal_entity_ids` чужих в порядке
|
(`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`, история с одной записью,
|
- **THEN** 201; версия №1 `INITIATED`, `reporting_date = 2024-10-01`, два `consolidation_member`, история с одной записью,
|
||||||
`audit_event(CONSOLIDATION_CREATE)` с `consolidation_version_id`; активной блокировки нет
|
`audit_event(CONSOLIDATION_CREATE)` с `consolidation_version_id`; активной блокировки нет
|
||||||
|
|
||||||
|
#### Scenario: Правила действуют на дату создания версии
|
||||||
|
- **WHEN** в день создания консолидации за 2024 год действует версия правил 2, а версия 1 действовала в отчётном периоде
|
||||||
|
- **THEN** `consolidation_version.elimination_rules_version = 2`; номер не меняется при последующей публикации правил
|
||||||
|
|
||||||
#### Scenario: Выбранный периметр шире ГСК
|
#### Scenario: Выбранный периметр шире ГСК
|
||||||
- **WHEN** в ГК три ЮЛ, в ГСК два из них, создаётся консолидация с `legal_entity_ids` из всех трёх ЮЛ ГК
|
- **WHEN** в ГК три ЮЛ, в ГСК два из них, создаётся консолидация с `legal_entity_ids` из всех трёх ЮЛ ГК
|
||||||
- **THEN** 201; в `members` три ЮЛ; `after_value.members` события `CONSOLIDATION_CREATE` — три ЮЛ; состав ГСК не изменился
|
- **THEN** 201; в `members` три ЮЛ; `after_value.members` события `CONSOLIDATION_CREATE` — три ЮЛ; состав ГСК не изменился
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user