Enforce granular capabilities across cashup
Backend (server-side enforcement, not just UI): - auth.js: read caps from JWT; hasCap() + requireCap() helpers; legacy-token fallback (full access minus settings) so existing sessions keep working until re-login - finalise: submit final, delete draft, bulk-finalise, attachments - reports: multiday report, cash summary, debtors - floats: float management + safe count - settings: settings mutations (was is_admin) - count: draft save, newbook fetch Frontend: - can(user, cap) helper; User.caps from /verify - Nav items, routes and actions (Submit Final, delete, bulk-finalise) gated on capabilities; non-finalisers see a draft-only hint Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
43c332be9b
commit
be670f724d
13 changed files with 117 additions and 52 deletions
|
|
@ -3,7 +3,7 @@ import { RefreshCw, Save, CheckCircle, Loader, Camera, FileText, X } from 'lucid
|
|||
import { api, uploadAttachment } from '../api'
|
||||
import { PageHeader, Card, Btn, StatusBadge } from '../components/Layout'
|
||||
import {
|
||||
GBP_DENOMINATIONS, fmtGBP, today,
|
||||
GBP_DENOMINATIONS, fmtGBP, today, can,
|
||||
type User, type CashUp, type Denomination, type CardMachine,
|
||||
type PaymentTotals, type ReconciliationRow, type TillPayment, type Attachment,
|
||||
type TransactionBreakdown, type TransactionItem,
|
||||
|
|
@ -35,7 +35,8 @@ interface Props { user: User }
|
|||
// flow: checking → empty (no record) | editing (draft) | locked (final)
|
||||
type PageState = 'checking' | 'empty' | 'editing' | 'locked'
|
||||
|
||||
export function DailyCashUp({ user: _user }: Props) {
|
||||
export function DailyCashUp({ user }: Props) {
|
||||
const canFinalise = can(user, 'finalise')
|
||||
const [date, setDate] = useState(today())
|
||||
const [pageState, setPageState] = useState<PageState>('checking')
|
||||
const [cashUp, setCashUp] = useState<CashUp | null>(null)
|
||||
|
|
@ -465,15 +466,21 @@ export function DailyCashUp({ user: _user }: Props) {
|
|||
|
||||
{/* Action buttons */}
|
||||
{pageState === 'editing' && (
|
||||
<div style={{ display: 'flex', gap: '0.75rem' }}>
|
||||
<div style={{ display: 'flex', gap: '0.75rem', alignItems: 'center' }}>
|
||||
<Btn onClick={() => save('draft')} disabled={saving} variant="secondary">
|
||||
<Save size={14} style={{ marginRight: '0.4rem' }} />
|
||||
{saving ? 'Saving…' : 'Save Draft'}
|
||||
</Btn>
|
||||
<Btn onClick={() => save('final')} disabled={saving}>
|
||||
<CheckCircle size={14} style={{ marginRight: '0.4rem' }} />
|
||||
{saving ? 'Submitting…' : 'Submit Final'}
|
||||
</Btn>
|
||||
{canFinalise ? (
|
||||
<Btn onClick={() => save('final')} disabled={saving}>
|
||||
<CheckCircle size={14} style={{ marginRight: '0.4rem' }} />
|
||||
{saving ? 'Submitting…' : 'Submit Final'}
|
||||
</Btn>
|
||||
) : (
|
||||
<span style={{ fontSize: '0.8rem', color: 'var(--text-mid)' }}>
|
||||
Save as draft — a manager with finalise permission will submit it.
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue