89 lines
2.9 KiB
Python
89 lines
2.9 KiB
Python
from __future__ import annotations
|
||
|
||
from collections.abc import Sequence
|
||
from datetime import date
|
||
from decimal import Decimal
|
||
|
||
from sqlalchemy import case, func, select
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from app.db.models import NominalSource, SecurityNominalHistory
|
||
from app.repositories._upsert import insert_for
|
||
|
||
_History = SecurityNominalHistory
|
||
|
||
# На одну дату ручное значение важнее значения MOEX (D11).
|
||
_MANUAL_FIRST = case((_History.source == NominalSource.MANUAL, 0), else_=1)
|
||
|
||
|
||
class NominalHistoryRepository:
|
||
def __init__(self, session: AsyncSession) -> None:
|
||
self.session = session
|
||
|
||
async def effective_on(
|
||
self, security_id: int, on_date: date
|
||
) -> SecurityNominalHistory | None:
|
||
"""Номинал, действующий на дату: последняя запись с valid_from <= даты."""
|
||
result = await self.session.execute(
|
||
select(_History)
|
||
.where(_History.security_id == security_id, _History.valid_from <= on_date)
|
||
.order_by(_History.valid_from.desc(), _MANUAL_FIRST)
|
||
.limit(1)
|
||
)
|
||
return result.scalar_one_or_none()
|
||
|
||
async def last_moex_on(
|
||
self, security_id: int, on_date: date
|
||
) -> SecurityNominalHistory | None:
|
||
result = await self.session.execute(
|
||
select(_History)
|
||
.where(
|
||
_History.security_id == security_id,
|
||
_History.source == NominalSource.MOEX,
|
||
_History.valid_from <= on_date,
|
||
)
|
||
.order_by(_History.valid_from.desc())
|
||
.limit(1)
|
||
)
|
||
return result.scalar_one_or_none()
|
||
|
||
async def upsert(
|
||
self,
|
||
*,
|
||
security_id: int,
|
||
valid_from: date,
|
||
face_value: Decimal,
|
||
source: str,
|
||
reason: str | None,
|
||
user: str,
|
||
) -> None:
|
||
stmt = insert_for(self.session, _History.__table__).values(
|
||
security_id=security_id,
|
||
valid_from=valid_from,
|
||
face_value=face_value,
|
||
source=source,
|
||
reason=reason,
|
||
created_by=user,
|
||
updated_by=user,
|
||
)
|
||
stmt = stmt.on_conflict_do_update(
|
||
index_elements=["security_id", "valid_from", "source"],
|
||
set_={
|
||
"face_value": stmt.excluded.face_value,
|
||
"reason": stmt.excluded.reason,
|
||
"updated_at": func.now(),
|
||
"updated_by": user,
|
||
},
|
||
)
|
||
await self.session.execute(stmt)
|
||
|
||
async def list_for_security(
|
||
self, security_id: int
|
||
) -> Sequence[SecurityNominalHistory]:
|
||
result = await self.session.execute(
|
||
select(_History)
|
||
.where(_History.security_id == security_id)
|
||
.order_by(_History.valid_from.desc(), _MANUAL_FIRST)
|
||
)
|
||
return result.scalars().all()
|