feat(api): settle a cash_owed / partial_pending settlement off-machine

POST /api/v1/dca/settlements/{id}/settle-cash-owed: the operator paid
the customer by hand. Appends the provenance note, distributes at the
full amount (the customer is whole), and publishes a settle_transaction
op so the machine flips its own dispense_error/partial row to
remediated. Until now the only way to close an owed row was to dispense
more cash from the machine that had just jammed.
This commit is contained in:
Padreug 2026-10-10 22:26:45 +02:00
commit 7375112638
2 changed files with 88 additions and 0 deletions

View file

@ -359,6 +359,31 @@ async def apply_partial_dispense_and_redistribute(
return after if after is not None else updated
async def settle_cash_owed_settlement(
settlement: DcaSettlement, note: str, author_user_id: str
) -> DcaSettlement:
"""ADR-005 §6 — operator paid the customer by hand. The sale is whole, so
the settlement distributes at the full amount it landed with. The note is
the provenance; `remediated` on the machine side comes from the
settle_transaction op the endpoint publishes."""
from .crud import append_settlement_note
if settlement.status not in ("cash_owed", "partial_pending"):
raise ValueError(f"settlement {settlement.id} is {settlement.status!r}")
await append_settlement_note(
settlement.id, f"Settled off-machine (full amount): {note}", author_user_id
)
await mark_settlement_status(settlement.id, "pending", None)
logger.info(
f"distribution: settlement {settlement.id} settled off-machine by "
f"{author_user_id[:8]}… — distributing in full"
)
await process_settlement(settlement.id)
after = await get_settlement(settlement.id)
assert after is not None
return after
async def process_settlement(settlement_id: str) -> None:
"""Process a pending settlement end-to-end.