From 6e771ad20be96d38027510bea6a8284fdcca3d74 Mon Sep 17 00:00:00 2001 From: Christian Hood Date: Thu, 19 Mar 2026 20:41:57 -0400 Subject: [PATCH] Fix variable bill amount lost on paid toggle; revert gross/net protection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PATCH paycheck-bills/:id/paid: variable bills now preserve amount_override rather than overwriting it with b.amount (which may be null/0). Fixed bills continue to lock in b.amount on paid and clear on unpaid. - generatePaychecks: revert gross/net protection — refresh always updates gross/net from current settings as originally intended. - CLAUDE.md: remove gross/net protection note; add td approve sub-agent rule. Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 3 ++- server/src/routes/paychecks.js | 22 +++++++--------------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 07d7e27..189c03e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,6 +9,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - **Mark tasks in td**: `td start ` when beginning, `td close ` when done. - **Run td commands one at a time**: Never chain `td` commands with `&&` or `;`. Each `td` call must be its own separate shell invocation. - **Only the orchestrator touches td**: Sub-agents must never call `td` commands directly. Concurrent `td` writes from parallel agents corrupt the SQLite database. The orchestrator handles all `td start`/`td close` calls before and after delegating to sub-agents. +- **td approve requires a sub-agent**: `td approve` cannot be run by the same agent that implemented the task. Always spin up a sub-agent to review and run `td approve ` after verifying the work. ## Task Management @@ -61,7 +62,7 @@ The default route `/` renders the paycheck-centric main view (`client/src/pages/ **Variable amount bills:** Bills with `variable_amount = true` require the amount to be entered each month in the paycheck view (stored as `amount_override` on `paycheck_bills`). The bill's `amount` field serves as an optional typical/estimated value. -**Lazy paycheck generation:** `GET /api/paychecks` returns virtual (unsaved) data with `id: null` when no DB record exists for the month. Paychecks are only persisted when the first interaction occurs (bill toggle, expense add, etc.). The "↺ Refresh amounts" button on the paycheck view re-runs `POST /api/paychecks/generate` to sync gross/net from current Settings — but gross/net are never overwritten when the paycheck already has any paid bills, preserving historical income values. Individual paycheck gross/net can also be edited inline via the pencil icon. +**Lazy paycheck generation:** `GET /api/paychecks` returns virtual (unsaved) data with `id: null` when no DB record exists for the month. Paychecks are only persisted when the first interaction occurs (bill toggle, expense add, etc.). The "↺ Refresh amounts" button on the paycheck view re-runs `POST /api/paychecks/generate` to sync gross/net from current Settings. Individual paycheck gross/net can also be edited inline via the pencil icon. **Financing:** `GET/POST /api/financing`, `PUT/DELETE /api/financing/:id`, `PATCH /api/financing-payments/:id/paid`. Plans track a total amount, payoff due date, and `start_date`. Payment per period is auto-calculated as `(remaining balance) / (remaining periods)`. Split plans (`assigned_paycheck = null`) divide each period's payment across both paychecks. Plans auto-close when fully paid. Financing payments are included in the paycheck remaining balance. `start_date` prevents a plan from appearing on paycheck months before it was created — both virtual previews and `generate` respect this guard. diff --git a/server/src/routes/paychecks.js b/server/src/routes/paychecks.js index ccd9472..b34ed0b 100644 --- a/server/src/routes/paychecks.js +++ b/server/src/routes/paychecks.js @@ -159,20 +159,8 @@ async function generatePaychecks(year, month) { VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (period_year, period_month, paycheck_number) DO UPDATE SET pay_date = EXCLUDED.pay_date, - gross = CASE - WHEN EXISTS ( - SELECT 1 FROM paycheck_bills pb - WHERE pb.paycheck_id = paychecks.id AND pb.paid = TRUE - ) THEN paychecks.gross - ELSE EXCLUDED.gross - END, - net = CASE - WHEN EXISTS ( - SELECT 1 FROM paycheck_bills pb - WHERE pb.paycheck_id = paychecks.id AND pb.paid = TRUE - ) THEN paychecks.net - ELSE EXCLUDED.net - END + gross = EXCLUDED.gross, + net = EXCLUDED.net RETURNING id`, [year, month, num, payDate, gross || 0, net || 0] ); @@ -474,7 +462,11 @@ router.patch('/paycheck-bills/:id/paid', async (req, res) => { `UPDATE paycheck_bills pb SET paid = $1, paid_at = CASE WHEN $1 THEN NOW() ELSE NULL END, - amount_override = CASE WHEN $1 THEN b.amount ELSE NULL END + amount_override = CASE + WHEN b.variable_amount THEN pb.amount_override + WHEN $1 THEN b.amount + ELSE NULL + END FROM bills b WHERE pb.bill_id = b.id AND pb.id = $2 RETURNING pb.id, pb.paid, pb.paid_at, pb.amount_override`,