feat: support manual dividend ledger entries

Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-09-09 02:04:02 +00:00
committed by GitHub
co-authored by genichin
parent 9d2c2aea5f
commit ccde81b7a0
6 changed files with 281 additions and 12 deletions
+35 -2
View File
@@ -687,7 +687,7 @@ def record_ledger_trade_v1(
def _manual_entry_to_response(entry) -> ManualLedgerResponse:
action = (entry.action or entry.side or "").upper()
amount = entry.price if action in {"INVEST", "BUFFER_OUT", "BUFFER_IN", "WITHDRAW", "PROFIT"} else None
amount = entry.price if action in {"INVEST", "BUFFER_OUT", "BUFFER_IN", "WITHDRAW", "PROFIT", "DIVIDEND"} else None
return ManualLedgerResponse(
ledger_entry_id=entry.ledger_entry_id,
portfolio_id=entry.portfolio_id,
@@ -697,6 +697,7 @@ def _manual_entry_to_response(entry) -> ManualLedgerResponse:
price=entry.price,
fee=entry.fee_amount,
tax=entry.tax_amount,
executed_at=entry.executed_at,
note=entry.note,
recorded_at=entry.recorded_at,
cash_balance_after=entry.cash_balance_after,
@@ -710,6 +711,8 @@ def _manual_entry_to_response(entry) -> ManualLedgerResponse:
def _manual_error_code(message: str) -> str:
if "POTENTIAL_DUPLICATE_DIVIDEND" in message:
return "POTENTIAL_DUPLICATE_DIVIDEND"
if "LEDGER_ENTRY_NOT_FOUND" in message or "원장 항목을 찾을 수 없습니다" in message:
return "LEDGER_ENTRY_NOT_FOUND"
if "INSUFFICIENT_CASH" in message or "현금" in message:
@@ -723,6 +726,16 @@ def _manual_error_code(message: str) -> str:
return "MANUAL_LEDGER_CONFLICT"
def _extract_duplicate_dividend_entry_id(message: str) -> int | None:
prefix = "POTENTIAL_DUPLICATE_DIVIDEND:"
if not message.startswith(prefix):
return None
try:
return int(message.removeprefix(prefix))
except ValueError:
return None
def _encode_ledger_cursor(*, portfolio_id: str, source: str | None, recorded_at: datetime, ledger_entry_id: int) -> str:
payload = {
"portfolio_id": portfolio_id,
@@ -817,10 +830,21 @@ def record_manual_ledger_v1(
price=float(body.price) if body.price is not None else None,
fee=float(body.fee),
tax=float(body.tax),
executed_at=(
datetime(
body.executed_at.year,
body.executed_at.month,
body.executed_at.day,
tzinfo=timezone.utc,
)
if body.executed_at is not None
else None
),
target_ledger_entry_id=body.target_ledger_entry_id,
note=body.note,
actor_user_id=auth.user_id,
confirmation_evidence=evidence,
confirm_duplicate=body.confirm_duplicate,
)
except HTTPException:
raise
@@ -829,9 +853,18 @@ def record_manual_ledger_v1(
code = _manual_error_code(message)
if code == "LEDGER_ENTRY_NOT_FOUND":
status_code = status.HTTP_404_NOT_FOUND
detail: str | dict[str, object] = {"code": code, "message": message}
elif code == "POTENTIAL_DUPLICATE_DIVIDEND":
status_code = status.HTTP_409_CONFLICT
detail = {
"code": code,
"message": "Potential duplicate dividend entry detected",
"matched_ledger_entry_id": _extract_duplicate_dividend_entry_id(message),
}
else:
status_code = status.HTTP_409_CONFLICT if code != "MANUAL_LEDGER_CONFLICT" else status.HTTP_422_UNPROCESSABLE_ENTITY
raise HTTPException(status_code=status_code, detail={"code": code, "message": message}) from exc
detail = {"code": code, "message": message}
raise HTTPException(status_code=status_code, detail=detail) from exc
finally:
_close_repo(repo)
return _manual_entry_to_response(entry)