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>
143 lines
3.4 KiB
TypeScript
143 lines
3.4 KiB
TypeScript
export type CashupCap = 'count' | 'finalise' | 'reports' | 'floats' | 'settings'
|
|
|
|
export interface User {
|
|
email: string
|
|
name: string
|
|
is_admin: boolean
|
|
caps: CashupCap[]
|
|
}
|
|
|
|
// Admins implicitly hold every capability.
|
|
export function can(user: User, cap: CashupCap): boolean {
|
|
return user.is_admin || (user.caps?.includes(cap) ?? false)
|
|
}
|
|
|
|
export interface CashUp {
|
|
id: number
|
|
session_date: string
|
|
created_by: string
|
|
created_at: string
|
|
updated_at: string
|
|
status: 'draft' | 'final'
|
|
total_float_counted: string
|
|
total_cash_counted: string
|
|
notes: string | null
|
|
submitted_at: string | null
|
|
submitted_by: string | null
|
|
}
|
|
|
|
export interface Denomination {
|
|
id?: number
|
|
cash_up_id?: number
|
|
count_type: 'takings' | 'float'
|
|
denomination_type: 'note' | 'coin'
|
|
denomination_value: number
|
|
quantity: number | null
|
|
value_entered: number | null
|
|
total_amount: number
|
|
}
|
|
|
|
export interface CardMachine {
|
|
id?: number
|
|
cash_up_id?: number
|
|
machine_name: string
|
|
total_amount: number
|
|
amex_amount: number
|
|
visa_mc_amount: number
|
|
}
|
|
|
|
export interface ReconciliationRow {
|
|
category: string
|
|
banked_amount: number
|
|
reported_amount: number
|
|
variance?: number
|
|
}
|
|
|
|
export interface PaymentTotals {
|
|
cash: number
|
|
manual_visa_mc: number
|
|
manual_amex: number
|
|
gateway_visa_mc: number
|
|
gateway_amex: number
|
|
bacs: number
|
|
}
|
|
|
|
export interface TillPayment {
|
|
payment_type: string
|
|
quantity: number
|
|
total_value: number
|
|
}
|
|
|
|
export interface TransactionItem {
|
|
time: string
|
|
payment_type: string
|
|
details: string
|
|
amount: number
|
|
is_voided: boolean
|
|
}
|
|
|
|
export interface TransactionBreakdown {
|
|
reception_manual: Record<string, TransactionItem[]>
|
|
reception_gateway: Record<string, TransactionItem[]>
|
|
restaurant_bar: Record<string, TransactionItem[]>
|
|
}
|
|
|
|
export interface Attachment {
|
|
id: number
|
|
cash_up_id: number
|
|
file_name: string
|
|
file_path: string
|
|
file_size: number
|
|
mime_type: string
|
|
uploaded_at: string
|
|
attachment_type: 'pdq_z_report' | 'receipt_error' | 'other'
|
|
label: string | null
|
|
}
|
|
|
|
export interface FloatCount {
|
|
id: number
|
|
count_type: 'petty_cash' | 'change_tin' | 'safe_cash'
|
|
count_date: string
|
|
created_by: string
|
|
total_counted: string
|
|
total_receipts: string
|
|
target_amount: string
|
|
variance: string
|
|
notes: string | null
|
|
}
|
|
|
|
export interface FloatDenomination {
|
|
denomination_value: string
|
|
quantity: number
|
|
total_amount: string
|
|
target?: number
|
|
}
|
|
|
|
export interface FloatReceipt {
|
|
id: number
|
|
receipt_value: string
|
|
receipt_description: string
|
|
}
|
|
|
|
export const GBP_DENOMINATIONS: Array<{ value: number; label: string; type: 'note' | 'coin' }> = [
|
|
{ value: 50, label: '£50', type: 'note' },
|
|
{ value: 20, label: '£20', type: 'note' },
|
|
{ value: 10, label: '£10', type: 'note' },
|
|
{ value: 5, label: '£5', type: 'note' },
|
|
{ value: 2, label: '£2', type: 'coin' },
|
|
{ value: 1, label: '£1', type: 'coin' },
|
|
{ value: 0.50, label: '50p', type: 'coin' },
|
|
{ value: 0.20, label: '20p', type: 'coin' },
|
|
{ value: 0.10, label: '10p', type: 'coin' },
|
|
{ value: 0.05, label: '5p', type: 'coin' },
|
|
{ value: 0.02, label: '2p', type: 'coin' },
|
|
{ value: 0.01, label: '1p', type: 'coin' },
|
|
]
|
|
|
|
export function fmtGBP(n: number | string) {
|
|
return '£' + parseFloat(String(n || 0)).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
|
}
|
|
|
|
export function today() {
|
|
return new Date().toISOString().slice(0, 10)
|
|
}
|