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:
@@ -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.
|
||||
- **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 <id>` 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.
|
||||
|
||||
|
||||
@@ -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`,
|
||||
|
||||
Reference in New Issue
Block a user