from __future__ import annotations from datetime import date, time from decimal import Decimal from sqlalchemy import ( BigInteger, Date, ForeignKey, Index, Integer, String, Text, Time, UniqueConstraint, text, ) from sqlalchemy.orm import Mapped, mapped_column from app.core.config import settings from app.db.base import Base from app.db.types import Amount, AuditMixin, IdMixin, Price, Quantity, qualified class Operation(IdMixin, AuditMixin, Base): """Сделка/событие по бумаге в портфеле. BUY/SELL — только из выгрузки QUIK (import_batch_id + source_row — откуда строка). CPN, NOM, UWFEE, ADJ — ручной ввод (D21), import_batch_id пуст. Лотов нет: каждая покупка — отдельная операция BUY (D20). """ __tablename__ = "operation" __table_args__ = ( # Номер сделки QUIK уникален в портфеле; у ручных операций (купон, # погашение номинала) его нет — поэтому индекс частичный. Index( "ux_operation_portfolio_trade_number", "portfolio_id", "trade_number", unique=True, postgresql_where=text("trade_number IS NOT NULL"), sqlite_where=text("trade_number IS NOT NULL"), ), # Строка файла попадает в операции не больше одного раза (D19). UniqueConstraint( "import_batch_id", "source_row", name="uq_operation_batch_row" ), Index( "ix_operation_portfolio_security_date", "portfolio_id", "security_id", "trade_date", ), {"schema": settings.DATABASE_SCHEMA} if settings.DATABASE_SCHEMA else {}, ) security_id: Mapped[int] = mapped_column( BigInteger, ForeignKey(qualified("security.id")), nullable=False ) portfolio_id: Mapped[int] = mapped_column( BigInteger, ForeignKey(qualified("portfolio.id")), nullable=False ) operation_type_id: Mapped[int] = mapped_column( BigInteger, ForeignKey(qualified("operation_type.id")), nullable=False ) # Валюта расчётов: итоги по разным валютам не смешиваем. currency_id: Mapped[int] = mapped_column( BigInteger, ForeignKey(qualified("currency.id"), name="fk_operation_currency"), nullable=False, ) trade_date: Mapped[date] = mapped_column(Date, nullable=False) trade_time: Mapped[time | None] = mapped_column(Time) settlement_date: Mapped[date | None] = mapped_column(Date) account: Mapped[str | None] = mapped_column(String(50)) quantity: Mapped[Decimal | None] = mapped_column(Quantity) price: Mapped[Decimal | None] = mapped_column(Price) amount: Mapped[Decimal | None] = mapped_column(Amount) # TODO(BIQ-38115): какая сумма «с НКД» — Q17. Для строк QUIK пока пусто, # «Расчетная сумма» хранится как есть в settlement_amount. amount_with_accrued_interest: Mapped[Decimal | None] = mapped_column(Amount) settlement_amount: Mapped[Decimal | None] = mapped_column(Amount) comment: Mapped[str | None] = mapped_column(Text) settlement_code: Mapped[str | None] = mapped_column(String(20)) order_number: Mapped[str | None] = mapped_column(String(50)) trade_number: Mapped[str | None] = mapped_column(String(50)) counterparty: Mapped[str | None] = mapped_column(String(255)) commission_ts: Mapped[Decimal | None] = mapped_column(Amount) commission_clearing: Mapped[Decimal | None] = mapped_column(Amount) commission_exchange: Mapped[Decimal | None] = mapped_column(Amount) commission_tc: Mapped[Decimal | None] = mapped_column(Amount) commission_broker: Mapped[Decimal | None] = mapped_column(Amount) import_batch_id: Mapped[int | None] = mapped_column( BigInteger, ForeignKey(qualified("import_batch.id"), name="fk_operation_import_batch"), ) # Порядковый номер строки в файле (QuikTrade.ordinal). source_row: Mapped[int | None] = mapped_column(Integer)