feat: re-model drawer cash as directional vouchers (Mandat Arkëtimi / Pagese)

Replace the single signed-± cash_movement with two distinct financial
documents — the direction is the event TYPE, not the sign of an amount:

  cash_in  = Mandat Arkëtimi (receipt / pay-IN,  +)  voucher AR-NNNN
  cash_out = Mandat Pagese  (disbursement / pay-OUT, −)  voucher PA-NNNN

Each carries a positive magnitude, voucher number, reason, the operator who
raised it and the admin who authorized it, and prints an Albanian slip.

Authorization changes from admin-only to operator-RAISED / admin-AUTHORIZED:
any shift:create holder raises the voucher, but POST /api/cash-voucher only
commits when authorizedBy is a real admin (shift:cash) re-entering their
password (verified server-side). Keeps the float control while letting the
operator do the booth paperwork.

Legacy cash_movement events are kept — they still verify and still fold into
the drawer (signed-±); the append-only chain is never rewritten. The drawer
fold and the Z-report window now sum all three types.

Verified against a copy of the live DB with the real signing modules:
cash_in 3000 + cash_out 5000 → drawer −2000, hash-chain verifies OK.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
2026-06-20 16:18:26 +02:00
parent a20400c2c5
commit 2835f78635
13 changed files with 335 additions and 78 deletions
+3 -2
View File
@@ -342,8 +342,9 @@ const shiftRoute = createRoute({
path: "/shift",
component: function ShiftRoute() {
const { user } = rootRoute.useRouteContext();
// "Admin" actions on the shift screen (drawer cash) need shift:cash.
return <ShiftControl isAdmin={can(user, "shift:cash")} />;
// The drawer-voucher form is operator-RAISED (shift:create); an admin still has
// to authorize each voucher with their password server-side.
return <ShiftControl canVoucher={can(user, "shift:create")} />;
},
});