Fix variable bill amount lost on paid toggle; revert gross/net protection

- 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 <noreply@anthropic.com>
This commit is contained in:
2026-03-19 20:41:57 -04:00
parent 5aaa5cfed8
commit 6e771ad20b
2 changed files with 9 additions and 16 deletions

View File

@@ -9,6 +9,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- **Mark tasks in td**: `td start <id>` when beginning, `td close <id>` when done. - **Mark tasks in td**: `td start <id>` when beginning, `td close <id>` 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. - **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. - **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 <id>` after verifying the work.
## Task Management ## 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. **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. **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.

View File

@@ -159,20 +159,8 @@ async function generatePaychecks(year, month) {
VALUES ($1, $2, $3, $4, $5, $6) VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (period_year, period_month, paycheck_number) ON CONFLICT (period_year, period_month, paycheck_number)
DO UPDATE SET pay_date = EXCLUDED.pay_date, DO UPDATE SET pay_date = EXCLUDED.pay_date,
gross = CASE gross = EXCLUDED.gross,
WHEN EXISTS ( net = EXCLUDED.net
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
RETURNING id`, RETURNING id`,
[year, month, num, payDate, gross || 0, net || 0] [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 `UPDATE paycheck_bills pb
SET paid = $1, SET paid = $1,
paid_at = CASE WHEN $1 THEN NOW() ELSE NULL END, 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 FROM bills b
WHERE pb.bill_id = b.id AND pb.id = $2 WHERE pb.bill_id = b.id AND pb.id = $2
RETURNING pb.id, pb.paid, pb.paid_at, pb.amount_override`, RETURNING pb.id, pb.paid, pb.paid_at, pb.amount_override`,