import { pool } from '../db.js' import { requireAuth, hasCap } from '../auth.js' export async function cashupRoutes(app) { app.addHook('preHandler', requireAuth) // GET /api/cashup?date=YYYY-MM-DD app.get('/api/cashup', async (req, reply) => { const { date } = req.query if (!date) return reply.status(400).send({ error: 'date required' }) const { rows } = await pool.query( 'SELECT * FROM cash_ups WHERE session_date = $1', [date] ) if (!rows.length) return reply.status(404).send({ error: 'Not found' }) const cashUp = rows[0] const [denoms, machines, recon, attachments] = await Promise.all([ pool.query('SELECT * FROM denominations WHERE cash_up_id = $1 ORDER BY denomination_value DESC', [cashUp.id]), pool.query('SELECT * FROM card_machines WHERE cash_up_id = $1', [cashUp.id]), pool.query('SELECT * FROM reconciliation WHERE cash_up_id = $1', [cashUp.id]), pool.query('SELECT * FROM cash_count_attachments WHERE cash_up_id = $1 ORDER BY uploaded_at DESC', [cashUp.id]), ]) return { cash_up: cashUp, denominations: denoms.rows, card_machines: machines.rows, reconciliation: recon.rows, attachments: attachments.rows, } }) // POST /api/cashup/save app.post('/api/cashup/save', async (req, reply) => { const { session_date, status, notes, denominations = [], card_machines = [] } = req.body if (!session_date) return reply.status(400).send({ error: 'session_date required' }) if (!['draft', 'final'].includes(status)) return reply.status(400).send({ error: 'invalid status' }) // Draft save needs 'count'; finalising needs 'finalise'. if (!hasCap(req, 'count')) return reply.status(403).send({ error: 'Missing capability: count' }) if (status === 'final' && !hasCap(req, 'finalise')) { return reply.status(403).send({ error: 'Missing capability: finalise' }) } let totalFloat = 0, totalCash = 0 for (const d of denominations) { if (d.count_type === 'float') totalFloat += parseFloat(d.total_amount || 0) else totalCash += parseFloat(d.total_amount || 0) } const existing = await pool.query('SELECT id, status FROM cash_ups WHERE session_date = $1', [session_date]) if (existing.rows.length && existing.rows[0].status === 'final') { return reply.status(409).send({ error: 'Cannot edit a finalised cash up' }) } let cashUpId if (existing.rows.length) { cashUpId = existing.rows[0].id const updateData = [ status, totalFloat, totalCash, notes || null, status === 'final' ? new Date() : null, status === 'final' ? req.user.email : null, new Date(), cashUpId, ] await pool.query( `UPDATE cash_ups SET status=$1, total_float_counted=$2, total_cash_counted=$3, notes=$4, submitted_at=$5, submitted_by=$6, updated_at=$7 WHERE id=$8`, updateData ) } else { const ins = await pool.query( `INSERT INTO cash_ups (session_date, created_by, status, total_float_counted, total_cash_counted, notes, submitted_at, submitted_by) VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING id`, [ session_date, req.user.email, status, totalFloat, totalCash, notes || null, status === 'final' ? new Date() : null, status === 'final' ? req.user.email : null, ] ) cashUpId = ins.rows[0].id } // Replace denominations await pool.query('DELETE FROM denominations WHERE cash_up_id = $1', [cashUpId]) for (const d of denominations) { await pool.query( `INSERT INTO denominations (cash_up_id, count_type, denomination_type, denomination_value, quantity, value_entered, total_amount) VALUES ($1,$2,$3,$4,$5,$6,$7)`, [cashUpId, d.count_type || 'takings', d.type, parseFloat(d.value), d.quantity ?? null, d.value_entered ?? null, parseFloat(d.total_amount)] ) } // Replace card machines await pool.query('DELETE FROM card_machines WHERE cash_up_id = $1', [cashUpId]) for (const m of card_machines) { await pool.query( `INSERT INTO card_machines (cash_up_id, machine_name, total_amount, amex_amount, visa_mc_amount) VALUES ($1,$2,$3,$4,$5)`, [cashUpId, m.name, parseFloat(m.total || 0), parseFloat(m.amex || 0), parseFloat(m.visa_mc || 0)] ) } return { message: status === 'final' ? 'Cash up submitted.' : 'Saved as draft.', cash_up_id: cashUpId } }) // DELETE /api/cashup/:id (requires cashup:finalise) app.delete('/api/cashup/:id', async (req, reply) => { if (!hasCap(req, 'finalise')) return reply.status(403).send({ error: 'Missing capability: finalise' }) const { rows } = await pool.query('SELECT status FROM cash_ups WHERE id = $1', [req.params.id]) if (!rows.length) return reply.status(404).send({ error: 'Not found' }) if (rows[0].status === 'final') return reply.status(409).send({ error: 'Cannot delete a finalised cash up' }) await pool.query('DELETE FROM cash_ups WHERE id = $1', [req.params.id]) return { message: 'Deleted' } }) // POST /api/cashup/bulk-finalize (requires cashup:finalise) app.post('/api/cashup/bulk-finalize', async (req, reply) => { if (!hasCap(req, 'finalise')) return reply.status(403).send({ error: 'Missing capability: finalise' }) const { ids } = req.body if (!Array.isArray(ids) || !ids.length) return reply.status(400).send({ error: 'ids required' }) let success = 0, failed = [] for (const id of ids) { const { rows } = await pool.query('SELECT status FROM cash_ups WHERE id = $1', [id]) if (!rows.length || rows[0].status !== 'draft') { failed.push(id); continue } await pool.query( 'UPDATE cash_ups SET status=$1, submitted_at=$2 WHERE id=$3', ['final', new Date(), id] ) success++ } return { success, failed_count: failed.length } }) // GET /api/cashup/history app.get('/api/cashup/history', async (req, reply) => { const { status, from, to, offset = 0, limit = 20 } = req.query const conditions = [] const params = [] if (status && status !== 'all') { params.push(status); conditions.push(`status = $${params.length}`) } if (from) { params.push(from); conditions.push(`session_date >= $${params.length}`) } if (to) { params.push(to); conditions.push(`session_date <= $${params.length}`) } const where = conditions.length ? 'WHERE ' + conditions.join(' AND ') : '' params.push(parseInt(limit), parseInt(offset)) const { rows } = await pool.query( `SELECT * FROM cash_ups ${where} ORDER BY session_date DESC LIMIT $${params.length - 1} OFFSET $${params.length}`, params ) const countParams = params.slice(0, params.length - 2) const { rows: countRows } = await pool.query(`SELECT COUNT(*) FROM cash_ups ${where}`, countParams) return { rows, total: parseInt(countRows[0].count) } }) }