diff --git a/views_api.py b/views_api.py index ccab37a..3fa901f 100644 --- a/views_api.py +++ b/views_api.py @@ -19,12 +19,14 @@ from .crud import ( delete_deposit, delete_machine, get_client_balance_summary, + get_commission_splits, get_dca_client, get_dca_clients_for_machine, get_dca_clients_for_operator, get_deposit, get_deposits_for_client, get_deposits_for_operator, + get_effective_commission_splits, get_machine, get_machines_for_operator, get_payments_for_operator, @@ -32,6 +34,7 @@ from .crud import ( get_settlements_for_machine, get_settlements_for_operator, get_super_config, + replace_commission_splits, update_dca_client, update_deposit, update_deposit_status, @@ -40,6 +43,7 @@ from .crud import ( ) from .models import ( ClientBalanceSummary, + CommissionSplit, CreateDcaClientData, CreateDepositData, CreateMachineData, @@ -48,6 +52,7 @@ from .models import ( DcaPayment, DcaSettlement, Machine, + SetCommissionSplitsData, SuperConfig, UpdateDcaClientData, UpdateDepositData, @@ -384,6 +389,66 @@ async def api_list_payments( 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. # =============================================================================