feat(deposits): pre-fill 30 slots and add back-page overflow for 31-60 checks

- Deposit panel now pre-fills all 30 check slots on open (new and existing)
- Remove button maintains 30-slot minimum by appending a blank row
- Add Row button hidden at <30, visible at 30-59, disabled at 60
- Deposit slip PDF splits items: first 30 on front, up to 30 more on back
- Front page gains a FROM REVERSE row carrying back-page subtotal when needed
- Back page renders with same column positions/width as front, titled
  'ADDITIONAL CHECK LISTING', numbered rows 31-60, and
  'Forward to other side / TOTAL $' footer

Closes #10, closes #11
This commit is contained in:
2026-04-28 09:04:44 -06:00
parent 657de9e61a
commit 189ae53d34
2 changed files with 140 additions and 3 deletions
+125 -2
View File
@@ -238,7 +238,17 @@ function generateDepositSlip(account, deposit, items) {
const depositTotal = subTotal - (deposit.cash_back || 0);
const checkCount = items.length;
const totalRows = SL.firstCheckRow + SL.maxChecks;
// Split items: first 30 on front, up to 30 more on back
const frontItems = items.slice(0, SL.maxChecks);
const backItems = items.slice(SL.maxChecks, SL.maxChecks * 2);
const hasBackPage = backItems.some(it => (it.amount || 0) > 0 || it.check_no || it.payee);
const backTotal = hasBackPage ? backItems.reduce((s, i) => s + (i.amount || 0), 0) : 0;
// When back page exists, add one extra row on front for "FROM REVERSE"
const fromReverseRow = hasBackPage ? SL.firstCheckRow + SL.maxChecks : null;
const totalRows = fromReverseRow != null
? SL.firstCheckRow + SL.maxChecks + 1
: SL.firstCheckRow + SL.maxChecks;
const totalRowY_ = rowTopY(totalRows);
const gridBottom = totalRowY_ + SL.rowH;
@@ -375,7 +385,7 @@ function generateDepositSlip(account, deposit, items) {
drawAmountRow(deposit.currency || 0, SL.currencyRow);
drawAmountRow(deposit.coin || 0, SL.coinRow);
items.slice(0, SL.maxChecks).forEach((item, i) => {
frontItems.forEach((item, i) => {
const r = SL.firstCheckRow + i;
const y = (rowY(r) - 0.015) * PT;
if (item.check_no) {
@@ -387,6 +397,13 @@ function generateDepositSlip(account, deposit, items) {
drawAmountRow(item.amount || 0, r);
});
// "FROM REVERSE" row carries back-page subtotal onto the front
if (fromReverseRow != null) {
doc.font('Courier').fontSize(6).fillColor(SL.bgLabelColor)
.text('FROM REVERSE', SL.cX * PT, rowY(fromReverseRow) * PT - 4, { lineBreak: false });
drawAmountRow(backTotal, fromReverseRow);
}
drawAmountRow(depositTotal, totalRows);
// ── Rotated left strip elements ─────────────────────────────────────────
@@ -440,10 +457,116 @@ function generateDepositSlip(account, deposit, items) {
doc.restore(); // end slip position translate
if (hasBackPage) {
doc.addPage();
renderDepositBackPage(doc, backItems, backTotal);
}
doc.end();
});
}
// ── Back page renderer ────────────────────────────────────────────────────────
function renderDepositBackPage(doc, backItems, backTotal) {
// Same slip position and width as front (slipX=0, W=3.375").
// No left strip elements; grid starts near the top.
const BK = {
gridTop: 0.48,
checksRow: 0,
firstRow: 1,
maxChecks: SL.maxChecks, // 30
};
const totalRows = BK.firstRow + BK.maxChecks; // "TOTAL $" row index
const bkRowTopY = r => BK.gridTop + r * SL.rowH;
const bkRowY = r => BK.gridTop + r * SL.rowH + SL.rowH * 0.7;
const gridTopPt = bkRowTopY(0) * PT;
const gridBotPt = (bkRowTopY(totalRows) + SL.rowH) * PT;
doc.save();
doc.translate(SL.slipX * PT, 0);
// ── Title ─────────────────────────────────────────────────────────────────
doc.font('Helvetica-Bold').fontSize(9).fillColor(SL.bgHeaderColor)
.text('A D D I T I O N A L C H E C K L I S T I N G',
SL.cX * PT, 0.10 * PT,
{ width: (SL.W - SL.cX - 0.05) * PT, align: 'center', lineBreak: false });
// ── Grid verticals (same column positions as front) ───────────────────────
const dollarsRightX = SL.colCentsR - SL.colCentsW - SL.colDollarSep;
const dividerX = (dollarsRightX - 7 * SL.digitW) * PT;
const dollarsCentsX = dollarsRightX * PT;
doc.moveTo(dividerX, gridTopPt).lineTo(dividerX, gridBotPt).lineWidth(0.5).stroke(SL.bgLineColor);
doc.moveTo(dollarsCentsX, gridTopPt).lineTo(dollarsCentsX, gridBotPt).lineWidth(0.5).stroke(SL.bgLineColor);
doc.moveTo(SL.colCentsR * PT, gridTopPt).lineTo(SL.colCentsR * PT, gridBotPt).lineWidth(0.5).stroke(SL.bgLineColor);
// Column headers
doc.font('Helvetica').fontSize(6).fillColor(SL.bgLabelColor);
const hdrY = (BK.gridTop - 0.10) * PT;
doc.text('DOLLARS', dollarsCentsX - 7 * SL.digitW * PT, hdrY,
{ width: 7 * SL.digitW * PT, align: 'center', lineBreak: false });
doc.text('CENTS', (SL.colCentsR - SL.colCentsW) * PT, hdrY,
{ width: SL.colCentsW * PT, align: 'center', lineBreak: false });
// "CHECKS:" header label
doc.font('Courier').fontSize(7).fillColor(SL.bgLabelColor)
.text('CHECKS:', SL.cX * PT, bkRowY(BK.checksRow) * PT - 5, { lineBreak: false });
// ── Horizontal grid lines ─────────────────────────────────────────────────
for (let r = 0; r <= totalRows + 1; r++) {
const y = bkRowTopY(r) * PT;
const isOuter = r === 0 || r === totalRows + 1;
doc.moveTo(SL.stripX * PT, y).lineTo(SL.colCentsR * PT, y)
.lineWidth(isOuter ? 0.75 : 0.3).stroke(SL.bgLineColor);
}
// ── Row numbers (continuing from front: 3160) ────────────────────────────
doc.font('Courier').fontSize(6).fillColor(SL.bgLabelColor);
for (let i = 0; i < BK.maxChecks; i++) {
const r = BK.firstRow + i;
doc.text(String(SL.maxChecks + i + 1), SL.cX * PT, bkRowY(r) * PT - 4,
{ width: 14, align: 'right', lineBreak: false });
}
// ── "TOTAL $" footer label ────────────────────────────────────────────────
doc.font('Courier-Bold').fontSize(7).fillColor('#000000')
.text('T O T A L $', SL.cX * PT, bkRowY(totalRows) * PT - 5, { lineBreak: false });
// ── "Forward to other side" in left strip (rotated) ───────────────────────
const fwdY = bkRowTopY(totalRows) + SL.rowH * 0.5;
doc.save();
doc.translate(SL.stripCenterX * PT, fwdY * PT);
doc.rotate(90);
doc.font('Helvetica').fontSize(6).fillColor(SL.bgLabelColor)
.text('Forward to other side', 0, 0, { lineBreak: false });
doc.restore();
// ── Amount data ───────────────────────────────────────────────────────────
backItems.forEach((item, i) => {
const r = BK.firstRow + i;
const y = (bkRowY(r) - 0.015) * PT;
if (item.check_no) {
doc.font('Courier').fontSize(7).fillColor('#000000')
.text(String(item.check_no).slice(0, 8),
(SL.cX + 0.16) * PT, y,
{ width: SL.checkNoW * PT, lineBreak: false });
}
if ((item.amount || 0) > 0) {
doc.font('Courier').fontSize(8).fillColor('#000000');
drawDigitAmount(doc, item.amount, dollarsRightX, y);
}
});
// Back page total
doc.font('Courier').fontSize(8).fillColor('#000000');
drawDigitAmount(doc, backTotal, dollarsRightX, (bkRowY(totalRows) - 0.015) * PT);
doc.restore();
}
// ── Amount rendering helpers ──────────────────────────────────────────────────
/**