refactor(services): общий utc_now вместо локальных _now в сервисах справочников
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
This commit is contained in:
parent
69d641c14e
commit
e3ea074e4d
8
api/src/common/clock.py
Normal file
8
api/src/common/clock.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
"""Текущее время сервисов: одно место для «сейчас» (UTC, с часовым поясом) — `valid_from`/`valid_to`, `updated_at`."""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def utc_now() -> datetime.datetime:
|
||||||
|
"""Текущий момент в UTC (aware)."""
|
||||||
|
return datetime.datetime.now(datetime.UTC)
|
||||||
@ -4,12 +4,12 @@
|
|||||||
Аудит `LEGAL_ENTITY_CHANGE` / `object_type = legal_entity_alias` пишется в той же транзакции запроса.
|
Аудит `LEGAL_ENTITY_CHANGE` / `object_type = legal_entity_alias` пишется в той же транзакции запроса.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
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.exceptions import ConflictError, DomainValidationError, NotFoundError
|
from src.common.exceptions import ConflictError, DomainValidationError, NotFoundError
|
||||||
from src.domain.aliases import normalize_alias
|
from src.domain.aliases import normalize_alias
|
||||||
from src.domain.policy import VisibilityScope
|
from src.domain.policy import VisibilityScope
|
||||||
@ -50,7 +50,7 @@ class AliasService:
|
|||||||
alias=data.alias,
|
alias=data.alias,
|
||||||
normalized_alias=normalized,
|
normalized_alias=normalized,
|
||||||
created_by=self._audit.user_id,
|
created_by=self._audit.user_id,
|
||||||
created_at=datetime.datetime.now(datetime.UTC),
|
created_at=utc_now(),
|
||||||
)
|
)
|
||||||
self._session.add(alias)
|
self._session.add(alias)
|
||||||
await self._session.flush()
|
await self._session.flush()
|
||||||
|
|||||||
@ -4,13 +4,13 @@
|
|||||||
`commit` делает `SessionDep`; `audit_event` пишется в той же транзакции через `RequestAudit`.
|
`commit` делает `SessionDep`; `audit_event` пишется в той же транзакции через `RequestAudit`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
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.exceptions import ConflictError, DomainValidationError, NotFoundError
|
from src.common.exceptions import ConflictError, DomainValidationError, NotFoundError
|
||||||
from src.domain.policy import VisibilityScope, office_for_create
|
from src.domain.policy import VisibilityScope, office_for_create
|
||||||
from src.models.directories import COMPANY_GROUP_CODE_SEQ, CompanyGroup, CompanyGroupHistory
|
from src.models.directories import COMPANY_GROUP_CODE_SEQ, CompanyGroup, CompanyGroupHistory
|
||||||
@ -25,10 +25,6 @@ OBJECT_TYPE = "company_group"
|
|||||||
TRACKED_FIELDS = ("name", "crm_id", "base_regional_office_id") # поля ГК, изменяемые PATCH и попадающие в историю
|
TRACKED_FIELDS = ("name", "crm_id", "base_regional_office_id") # поля ГК, изменяемые PATCH и попадающие в историю
|
||||||
|
|
||||||
|
|
||||||
def _now() -> datetime.datetime:
|
|
||||||
return datetime.datetime.now(datetime.UTC)
|
|
||||||
|
|
||||||
|
|
||||||
class CompanyGroupService:
|
class CompanyGroupService:
|
||||||
"""Операции над ГК от имени текущего пользователя в его области видимости."""
|
"""Операции над ГК от имени текущего пользователя в его области видимости."""
|
||||||
|
|
||||||
@ -43,7 +39,7 @@ class CompanyGroupService:
|
|||||||
await self._check_office_exists(office_id)
|
await self._check_office_exists(office_id)
|
||||||
if data.crm_id is not None:
|
if data.crm_id is not None:
|
||||||
await self._check_crm_id_free(data.crm_id)
|
await self._check_crm_id_free(data.crm_id)
|
||||||
now = _now()
|
now = utc_now()
|
||||||
group = CompanyGroup(
|
group = CompanyGroup(
|
||||||
code=await repo.next_code(self._session, COMPANY_GROUP_CODE_SEQ, COMPANY_GROUP_CODE_PREFIX),
|
code=await repo.next_code(self._session, COMPANY_GROUP_CODE_SEQ, COMPANY_GROUP_CODE_PREFIX),
|
||||||
crm_id=data.crm_id,
|
crm_id=data.crm_id,
|
||||||
@ -77,7 +73,7 @@ class CompanyGroupService:
|
|||||||
if not changes:
|
if not changes:
|
||||||
return group
|
return group
|
||||||
before = {field: getattr(group, field) for field in changes}
|
before = {field: getattr(group, field) for field in changes}
|
||||||
now = _now()
|
now = utc_now()
|
||||||
self._session.add(
|
self._session.add(
|
||||||
CompanyGroupHistory(
|
CompanyGroupHistory(
|
||||||
company_group_id=group.id,
|
company_group_id=group.id,
|
||||||
@ -112,7 +108,7 @@ class CompanyGroupService:
|
|||||||
if group.is_archived == archived:
|
if group.is_archived == archived:
|
||||||
return group
|
return group
|
||||||
group.is_archived = archived
|
group.is_archived = archived
|
||||||
group.updated_at = _now()
|
group.updated_at = utc_now()
|
||||||
group.updated_by = self._audit.user_id
|
group.updated_by = self._audit.user_id
|
||||||
await self._session.flush()
|
await self._session.flush()
|
||||||
await self._audit.record(
|
await self._audit.record(
|
||||||
|
|||||||
@ -13,6 +13,7 @@ from typing import Any
|
|||||||
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.exceptions import ConflictError, DomainValidationError, NotFoundError
|
from src.common.exceptions import ConflictError, DomainValidationError, NotFoundError
|
||||||
from src.domain.inn import validate_inn
|
from src.domain.inn import validate_inn
|
||||||
from src.domain.policy import VisibilityScope, office_for_create
|
from src.domain.policy import VisibilityScope, office_for_create
|
||||||
@ -38,13 +39,9 @@ MEMBER_OBJECT_TYPE = "related_group_member"
|
|||||||
TRACKED_FIELDS = ("name", "entity_type", "crm_id", "regional_office_id") # поля ЮЛ, изменяемые PATCH
|
TRACKED_FIELDS = ("name", "entity_type", "crm_id", "regional_office_id") # поля ЮЛ, изменяемые PATCH
|
||||||
|
|
||||||
|
|
||||||
def _now() -> datetime.datetime:
|
|
||||||
return datetime.datetime.now(datetime.UTC)
|
|
||||||
|
|
||||||
|
|
||||||
def _today() -> datetime.date:
|
def _today() -> datetime.date:
|
||||||
"""«Сегодня» для проверки `effective_date` — дата UTC (часовой пояс пользователя в Срезе 1 не известен)."""
|
"""«Сегодня» для проверки `effective_date` — дата UTC (часовой пояс пользователя в Срезе 1 не известен)."""
|
||||||
return _now().date()
|
return utc_now().date()
|
||||||
|
|
||||||
|
|
||||||
class LegalEntityService:
|
class LegalEntityService:
|
||||||
@ -69,7 +66,7 @@ class LegalEntityService:
|
|||||||
# Филиал не может завести ЮЛ (в т.ч. монозаёмщика) на чужой филиал; без филиала — свой (§10.1)
|
# Филиал не может завести ЮЛ (в т.ч. монозаёмщика) на чужой филиал; без филиала — свой (§10.1)
|
||||||
office_id = office_for_create(self._scope, requested_office)
|
office_id = office_for_create(self._scope, requested_office)
|
||||||
await self._check_office_exists(office_id)
|
await self._check_office_exists(office_id)
|
||||||
now = _now()
|
now = utc_now()
|
||||||
entity = LegalEntity(
|
entity = LegalEntity(
|
||||||
inn=inn,
|
inn=inn,
|
||||||
crm_id=data.crm_id,
|
crm_id=data.crm_id,
|
||||||
@ -107,7 +104,7 @@ class LegalEntityService:
|
|||||||
if not changes:
|
if not changes:
|
||||||
return entity
|
return entity
|
||||||
before = {field: getattr(entity, field) for field in changes}
|
before = {field: getattr(entity, field) for field in changes}
|
||||||
now = _now()
|
now = utc_now()
|
||||||
self._snapshot(entity, LeHistoryEvent.UPDATE, now, effective_date=None)
|
self._snapshot(entity, LeHistoryEvent.UPDATE, now, effective_date=None)
|
||||||
for field, value in changes.items():
|
for field, value in changes.items():
|
||||||
setattr(entity, field, value)
|
setattr(entity, field, value)
|
||||||
@ -133,7 +130,7 @@ class LegalEntityService:
|
|||||||
if entity.company_group_id == target.id:
|
if entity.company_group_id == target.id:
|
||||||
raise ConflictError("SAME_COMPANY_GROUP", "ЮЛ уже входит в эту ГК")
|
raise ConflictError("SAME_COMPANY_GROUP", "ЮЛ уже входит в эту ГК")
|
||||||
before = {"company_group_id": entity.company_group_id}
|
before = {"company_group_id": entity.company_group_id}
|
||||||
now = _now()
|
now = utc_now()
|
||||||
self._snapshot(entity, LeHistoryEvent.MOVE_TO_GROUP, now, effective_date=data.effective_date)
|
self._snapshot(entity, LeHistoryEvent.MOVE_TO_GROUP, now, effective_date=data.effective_date)
|
||||||
entity.company_group_id = target.id
|
entity.company_group_id = target.id
|
||||||
await self._touch(entity, now)
|
await self._touch(entity, now)
|
||||||
@ -164,7 +161,7 @@ class LegalEntityService:
|
|||||||
changes["close_date"] = data.effective_date
|
changes["close_date"] = data.effective_date
|
||||||
changes = {field: value for field, value in changes.items() if getattr(entity, field) != value}
|
changes = {field: value for field, value in changes.items() if getattr(entity, field) != value}
|
||||||
before = {field: getattr(entity, field) for field in changes}
|
before = {field: getattr(entity, field) for field in changes}
|
||||||
now = _now()
|
now = utc_now()
|
||||||
self._snapshot(entity, event, now, effective_date=data.effective_date)
|
self._snapshot(entity, event, now, effective_date=data.effective_date)
|
||||||
for field, value in changes.items():
|
for field, value in changes.items():
|
||||||
setattr(entity, field, value)
|
setattr(entity, field, value)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user