feat(ui): super-user payroll console
The page the whole extension exists to be driven from: pick a user, pick
which of their wallets to pay into, set amount, currency, frequency, start
date and how many payments — plus the ledger, filters and a CSV button.
Notes for review:
- The employee-wallet picker only offers wallets belonging to the selected
employee. The API rejects anything else, so this is about not presenting
the mistake rather than about enforcement.
- The dialog draws a live calendar from whatever is currently typed. A
wrong start date or frequency is cheapest to catch before saving, which
is what the preview endpoint was for.
- "Next payday" comes from the schedule endpoint per row rather than being
computed in JS. A payday this page derived for itself could disagree with
the one the scheduler will actually use, and month-end is exactly where
that would happen.
- The destructive actions say what they do: resume warns that missed
paydays are written off, delete says the schedule position goes with the
row and points at cancel instead, pay-now says the period is consumed
even if its payday has not arrived.
- A refused pay-now surfaces the ledger row's reason with a longer toast —
triggering a payout by hand is precisely when you want to know why it
did not land.
Quasar UMD rules honoured: no self-closing tags anywhere in the template,
`${ }` delimiters so Jinja never sees a moustache, and `:style` bindings
instead of a <style> block, since LNbits themes override typography
utilities with !important.
The page route is gated on super_user as well, via check_user_exists plus
an explicit flag check — the template needs the full User for its wallet
picker, which check_super_user does not return. It is a UX nicety; the API
behind it is gated independently and does not trust this route.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018jy52j9GRZ6XKa1Zt21LLj
This commit is contained in:
parent
4b6d647d23
commit
23bc54f558
4 changed files with 901 additions and 0 deletions
35
views.py
Normal file
35
views.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
"""Frontend route — serves the operator console.
|
||||
|
||||
Gated the same way the API is: super user only. `check_user_exists` is used
|
||||
rather than `check_super_user` because the template needs the full `User`
|
||||
(its wallets populate the source-wallet picker) and `check_super_user`
|
||||
resolves to an `Account`; the privilege check is then made explicitly off
|
||||
`user.super_user`. Rejecting here is a UX nicety — the API behind the page
|
||||
is gated independently and does not trust this route.
|
||||
"""
|
||||
|
||||
from http import HTTPStatus
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from lnbits.core.models import User
|
||||
from lnbits.decorators import check_user_exists
|
||||
from lnbits.helpers import template_renderer
|
||||
|
||||
payroll_generic_router = APIRouter()
|
||||
|
||||
|
||||
def payroll_renderer():
|
||||
return template_renderer(["payroll/templates"])
|
||||
|
||||
|
||||
@payroll_generic_router.get("/", response_class=HTMLResponse)
|
||||
async def index(request: Request, user: User = Depends(check_user_exists)):
|
||||
if not user.super_user:
|
||||
raise HTTPException(
|
||||
HTTPStatus.FORBIDDEN,
|
||||
"Payroll is restricted to the instance super user.",
|
||||
)
|
||||
return payroll_renderer().TemplateResponse(
|
||||
"payroll/index.html", {"request": request, "user": user.json()}
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue