diff --git a/public/index.html b/public/index.html
index 0e1ceca..62212b4 100644
--- a/public/index.html
+++ b/public/index.html
@@ -472,6 +472,18 @@
+
Check Position
+
+
+
+ Choose which slot(s) on the page to print checks in.
+
+
diff --git a/public/js/app.js b/public/js/app.js
index 28c5442..509ccc4 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -963,6 +963,7 @@ function openAccountSettings() {
f.elements.offset_up.value = a.offset_up || 0;
f.elements.offset_down.value = a.offset_down || 0;
document.getElementById('as-second-sig').checked = !!a.second_signature;
+ document.getElementById('as-check-position').value = a.check_position || '3-per-page';
document.getElementById('as-logo').value = '';
document.getElementById('as-logo-preview').hidden = true;
@@ -1001,6 +1002,7 @@ async function saveAccountSettings() {
offset_up: parseFloat(f.elements.offset_up.value) || 0,
offset_down: parseFloat(f.elements.offset_down.value) || 0,
second_signature: document.getElementById('as-second-sig').checked ? 1 : 0,
+ check_position: document.getElementById('as-check-position').value,
logo_data: acctSettings.logoData || null,
};
diff --git a/src/app.js b/src/app.js
index 69a5aaf..d407bef 100644
--- a/src/app.js
+++ b/src/app.js
@@ -106,7 +106,7 @@ app.put('/api/account/:id', requireAdmin, (req, res) => {
bank_name, bank_info1, bank_info2, bank_info3, transit_code,
routing_number, account_number,
offset_left, offset_right, offset_up, offset_down,
- logo_data, second_signature,
+ logo_data, second_signature, check_position,
} = req.body;
if (!company1 || !routing_number || !account_number) {
@@ -117,13 +117,16 @@ app.put('/api/account/:id', requireAdmin, (req, res) => {
return res.status(400).json({ error: 'Logo image must be smaller than 512 KB.' });
}
+ const VALID_POSITIONS = ['3-per-page', 'top', 'middle', 'bottom'];
+ const resolvedPosition = VALID_POSITIONS.includes(check_position) ? check_position : '3-per-page';
+
db.prepare(`
UPDATE account SET
company1 = ?, company2 = ?, company3 = ?, company4 = ?,
bank_name = ?, bank_info1 = ?, bank_info2 = ?, bank_info3 = ?, transit_code = ?,
routing_number = ?, account_number = ?,
offset_left = ?, offset_right = ?, offset_up = ?, offset_down = ?,
- second_signature = ?,
+ second_signature = ?, check_position = ?,
logo_data = CASE WHEN ? IS NOT NULL THEN ? ELSE logo_data END,
updated_at = datetime('now')
WHERE id = ?
@@ -133,7 +136,7 @@ app.put('/api/account/:id', requireAdmin, (req, res) => {
routing_number, account_number,
parseFloat(offset_left) || 0, parseFloat(offset_right) || 0,
parseFloat(offset_up) || 0, parseFloat(offset_down) || 0,
- second_signature ? 1 : 0,
+ second_signature ? 1 : 0, resolvedPosition,
logo_data || null, logo_data || null,
req.params.id
);
diff --git a/src/db/database.js b/src/db/database.js
index 61e544e..98bb911 100644
--- a/src/db/database.js
+++ b/src/db/database.js
@@ -160,6 +160,8 @@ db.exec(`
// Default layout fields used for seeding and migration.
const DEFAULT_LAYOUT_FIELDS = [
+ // Logo — top left corner (Graph type, rendered as image from account.logo_data)
+ { field_name: 'Logo', field_type: 'Graph', x_pos: 0.10, y_pos: 0.08, x_end_pos: 0.45, y_end_pos: 0.58, font_name: 'Helvetica', font_size: 10, font_bold: 0, field_text: null, line_thick: 1, visible: 1 },
// Company block — top left
{ field_name: 'Company Name', field_type: 'Regular', x_pos: 0.50, y_pos: 0.12, x_end_pos: 0, y_end_pos: 0, font_name: 'Helvetica-Bold', font_size: 10, font_bold: 1, field_text: null, line_thick: 1, visible: 1 },
{ field_name: 'Company Name2', field_type: 'Regular', x_pos: 0.50, y_pos: 0.30, x_end_pos: 0, y_end_pos: 0, font_name: 'Helvetica', font_size: 9, font_bold: 0, field_text: null, line_thick: 1, visible: 1 },
@@ -227,6 +229,21 @@ if (!db.prepare("SELECT value FROM settings WHERE key = 'layout_reset_v1'").get(
})();
}
+// Migration: add Logo field to existing accounts that don't have one.
+(function addLogoField() {
+ const accounts = db.prepare('SELECT id FROM account').all();
+ const insertLogo = db.prepare(`
+ INSERT OR IGNORE INTO layout_fields
+ (account_id, field_name, field_text, font_name, font_size, font_bold,
+ field_type, line_thick, x_pos, y_pos, x_end_pos, y_end_pos, visible)
+ VALUES (?, 'Logo', NULL, 'Helvetica', 10, 0, 'Graph', 1, 0.10, 0.08, 0.45, 0.58, 1)
+ `);
+ for (const { id } of accounts) {
+ const existing = db.prepare("SELECT id FROM layout_fields WHERE account_id = ? AND field_name = 'Logo'").get(id);
+ if (!existing) insertLogo.run(id);
+ }
+})();
+
// Migration: seed default layout fields for any account that has none (ongoing, idempotent).
(function seedMissingLayoutFields() {
const accounts = db.prepare('SELECT id FROM account').all();
diff --git a/src/services/pdfService.js b/src/services/pdfService.js
index 8b74d08..33ea1ef 100644
--- a/src/services/pdfService.js
+++ b/src/services/pdfService.js
@@ -131,13 +131,21 @@ function generateCheckPdf(account, checks, fields) {
const offX = (account.offset_right - account.offset_left);
const offY = (account.offset_down - account.offset_up);
- // Render checks in pages of 3; add a new page for each additional group
- const pages = Math.ceil(checks.length / 3);
+ // Determine slot assignment based on check_position setting
+ const position = account.check_position || '3-per-page';
+ const SLOT_MAP = { top: 0, middle: 1, bottom: 2 };
+ const fixedSlot = SLOT_MAP[position]; // undefined for '3-per-page'
+ const checksPerPage = fixedSlot !== undefined ? 1 : 3;
+
+ const pages = Math.ceil(checks.length / checksPerPage);
for (let page = 0; page < pages; page++) {
if (page > 0) doc.addPage();
for (let slot = 0; slot < 3; slot++) {
- const check = checks[page * 3 + slot] || null;
+ // For fixed-slot mode, only render in the designated slot
+ if (fixedSlot !== undefined && slot !== fixedSlot) continue;
+ const checkIndex = fixedSlot !== undefined ? page : page * 3 + slot;
+ const check = checks[checkIndex] || null;
const slotOriginY = slot * SLOT_HEIGHT_IN;
// Helper: convert inches (relative to slot) to PDF points (absolute page)