feat(v2): commission splits CRUD endpoints (P3c)
Adds 3 operator-scoped endpoints for managing the commission remainder
ruleset:
GET /api/v1/dca/commission-splits
— operator's default ruleset
GET /api/v1/dca/commission-splits?machine_id=X
— per-machine override (just the
override, not the default)
GET /api/v1/dca/commission-splits?machine_id=X&effective=true
— what the settlement processor
actually applies (override if
set, else operator default)
PUT /api/v1/dca/commission-splits — atomic replace; model validator
enforces legs sum to 1.0
DELETE /api/v1/dca/commission-splits — clear default (per-machine
overrides still apply)
DELETE /api/v1/dca/commission-splits?machine_id=X
— clear per-machine override
(falls back to default)
All routes verify operator owns the referenced machine (404 not 403 if
not). The DELETE path bypasses SetCommissionSplitsData's sum-to-1.0
validator by calling replace_commission_splits([]) directly, since an
empty ruleset is the correct "no rules" state — distribution.py logs a
warning and leaves operator_fee_sats in the machine wallet when this
happens.
28 routes registered total. 72/72 tests pass.
Refs: aiolabs/satmachineadmin#9
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b7f6f0a696
commit
e8dcbfe26e
1 changed files with 65 additions and 0 deletions
65
views_api.py
65
views_api.py
|
|
@ -19,12 +19,14 @@ from .crud import (
|
||||||
delete_deposit,
|
delete_deposit,
|
||||||
delete_machine,
|
delete_machine,
|
||||||
get_client_balance_summary,
|
get_client_balance_summary,
|
||||||
|
get_commission_splits,
|
||||||
get_dca_client,
|
get_dca_client,
|
||||||
get_dca_clients_for_machine,
|
get_dca_clients_for_machine,
|
||||||
get_dca_clients_for_operator,
|
get_dca_clients_for_operator,
|
||||||
get_deposit,
|
get_deposit,
|
||||||
get_deposits_for_client,
|
get_deposits_for_client,
|
||||||
get_deposits_for_operator,
|
get_deposits_for_operator,
|
||||||
|
get_effective_commission_splits,
|
||||||
get_machine,
|
get_machine,
|
||||||
get_machines_for_operator,
|
get_machines_for_operator,
|
||||||
get_payments_for_operator,
|
get_payments_for_operator,
|
||||||
|
|
@ -32,6 +34,7 @@ from .crud import (
|
||||||
get_settlements_for_machine,
|
get_settlements_for_machine,
|
||||||
get_settlements_for_operator,
|
get_settlements_for_operator,
|
||||||
get_super_config,
|
get_super_config,
|
||||||
|
replace_commission_splits,
|
||||||
update_dca_client,
|
update_dca_client,
|
||||||
update_deposit,
|
update_deposit,
|
||||||
update_deposit_status,
|
update_deposit_status,
|
||||||
|
|
@ -40,6 +43,7 @@ from .crud import (
|
||||||
)
|
)
|
||||||
from .models import (
|
from .models import (
|
||||||
ClientBalanceSummary,
|
ClientBalanceSummary,
|
||||||
|
CommissionSplit,
|
||||||
CreateDcaClientData,
|
CreateDcaClientData,
|
||||||
CreateDepositData,
|
CreateDepositData,
|
||||||
CreateMachineData,
|
CreateMachineData,
|
||||||
|
|
@ -48,6 +52,7 @@ from .models import (
|
||||||
DcaPayment,
|
DcaPayment,
|
||||||
DcaSettlement,
|
DcaSettlement,
|
||||||
Machine,
|
Machine,
|
||||||
|
SetCommissionSplitsData,
|
||||||
SuperConfig,
|
SuperConfig,
|
||||||
UpdateDcaClientData,
|
UpdateDcaClientData,
|
||||||
UpdateDepositData,
|
UpdateDepositData,
|
||||||
|
|
@ -384,6 +389,66 @@ async def api_list_payments(
|
||||||
return await get_payments_for_operator(user.id, leg_type=leg_type)
|
return await get_payments_for_operator(user.id, leg_type=leg_type)
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Commission splits — operator's rules for distributing the commission
|
||||||
|
# remainder (post-super-fee). Sum-to-1.0 invariant enforced at the model
|
||||||
|
# boundary by SetCommissionSplitsData.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
@satmachineadmin_api_router.get(
|
||||||
|
"/api/v1/dca/commission-splits", response_model=list[CommissionSplit]
|
||||||
|
)
|
||||||
|
async def api_get_commission_splits(
|
||||||
|
machine_id: str | None = None,
|
||||||
|
effective: bool = False,
|
||||||
|
user: User = Depends(check_user_exists),
|
||||||
|
) -> list[CommissionSplit]:
|
||||||
|
"""No machine_id: operator's default ruleset (rows where machine_id IS NULL).
|
||||||
|
With machine_id: per-machine override only (404 the machine if not yours).
|
||||||
|
With machine_id and ?effective=true: per-machine override if set, else
|
||||||
|
operator default — what the settlement processor actually applies."""
|
||||||
|
if machine_id is not None:
|
||||||
|
await _machine_owned_by(machine_id, user.id)
|
||||||
|
if effective:
|
||||||
|
return await get_effective_commission_splits(user.id, machine_id)
|
||||||
|
return await get_commission_splits(user.id, machine_id)
|
||||||
|
return await get_commission_splits(user.id, None)
|
||||||
|
|
||||||
|
|
||||||
|
@satmachineadmin_api_router.put(
|
||||||
|
"/api/v1/dca/commission-splits", response_model=list[CommissionSplit]
|
||||||
|
)
|
||||||
|
async def api_replace_commission_splits(
|
||||||
|
data: SetCommissionSplitsData,
|
||||||
|
user: User = Depends(check_user_exists),
|
||||||
|
) -> list[CommissionSplit]:
|
||||||
|
"""Atomic replace for the (operator, machine) scope. If
|
||||||
|
data.machine_id is None, replaces the operator's default ruleset;
|
||||||
|
otherwise replaces the per-machine override (machine must be owned).
|
||||||
|
Sum-to-1.0 invariant enforced upstream by the Pydantic validator."""
|
||||||
|
if data.machine_id is not None:
|
||||||
|
await _machine_owned_by(data.machine_id, user.id)
|
||||||
|
return await replace_commission_splits(user.id, data.machine_id, data.legs)
|
||||||
|
|
||||||
|
|
||||||
|
@satmachineadmin_api_router.delete(
|
||||||
|
"/api/v1/dca/commission-splits",
|
||||||
|
status_code=HTTPStatus.NO_CONTENT,
|
||||||
|
)
|
||||||
|
async def api_delete_commission_splits(
|
||||||
|
machine_id: str | None = None,
|
||||||
|
user: User = Depends(check_user_exists),
|
||||||
|
) -> None:
|
||||||
|
"""Clear a ruleset. With machine_id: clears the per-machine override
|
||||||
|
(machine falls back to operator default). Without: clears the operator
|
||||||
|
default (any per-machine overrides keep applying)."""
|
||||||
|
if machine_id is not None:
|
||||||
|
await _machine_owned_by(machine_id, user.id)
|
||||||
|
# Atomic replace with an empty leg list — same effect as DELETE WHERE.
|
||||||
|
await replace_commission_splits(user.id, machine_id, [])
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Super config — operators read; super (LNbits instance admin) writes.
|
# Super config — operators read; super (LNbits instance admin) writes.
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue