mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
Accounting: A/P-clearing payments, check return address + MICR gaps, dashboard fixes
Bill/vendor payments: - Bill "Pay" and bank-register vendor debits now clear Accounts Payable (Dr A/P / Cr Bank) instead of re-debiting the expense, which had double-counted expenses in the P&L and never cleared A/P. The expense account is kept as a display-only category on the payment. Checks: - Bill-payment checks now print the return address (payer name/address) from the company check layout (was hardcoded blank). - Per-segment MICR gap control (check# / routing / account) in both check generators, wired to Check Setup (bill payments) and the Check Layout editor (Print Checks). New columns: check_settings.micr_gap_1/2 and check_layouts/company_check_layouts.micr_gap_1/2. Accounting Dashboard / Financial Overview: - Date-range selector (presets + custom) drives the charts, top expenses, and recent transactions. - PDF title renamed to "Financial Overview" and shows the period. - Fixed amounts rendering as "$ 5 0 0. 0 0": the U+2212 minus sign forced jsPDF into a UTF-16 fallback; replaced with an ASCII hyphen (also in the report PDF out-of-balance line). DB migrations applied to the project: repost_gl_on_line_item_change, budget_actuals_accrual_owner_income, add_micr_gap_controls. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -51,6 +51,10 @@ export type CheckPrintOptions = {
|
||||
offsetY?: number;
|
||||
/** Extra vertical shift for the MICR line only (fine-tune for check stock) */
|
||||
micrOffsetY?: number;
|
||||
/** Spaces between MICR check# and routing segments (default 1) */
|
||||
micrGap1?: number;
|
||||
/** Spaces between MICR routing and account segments (default 1) */
|
||||
micrGap2?: number;
|
||||
};
|
||||
|
||||
// ── Constants ────────────────────────────────────────────────────────────────
|
||||
@@ -71,12 +75,15 @@ const CHECK_H = SECTION_H; // check occupies the full top section
|
||||
// Format matches reference check: C000000305C A263191387A 1100034740184C
|
||||
// C{check#}C A{routing}A {account}C
|
||||
|
||||
function buildMicr(routing: string, account: string, checkNum: number): string {
|
||||
function buildMicr(routing: string, account: string, checkNum: number, gap1 = 1, gap2 = 1): string {
|
||||
const r = (routing ?? "").replace(/\D/g, "").slice(0, 9);
|
||||
const a = (account ?? "").replace(/\D/g, "");
|
||||
const c = String(checkNum).padStart(9, "0");
|
||||
if (!r && !a) return "";
|
||||
return `C${c}C A${r}A ${a}C`;
|
||||
const g1 = " ".repeat(Math.max(0, Math.round(gap1)));
|
||||
const g2 = " ".repeat(Math.max(0, Math.round(gap2)));
|
||||
// C{check#}C {gap1} A{routing}A {gap2} {account}C
|
||||
return `C${c}C${g1}A${r}A${g2}${a}C`;
|
||||
}
|
||||
|
||||
// ── Fill a line to width with a pad character (for written-amount security) ─
|
||||
@@ -103,7 +110,7 @@ function hline(doc: jsPDF, x1: number, y: number, x2: number, w = 0.006, gray =
|
||||
|
||||
// ── Check face ───────────────────────────────────────────────────────────────
|
||||
|
||||
function drawCheck(doc: jsPDF, originY: number, c: CheckData, ox = 0, micrOy = 0) {
|
||||
function drawCheck(doc: jsPDF, originY: number, c: CheckData, ox = 0, micrOy = 0, micrGap1 = 1, micrGap2 = 1) {
|
||||
const y = (dy: number) => originY + dy;
|
||||
const x = (dx: number) => dx + ox; // Apply global X offset to every horizontal coordinate
|
||||
|
||||
@@ -270,7 +277,9 @@ function drawCheck(doc: jsPDF, originY: number, c: CheckData, ox = 0, micrOy = 0
|
||||
const micr = buildMicr(
|
||||
(c.routingNumber ?? "").replace(/\D/g, ""),
|
||||
(c.accountNumber ?? "").replace(/\D/g, ""),
|
||||
c.checkNumber
|
||||
c.checkNumber,
|
||||
micrGap1,
|
||||
micrGap2
|
||||
);
|
||||
|
||||
if (micr) {
|
||||
@@ -351,19 +360,21 @@ export function generateCheckPDF(checks: CheckData[], opts: CheckPrintOptions):
|
||||
const ox = opts.offsetX ?? 0;
|
||||
const oy = opts.offsetY ?? 0;
|
||||
const micrOy = opts.micrOffsetY ?? 0;
|
||||
const g1 = opts.micrGap1 ?? 1;
|
||||
const g2 = opts.micrGap2 ?? 1;
|
||||
|
||||
checks.forEach((c, idx) => {
|
||||
if (idx > 0) doc.addPage();
|
||||
|
||||
if (opts.style === "voucher") {
|
||||
drawCheck(doc, 0 + oy, c, ox, micrOy);
|
||||
drawCheck(doc, 0 + oy, c, ox, micrOy, g1, g2);
|
||||
drawStub(doc, SECTION_H + oy, c, ox);
|
||||
drawStub(doc, SECTION_H * 2 + oy, c, ox);
|
||||
} else {
|
||||
let posY = 0;
|
||||
if (opts.position === "middle") posY = PH / 2 - CHECK_H / 2;
|
||||
if (opts.position === "bottom") posY = PH - CHECK_H;
|
||||
drawCheck(doc, posY + oy, c, ox, micrOy);
|
||||
drawCheck(doc, posY + oy, c, ox, micrOy, g1, g2);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user