feat: super-user REST API for payroll contracts

Contract CRUD plus the account directory the operator picks an employee
from. The extension loads and is fully drivable over HTTP after this
commit; the console UI lands separately.

Auth: the gate is `check_super_user`, applied at the *router* level so a
new endpoint cannot be added ungated by forgetting a decorator. Payroll
reads accounts the caller does not own and moves money between their
wallets, so a wallet-scoped `require_admin_key` — which any user holds for
their own wallets — would be the wrong gate here despite the similar name.

Validation lives in one place (`_validate_terms`) because the employee and
the destination wallet arrive from a form as two independent ids and
nothing further down the write path re-checks that they belong together.
That check is what stops a payout being aimed at a third party's wallet.

Edits are deliberately partial: recipient, source wallet and start date are
not patchable. They are the contract's identity — changing them would
retroactively alter what already-made payouts were for, and re-anchoring
the start date would silently move every remaining payday.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018jy52j9GRZ6XKa1Zt21LLj
This commit is contained in:
Padreug 2026-08-31 13:40:18 +02:00
commit dc29173ada
3 changed files with 257 additions and 0 deletions

23
__init__.py Normal file
View file

@ -0,0 +1,23 @@
from fastapi import APIRouter
from .crud import db
from .views import payroll_generic_router
from .views_api import payroll_api_router
payroll_static_files = [
{
"path": "/payroll/static",
"name": "payroll_static",
}
]
payroll_ext: APIRouter = APIRouter(prefix="/payroll", tags=["payroll"])
payroll_ext.include_router(payroll_generic_router)
payroll_ext.include_router(payroll_api_router)
__all__ = [
"db",
"payroll_ext",
"payroll_static_files",
]