Preserve ledger venue provenance (#39)
* feat: preserve ledger venue provenance * fix: satisfy ledger test lint ratchet * fix: advance image migration inventory
This commit is contained in:
@@ -27,6 +27,7 @@ from app.domain.models import CorporateAction, Portfolio, PortfolioComment
|
||||
from app.domain.pnl import calculate_current_wac, calculate_realized_pnl
|
||||
from app.domain.services import PORTFOLIO_NOT_FOUND_MESSAGE
|
||||
from app.repositories.factory import get_portfolio_repository_from_settings
|
||||
from app.repositories.portfolio_repository import LedgerTradeConflictError
|
||||
from app.schemas.corporate_actions import (
|
||||
CorporateActionRequest,
|
||||
CorporateActionResponse,
|
||||
@@ -604,7 +605,13 @@ def _require_service_token(auth: AuthContext = Depends(require_trade_write)) ->
|
||||
return auth
|
||||
|
||||
|
||||
def _payloads_match(existing, body: LedgerTradeRequest) -> bool:
|
||||
def _economic_payloads_match(existing, body: LedgerTradeRequest) -> bool:
|
||||
"""Defence in depth for non-Postgres repository implementations.
|
||||
|
||||
The Postgres repository performs this comparison under the portfolio and
|
||||
ledger row locks; this check preserves the public contract for test or
|
||||
alternate repository implementations.
|
||||
"""
|
||||
return (
|
||||
existing.side == body.side
|
||||
and abs(existing.quantity - float(body.quantity)) < 1e-9
|
||||
@@ -659,14 +666,21 @@ def record_ledger_trade_v1(
|
||||
executed_at=body.executed_at,
|
||||
fee_amount=float(body.fee_amount),
|
||||
tax_amount=float(body.tax_amount),
|
||||
execution_venue=body.execution_venue,
|
||||
order_venue=body.order_venue,
|
||||
)
|
||||
except LedgerTradeConflictError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="Duplicate key with different payload",
|
||||
) from None
|
||||
except HTTPException:
|
||||
raise
|
||||
finally:
|
||||
_close_repo(repo)
|
||||
|
||||
if not created:
|
||||
if not _payloads_match(trade, body):
|
||||
if not _economic_payloads_match(trade, body):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="Duplicate key with different payload",
|
||||
@@ -687,6 +701,8 @@ def record_ledger_trade_v1(
|
||||
fee_amount=trade.fee_amount,
|
||||
tax_amount=trade.tax_amount,
|
||||
executed_at=trade.executed_at,
|
||||
execution_venue=trade.execution_venue,
|
||||
order_venue=trade.order_venue,
|
||||
recorded_at=trade.recorded_at,
|
||||
cash_balance_after=trade.cash_balance_after,
|
||||
asset_quantity_after=trade.asset_quantity_after,
|
||||
@@ -827,6 +843,7 @@ def _ledger_entry_to_history_item(entry) -> LedgerHistoryItem | DividendLedgerHi
|
||||
total_cost_basis=event["total_cost_basis"], cash_balance=event["cash_balance"],
|
||||
profit_buffer=event["profit_buffer"], evidence_ref=event["evidence_ref"],
|
||||
actor_user_id=event.get("actor_user_id"), recorded_at=entry.recorded_at,
|
||||
execution_venue=entry.execution_venue, order_venue=entry.order_venue,
|
||||
)
|
||||
action = (entry.action or entry.side or "").upper()
|
||||
if action == "DIVIDEND":
|
||||
@@ -853,6 +870,8 @@ def _ledger_entry_to_history_item(entry) -> LedgerHistoryItem | DividendLedgerHi
|
||||
total_value_after=entry.total_value_after,
|
||||
target_ledger_entry_id=entry.target_ledger_entry_id,
|
||||
confirmation_evidence=entry.confirmation_evidence,
|
||||
execution_venue=entry.execution_venue,
|
||||
order_venue=entry.order_venue,
|
||||
)
|
||||
return LedgerHistoryItem(
|
||||
ledger_entry_id=entry.ledger_entry_id,
|
||||
@@ -875,6 +894,8 @@ def _ledger_entry_to_history_item(entry) -> LedgerHistoryItem | DividendLedgerHi
|
||||
total_value_after=entry.total_value_after,
|
||||
target_ledger_entry_id=entry.target_ledger_entry_id,
|
||||
confirmation_evidence=entry.confirmation_evidence,
|
||||
execution_venue=entry.execution_venue,
|
||||
order_venue=entry.order_venue,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user