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:
@@ -35,6 +35,8 @@ export default function AccountingCheckSetupPage() {
|
||||
const [offsetX, setOffsetX] = useState(0);
|
||||
const [offsetY, setOffsetY] = useState(0);
|
||||
const [micrOffsetY, setMicrOffsetY] = useState(0);
|
||||
const [micrGap1, setMicrGap1] = useState(1);
|
||||
const [micrGap2, setMicrGap2] = useState(1);
|
||||
|
||||
const { data: settings } = useQuery({
|
||||
queryKey: ["check-settings", cid],
|
||||
@@ -67,6 +69,8 @@ export default function AccountingCheckSetupPage() {
|
||||
setOffsetX(Number(s.offset_x ?? 0));
|
||||
setOffsetY(Number(s.offset_y ?? 0));
|
||||
setMicrOffsetY(Number(s.micr_offset_y ?? 0));
|
||||
setMicrGap1(Number(s.micr_gap_1 ?? 1));
|
||||
setMicrGap2(Number(s.micr_gap_2 ?? 1));
|
||||
}, [settings]);
|
||||
|
||||
const routingValid = /^\d{9}$/.test(routingNumber.replace(/\D/g, ""));
|
||||
@@ -90,6 +94,8 @@ export default function AccountingCheckSetupPage() {
|
||||
offset_x: offsetX,
|
||||
offset_y: offsetY,
|
||||
micr_offset_y: micrOffsetY,
|
||||
micr_gap_1: micrGap1,
|
||||
micr_gap_2: micrGap2,
|
||||
}, { onConflict: "company_id" });
|
||||
setSaving(false);
|
||||
if (error) return toast.error(error.message);
|
||||
@@ -130,7 +136,7 @@ export default function AccountingCheckSetupPage() {
|
||||
style: defaultStyle as any,
|
||||
position: defaultPosition as any,
|
||||
fontSize: fontSize as any,
|
||||
offsetX, offsetY, micrOffsetY,
|
||||
offsetX, offsetY, micrOffsetY, micrGap1, micrGap2,
|
||||
});
|
||||
const w = window.open("");
|
||||
if (w) w.document.write(`<iframe src="${dataUrl}" style="border:0;width:100%;height:100vh"></iframe>`);
|
||||
@@ -141,7 +147,7 @@ export default function AccountingCheckSetupPage() {
|
||||
style: defaultStyle as any,
|
||||
position: defaultPosition as any,
|
||||
fontSize: fontSize as any,
|
||||
offsetX, offsetY, micrOffsetY,
|
||||
offsetX, offsetY, micrOffsetY, micrGap1, micrGap2,
|
||||
});
|
||||
const w = window.open("");
|
||||
if (w) w.document.write(`<iframe src="${dataUrl}" style="border:0;width:100%;height:100vh" onload="this.contentWindow.print()"></iframe>`);
|
||||
@@ -345,6 +351,13 @@ export default function AccountingCheckSetupPage() {
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Run the alignment test first, measure the offset on blank paper, then adjust here and preview again.
|
||||
</p>
|
||||
<div className="border-t pt-3">
|
||||
<p className="text-sm font-medium">MICR gaps <span className="text-xs font-normal text-muted-foreground">(spaces between segments)</span></p>
|
||||
<div className="grid grid-cols-2 gap-6 mt-2">
|
||||
<GapControl label="Check # → Routing" value={micrGap1} onChange={setMicrGap1} />
|
||||
<GapControl label="Routing → Account" value={micrGap2} onChange={setMicrGap2} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
@@ -368,6 +381,35 @@ export default function AccountingCheckSetupPage() {
|
||||
);
|
||||
}
|
||||
|
||||
function GapControl({ label, value, onChange }: {
|
||||
label: string; value: number; onChange: (v: number) => void;
|
||||
}) {
|
||||
const adj = (d: number) => onChange(Math.max(0, Math.round(value + d)));
|
||||
return (
|
||||
<div className="space-y-1.5">
|
||||
<p className="text-xs font-medium text-foreground">{label}</p>
|
||||
<div className="flex items-center gap-1">
|
||||
<button onClick={() => adj(-1)}
|
||||
className="h-7 w-7 rounded border bg-background hover:bg-muted flex items-center justify-center font-bold text-sm shrink-0">
|
||||
−
|
||||
</button>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
step={1}
|
||||
value={value}
|
||||
onChange={(e) => onChange(Math.max(0, parseInt(e.target.value) || 0))}
|
||||
className="h-7 text-center font-mono text-xs"
|
||||
/>
|
||||
<button onClick={() => adj(1)}
|
||||
className="h-7 w-7 rounded border bg-background hover:bg-muted flex items-center justify-center font-bold text-sm shrink-0">
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OffsetControl({ label, hint, value, onChange }: {
|
||||
label: string; hint: string; value: number; onChange: (v: number) => void;
|
||||
}) {
|
||||
|
||||
Reference in New Issue
Block a user