Add deposit slip and report generation
- New Deposits tab with ledger: date, checks total, cash, deposit total, item count, status - Slide-in deposit panel: date, currency, coin, cash back, dynamic check entry rows, live totals - Save deposit, then generate Deposit Slip or Deposit Report PDF - Deposit slip: 3.375" x 8.5" portrait with Style A background drawn server-side, digit-column amounts, GnuMICR routing/account line rotated 90 deg, rotated deposit total and check count in left margin - Deposit report: plain Courier ledger with depositor/bank info, check grid, totals - deposits and deposit_items tables in schema; ON DELETE CASCADE for items - Routes: GET/POST/PUT/DELETE /api/deposits, POST /api/deposit-pdf - Generating a slip marks deposit as printed; date range and status filters - README updated to describe deposit slip feature
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
'use strict';
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('../db/database');
|
||||
|
||||
// Helper: fetch deposit with items
|
||||
function getDepositWithItems(id) {
|
||||
const deposit = db.prepare('SELECT * FROM deposits WHERE id = ?').get(id);
|
||||
if (!deposit) return null;
|
||||
deposit.items = db.prepare(
|
||||
'SELECT * FROM deposit_items WHERE deposit_id = ? ORDER BY sort_order ASC, id ASC'
|
||||
).all(id);
|
||||
return deposit;
|
||||
}
|
||||
|
||||
// GET /api/deposits?account_id=X
|
||||
router.get('/', (req, res) => {
|
||||
const { account_id } = req.query;
|
||||
if (!account_id) return res.status(400).json({ error: 'account_id is required.' });
|
||||
|
||||
const deposits = db.prepare(`
|
||||
SELECT d.*, COUNT(di.id) AS item_count,
|
||||
COALESCE(SUM(di.amount), 0) AS checks_total
|
||||
FROM deposits d
|
||||
LEFT JOIN deposit_items di ON di.deposit_id = d.id
|
||||
WHERE d.account_id = ?
|
||||
GROUP BY d.id
|
||||
ORDER BY d.deposit_date DESC, d.id DESC
|
||||
`).all(account_id);
|
||||
|
||||
res.json(deposits);
|
||||
});
|
||||
|
||||
// GET /api/deposits/:id
|
||||
router.get('/:id', (req, res) => {
|
||||
const deposit = getDepositWithItems(req.params.id);
|
||||
if (!deposit) return res.status(404).json({ error: 'Deposit not found.' });
|
||||
res.json(deposit);
|
||||
});
|
||||
|
||||
// POST /api/deposits
|
||||
router.post('/', (req, res) => {
|
||||
const { account_id, deposit_date, currency, coin, cash_back, items } = req.body;
|
||||
if (!account_id) return res.status(400).json({ error: 'account_id is required.' });
|
||||
if (!deposit_date) return res.status(400).json({ error: 'deposit_date is required.' });
|
||||
|
||||
const insert = db.transaction(() => {
|
||||
const result = db.prepare(`
|
||||
INSERT INTO deposits (account_id, deposit_date, currency, coin, cash_back)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
account_id,
|
||||
deposit_date,
|
||||
parseFloat(currency) || 0,
|
||||
parseFloat(coin) || 0,
|
||||
parseFloat(cash_back) || 0,
|
||||
);
|
||||
|
||||
const depositId = result.lastInsertRowid;
|
||||
|
||||
if (Array.isArray(items)) {
|
||||
const stmt = db.prepare(`
|
||||
INSERT INTO deposit_items (deposit_id, sort_order, check_no, bank_no, payee, memo, amount)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
items.forEach((item, i) => {
|
||||
stmt.run(
|
||||
depositId, i,
|
||||
item.check_no || null,
|
||||
item.bank_no || null,
|
||||
item.payee || null,
|
||||
item.memo || null,
|
||||
parseFloat(item.amount) || 0,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return depositId;
|
||||
});
|
||||
|
||||
const depositId = insert();
|
||||
res.status(201).json(getDepositWithItems(depositId));
|
||||
});
|
||||
|
||||
// PUT /api/deposits/:id
|
||||
router.put('/:id', (req, res) => {
|
||||
const existing = db.prepare('SELECT id FROM deposits WHERE id = ?').get(req.params.id);
|
||||
if (!existing) return res.status(404).json({ error: 'Deposit not found.' });
|
||||
|
||||
const { deposit_date, currency, coin, cash_back, items } = req.body;
|
||||
if (!deposit_date) return res.status(400).json({ error: 'deposit_date is required.' });
|
||||
|
||||
const update = db.transaction(() => {
|
||||
db.prepare(`
|
||||
UPDATE deposits SET deposit_date = ?, currency = ?, coin = ?, cash_back = ?
|
||||
WHERE id = ?
|
||||
`).run(
|
||||
deposit_date,
|
||||
parseFloat(currency) || 0,
|
||||
parseFloat(coin) || 0,
|
||||
parseFloat(cash_back) || 0,
|
||||
req.params.id,
|
||||
);
|
||||
|
||||
if (Array.isArray(items)) {
|
||||
db.prepare('DELETE FROM deposit_items WHERE deposit_id = ?').run(req.params.id);
|
||||
const stmt = db.prepare(`
|
||||
INSERT INTO deposit_items (deposit_id, sort_order, check_no, bank_no, payee, memo, amount)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
items.forEach((item, i) => {
|
||||
stmt.run(
|
||||
req.params.id, i,
|
||||
item.check_no || null,
|
||||
item.bank_no || null,
|
||||
item.payee || null,
|
||||
item.memo || null,
|
||||
parseFloat(item.amount) || 0,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
update();
|
||||
res.json(getDepositWithItems(req.params.id));
|
||||
});
|
||||
|
||||
// DELETE /api/deposits/:id
|
||||
router.delete('/:id', (req, res) => {
|
||||
const existing = db.prepare('SELECT id FROM deposits WHERE id = ?').get(req.params.id);
|
||||
if (!existing) return res.status(404).json({ error: 'Deposit not found.' });
|
||||
// deposit_items deleted via ON DELETE CASCADE
|
||||
db.prepare('DELETE FROM deposits WHERE id = ?').run(req.params.id);
|
||||
res.status(204).end();
|
||||
});
|
||||
|
||||
// PATCH /api/deposits/:id/mark-printed
|
||||
router.patch('/:id/mark-printed', (req, res) => {
|
||||
db.prepare('UPDATE deposits SET printed = 1 WHERE id = ?').run(req.params.id);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user