payroll/__init__.py
Padreug 23bc54f558 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
2026-08-31 13:56:16 +02:00

49 lines
1.1 KiB
Python

import asyncio
from fastapi import APIRouter
from loguru import logger
from .crud import db
from .views import payroll_generic_router
from .views_api import payroll_api_router, payroll_employee_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)
payroll_ext.include_router(payroll_employee_router)
scheduled_tasks: list[asyncio.Task] = []
def payroll_stop():
for task in scheduled_tasks:
try:
task.cancel()
except Exception as ex: # shutdown must not raise
logger.warning(ex)
def payroll_start():
from lnbits.tasks import create_permanent_unique_task
from .tasks import scheduler_loop
scheduled_tasks.append(
create_permanent_unique_task("ext_payroll_scheduler", scheduler_loop)
)
__all__ = [
"db",
"payroll_ext",
"payroll_start",
"payroll_static_files",
"payroll_stop",
]