From 0c705129f6f9067d8f9b50b6d655ace754fe5862 Mon Sep 17 00:00:00 2001 From: Konrad du Plessis Date: Sat, 16 May 2026 22:14:06 +0200 Subject: [PATCH] docs: TDD plan for Salary auto-scope picker (2 tasks, HARD STOP) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2 small tasks: (1) toggleProjectField() Salary sync + _paySalaryOpen re-apply, (2) docs. JS-only — manual-checklist verified, suite stays 207/207. Nothing pushed until Konrad's local verification. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...2026-05-16-salary-autoscope-picker-plan.md | 259 ++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 docs/plans/2026-05-16-salary-autoscope-picker-plan.md diff --git a/docs/plans/2026-05-16-salary-autoscope-picker-plan.md b/docs/plans/2026-05-16-salary-autoscope-picker-plan.md new file mode 100644 index 0000000..46e34e8 --- /dev/null +++ b/docs/plans/2026-05-16-salary-autoscope-picker-plan.md @@ -0,0 +1,259 @@ +# Salary Auto-Scope Picker — Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task (in-session, fresh subagent + 2-stage review per task). + +**Goal:** When the Add-Adjustment modal's type is set to `Salary`, auto-set the pay-type filter to "Managers only", hide daily-worker rows, and untick any selected daily worker — so a `Salary` adjustment can never silently target a daily worker. + +**Architecture:** Pure client-side JS. One sync block appended to the existing `toggleProjectField()` chokepoint (already fires on type change / init / Pay-Salary button / header reset) + one re-apply line in the `_paySalaryOpen` modal-open branch (because the `show.bs.modal` reset wipes the filter after the Pay-Salary button sets it). No view/model/template-data/URL change. + +**Tech Stack:** Django 5.2.7 template with vanilla JS; SQLite local (`USE_SQLITE=true`); Bootstrap 5 modal. + +**Design doc:** `docs/plans/2026-05-16-salary-autoscope-picker-design.md` (commit `8f443fa`). + +**Branch / baseline:** `ai-dev`, HEAD `8f443fa`, **207/207 tests passing**. On top of the paused, un-pushed Manager/Salaried + pay-type-filter commits. + +**Test command (Git Bash, per CLAUDE.md):** +```bash +USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests -v 2 +``` + +> **TDD note — read this.** This change is **pure browser JS** with no +> Django view / server / template-context surface, so there is no +> meaningful failing Django unit test to write first (asserting raw JS +> source text would be brittle and low-value). This matches the +> explicit, Konrad-approved precedent from the Task 3 modal toggle: the +> behaviour is verified by Konrad's **manual local checklist** (in the +> design doc), and the **automated regression gate is the full suite +> staying 207/207 OK** (the JS edit must not break template rendering). +> Do NOT add a source-text-sniffing test to satisfy a TDD ritual — the +> design doc documents this decision; the spec/quality reviewers will +> be told this is expected. + +> ⛔ **HARD STOP after Task 2.** Do NOT `git push`, do NOT deploy. Hand +> back to Konrad to run the manual verification checklist. Ships bundled +> with the rest of the paused Manager/Salaried + pay-type-filter work in +> ONE push, on Konrad's explicit say-so only. + +--- + +### Task 1: Salary auto-scope sync in `toggleProjectField()` + Pay-Salary re-apply + +**Files:** +- Modify: `core/templates/core/payroll_dashboard.html` — `toggleProjectField()` (currently lines 1928–1951) and the `_paySalaryOpen` branch (currently lines 2105–2108) + +**Step 1: Confirm the green baseline (regression anchor)** + +Run: +```bash +USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests -v 2 +``` +Expected: **207 tests, OK**. If not, STOP and report — do not proceed. + +**Step 2: Add the Salary sync block to `toggleProjectField()`** + +`toggleProjectField()` currently ends like this (lines 1943–1951): + +```javascript + // "Pay Immediately" checkbox — shown for New Loan AND Salary. + // Salary mirrors New Loan: ticked = pay the manager now + email + // payslip; unticked = leave it pending for the next pay cycle. + if (addAdjPayImmediatelyGroup) { + var payNowTypes = ['New Loan', 'Salary']; + addAdjPayImmediatelyGroup.style.display = + (payNowTypes.indexOf(addAdjType.value) !== -1) ? '' : 'none'; + } + } +``` + +Insert the following block **immediately before the closing `}` of +`toggleProjectField()`** (i.e. after the `if (addAdjPayImmediatelyGroup) { … }` +block, before the line ` }` that closes the function): + +```javascript + // === Salary → managers-only picker scope === + // Salary is a managers-only adjustment (Worker.pay_type='fixed'). + // To make it IMPOSSIBLE to accidentally pay a Salary to a daily + // worker, when the type is Salary we: (1) set the pay-type + // filter to "Managers only", (2) hide every non-manager row, + // and (3) UNTICK any non-manager that was already selected so a + // stray daily-worker tick can't ride along hidden into a bad + // Salary adjustment. For any other type we reset the filter to + // "All" and re-show every row (we do NOT re-tick anything — the + // user re-selects deliberately). Re-grab the filter