fix: reset all accounts to default layout; fix slot 0 top-field clipping
- One-time migration (layout_reset_v1) deletes all layout_fields for every account and re-seeds with the default layout, ensuring .mdb-imported accounts get the same clean check style as wizard-created ones. - Remove the -0.25" slot 0 y-offset that was pushing Company Name and Check Number above the top of the page on the first check. - Consolidate layout field seed logic into database.js (seedLayoutFields), removing the duplicate in app.js.
This commit is contained in:
+2
-59
@@ -10,6 +10,7 @@ const multer = require('multer');
|
|||||||
const session = require('express-session');
|
const session = require('express-session');
|
||||||
|
|
||||||
const db = require('./db/database');
|
const db = require('./db/database');
|
||||||
|
const { seedLayoutFields } = require('./db/database');
|
||||||
const { requireAuth, requireAdmin, canAccessAccount } = require('./middleware/auth');
|
const { requireAuth, requireAdmin, canAccessAccount } = require('./middleware/auth');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
@@ -191,64 +192,6 @@ app.delete('/api/account/:id', requireAdmin, (req, res) => {
|
|||||||
res.status(204).end();
|
res.status(204).end();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Default layout fields for manually-created accounts (no .mdb import).
|
|
||||||
// Coordinates are in inches from the top-left of each check slot (8.5" × 3.5").
|
|
||||||
// Field names for type 'Regular' must match the keys in pdfService.resolveFieldValue.
|
|
||||||
function seedDefaultLayoutFields(accountId) {
|
|
||||||
const fields = [
|
|
||||||
// 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 },
|
|
||||||
{ field_name: 'Company Name3', field_type: 'Regular', x_pos: 0.50, y_pos: 0.44, 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 },
|
|
||||||
{ field_name: 'Company Name4', field_type: 'Regular', x_pos: 0.50, y_pos: 0.58, 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 },
|
|
||||||
// Check number — top right
|
|
||||||
{ field_name: 'Check Number', field_type: 'Regular', x_pos: 7.20, 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 },
|
|
||||||
// Date — upper right
|
|
||||||
{ field_name: 'Date Label', field_type: 'Text', x_pos: 5.80, y_pos: 0.40, x_end_pos: 0, y_end_pos: 0, font_name: 'Helvetica', font_size: 8, font_bold: 0, field_text: 'DATE', line_thick: 1, visible: 1 },
|
|
||||||
{ field_name: 'Date', field_type: 'Regular', x_pos: 6.30, y_pos: 0.40, 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 },
|
|
||||||
// Pay to the order of
|
|
||||||
{ field_name: 'Pay To Label', field_type: 'Text', x_pos: 0.30, y_pos: 0.82, x_end_pos: 0, y_end_pos: 0, font_name: 'Helvetica', font_size: 7, font_bold: 0, field_text: 'PAY TO THE ORDER OF', line_thick: 1, visible: 1 },
|
|
||||||
{ field_name: 'Payee Name', field_type: 'Regular', x_pos: 2.15, y_pos: 0.80, x_end_pos: 0, y_end_pos: 0, font_name: 'Helvetica', font_size: 10, font_bold: 0, field_text: null, line_thick: 1, visible: 1 },
|
|
||||||
// Amount box
|
|
||||||
{ field_name: 'Dollar Sign', field_type: 'Text', x_pos: 6.80, y_pos: 0.80, x_end_pos: 0, y_end_pos: 0, font_name: 'Helvetica', font_size: 10, font_bold: 0, field_text: '$', line_thick: 1, visible: 1 },
|
|
||||||
{ field_name: 'Amount', field_type: 'Regular', x_pos: 6.95, y_pos: 0.80, 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 },
|
|
||||||
// Written amount
|
|
||||||
{ field_name: 'Text Amount', field_type: 'Regular', x_pos: 0.30, y_pos: 1.28, 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 },
|
|
||||||
{ field_name: 'Dollars Label', field_type: 'Text', x_pos: 6.30, y_pos: 1.28, x_end_pos: 0, y_end_pos: 0, font_name: 'Helvetica', font_size: 8, font_bold: 0, field_text: 'DOLLARS', line_thick: 1, visible: 1 },
|
|
||||||
// Bank info block
|
|
||||||
{ field_name: 'Bank Information', field_type: 'Regular', x_pos: 0.30, y_pos: 1.82, x_end_pos: 0, y_end_pos: 0, font_name: 'Helvetica', font_size: 8, font_bold: 0, field_text: null, line_thick: 1, visible: 1 },
|
|
||||||
{ field_name: 'Bank Transit Code', field_type: 'Regular', x_pos: 0.30, y_pos: 2.38, x_end_pos: 0, y_end_pos: 0, font_name: 'Helvetica', font_size: 7, font_bold: 0, field_text: null, line_thick: 1, visible: 1 },
|
|
||||||
// Payee address — center window area (for windowed envelopes)
|
|
||||||
{ field_name: 'Payee Address', field_type: 'Regular', x_pos: 3.50, y_pos: 1.82, 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 },
|
|
||||||
// Memo
|
|
||||||
{ field_name: 'Memo Label', field_type: 'Text', x_pos: 0.30, y_pos: 2.82, x_end_pos: 0, y_end_pos: 0, font_name: 'Helvetica', font_size: 7, font_bold: 0, field_text: 'MEMO', line_thick: 1, visible: 1 },
|
|
||||||
{ field_name: 'Memo', field_type: 'Regular', x_pos: 0.72, y_pos: 2.82, 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 },
|
|
||||||
// Auth signature label
|
|
||||||
{ field_name: 'Auth Signature Label', field_type: 'Text', x_pos: 5.00, y_pos: 3.14, x_end_pos: 0, y_end_pos: 0, font_name: 'Helvetica', font_size: 6, font_bold: 0, field_text: 'AUTHORIZED SIGNATURE', line_thick: 1, visible: 1 },
|
|
||||||
// Lines
|
|
||||||
{ field_name: 'Payee Line', field_type: 'Line', x_pos: 2.10, y_pos: 1.00, x_end_pos: 6.70, y_end_pos: 1.00, font_name: 'Helvetica', font_size: 10, font_bold: 0, field_text: null, line_thick: 1, visible: 1 },
|
|
||||||
{ field_name: 'Amount Box Top', field_type: 'Line', x_pos: 6.75, y_pos: 0.70, x_end_pos: 8.30, y_end_pos: 0.70, font_name: 'Helvetica', font_size: 10, font_bold: 0, field_text: null, line_thick: 1, visible: 1 },
|
|
||||||
{ field_name: 'Amount Box Left', field_type: 'Line', x_pos: 6.75, y_pos: 0.70, x_end_pos: 6.75, y_end_pos: 1.05, font_name: 'Helvetica', font_size: 10, font_bold: 0, field_text: null, line_thick: 1, visible: 1 },
|
|
||||||
{ field_name: 'Amount Box Bottom',field_type: 'Line', x_pos: 6.75, y_pos: 1.05, x_end_pos: 8.30, y_end_pos: 1.05, font_name: 'Helvetica', font_size: 10, font_bold: 0, field_text: null, line_thick: 1, visible: 1 },
|
|
||||||
{ field_name: 'Text Amount Line', field_type: 'Line', x_pos: 0.30, y_pos: 1.48, x_end_pos: 6.30, y_end_pos: 1.48, font_name: 'Helvetica', font_size: 10, font_bold: 0, field_text: null, line_thick: 1, visible: 1 },
|
|
||||||
{ field_name: 'Memo Line', field_type: 'Line', x_pos: 0.68, y_pos: 3.00, x_end_pos: 4.00, y_end_pos: 3.00, font_name: 'Helvetica', font_size: 10, font_bold: 0, field_text: null, line_thick: 1, visible: 1 },
|
|
||||||
{ field_name: 'Signature Line', field_type: 'Line', x_pos: 5.00, y_pos: 3.10, x_end_pos: 8.20, y_end_pos: 3.10, font_name: 'Helvetica', font_size: 10, font_bold: 0, field_text: null, line_thick: 1, visible: 1 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const stmt = 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
`);
|
|
||||||
db.transaction(() => {
|
|
||||||
for (const f of fields) {
|
|
||||||
stmt.run(accountId, f.field_name, f.field_text, f.font_name, f.font_size, f.font_bold,
|
|
||||||
f.field_type, f.line_thick, f.x_pos, f.y_pos, f.x_end_pos, f.y_end_pos, f.visible);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST /api/account/setup (admin only — creates a new checking account)
|
// POST /api/account/setup (admin only — creates a new checking account)
|
||||||
app.post('/api/account/setup', requireAdmin, (req, res) => {
|
app.post('/api/account/setup', requireAdmin, (req, res) => {
|
||||||
const {
|
const {
|
||||||
@@ -291,7 +234,7 @@ app.post('/api/account/setup', requireAdmin, (req, res) => {
|
|||||||
logo_data: logo_data || null,
|
logo_data: logo_data || null,
|
||||||
});
|
});
|
||||||
|
|
||||||
seedDefaultLayoutFields(result.lastInsertRowid);
|
seedLayoutFields(result.lastInsertRowid);
|
||||||
|
|
||||||
res.status(201).json({ success: true, accountId: result.lastInsertRowid });
|
res.status(201).json({ success: true, accountId: result.lastInsertRowid });
|
||||||
});
|
});
|
||||||
|
|||||||
+32
-19
@@ -147,19 +147,8 @@ db.exec(`
|
|||||||
)
|
)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// Migration: seed default layout fields for any account that has none.
|
// Default layout fields used for seeding and migration.
|
||||||
// Runs at every startup but INSERT OR IGNORE makes it idempotent.
|
const DEFAULT_LAYOUT_FIELDS = [
|
||||||
(function seedMissingLayoutFields() {
|
|
||||||
const accounts = db.prepare('SELECT id FROM account').all();
|
|
||||||
const countStmt = db.prepare('SELECT COUNT(*) AS n FROM layout_fields WHERE account_id = ?');
|
|
||||||
const insertStmt = 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
`);
|
|
||||||
|
|
||||||
const defaultFields = [
|
|
||||||
// Company block — top left
|
// 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 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 },
|
{ 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 },
|
||||||
@@ -199,18 +188,42 @@ db.exec(`
|
|||||||
{ field_name: 'Signature Line', field_type: 'Line', x_pos: 5.00, y_pos: 3.10, x_end_pos: 8.20, y_end_pos: 3.10, font_name: 'Helvetica', font_size: 10, font_bold: 0, field_text: null, line_thick: 1, visible: 1 },
|
{ field_name: 'Signature Line', field_type: 'Line', x_pos: 5.00, y_pos: 3.10, x_end_pos: 8.20, y_end_pos: 3.10, font_name: 'Helvetica', font_size: 10, font_bold: 0, field_text: null, line_thick: 1, visible: 1 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const seedAccount = db.transaction(accountId => {
|
function seedLayoutFields(accountId) {
|
||||||
for (const f of defaultFields) {
|
const insert = db.prepare(`
|
||||||
insertStmt.run(accountId, f.field_name, f.field_text, f.font_name, f.font_size, f.font_bold,
|
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
`);
|
||||||
|
db.transaction(() => {
|
||||||
|
for (const f of DEFAULT_LAYOUT_FIELDS) {
|
||||||
|
insert.run(accountId, f.field_name, f.field_text, f.font_name, f.font_size, f.font_bold,
|
||||||
f.field_type, f.line_thick, f.x_pos, f.y_pos, f.x_end_pos, f.y_end_pos, f.visible);
|
f.field_type, f.line_thick, f.x_pos, f.y_pos, f.x_end_pos, f.y_end_pos, f.visible);
|
||||||
}
|
}
|
||||||
});
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migration: reset all accounts to default layout (runs once, gated by settings key).
|
||||||
|
// Replaces any .mdb-imported or legacy layout_fields with the clean default layout.
|
||||||
|
if (!db.prepare("SELECT value FROM settings WHERE key = 'layout_reset_v1'").get()) {
|
||||||
|
const accounts = db.prepare('SELECT id FROM account').all();
|
||||||
|
db.transaction(() => {
|
||||||
for (const { id } of accounts) {
|
for (const { id } of accounts) {
|
||||||
if (countStmt.get(id).n === 0) {
|
db.prepare('DELETE FROM layout_fields WHERE account_id = ?').run(id);
|
||||||
seedAccount(id);
|
seedLayoutFields(id);
|
||||||
}
|
}
|
||||||
|
db.prepare("INSERT OR REPLACE INTO settings (key, value) VALUES ('layout_reset_v1', '1')").run();
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migration: seed default layout fields for any account that has none (ongoing, idempotent).
|
||||||
|
(function seedMissingLayoutFields() {
|
||||||
|
const accounts = db.prepare('SELECT id FROM account').all();
|
||||||
|
for (const { id } of accounts) {
|
||||||
|
const { n } = db.prepare('SELECT COUNT(*) AS n FROM layout_fields WHERE account_id = ?').get(id);
|
||||||
|
if (n === 0) seedLayoutFields(id);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
module.exports = db;
|
module.exports = db;
|
||||||
|
module.exports.seedLayoutFields = seedLayoutFields;
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ function generateCheckPdf(account, checks, fields) {
|
|||||||
|
|
||||||
for (let slot = 0; slot < 3; slot++) {
|
for (let slot = 0; slot < 3; slot++) {
|
||||||
const check = checks[page * 3 + slot] || null;
|
const check = checks[page * 3 + slot] || null;
|
||||||
const slotOriginY = slot * SLOT_HEIGHT_IN + (slot === 0 ? -0.25 : 0);
|
const slotOriginY = slot * SLOT_HEIGHT_IN;
|
||||||
|
|
||||||
// Helper: convert inches (relative to slot) to PDF points (absolute page)
|
// Helper: convert inches (relative to slot) to PDF points (absolute page)
|
||||||
const pt = (xIn, yIn) => ({
|
const pt = (xIn, yIn) => ({
|
||||||
|
|||||||
Reference in New Issue
Block a user