docs: document Manager/Salaried pay; park feature pending Konrad local verify + 2 follow-ups
This commit is contained in:
parent
268a050397
commit
61e1f1492c
43
CLAUDE.md
43
CLAUDE.md
@ -77,7 +77,7 @@ staticfiles/ — Collected static assets (Bootstrap, admin) — NOT in git (
|
||||
## Key Models
|
||||
- **UserProfile** — extends Django User (OneToOne); minimal, no extra fields in v5
|
||||
- **Project** — work sites with supervisor assignments (M2M User), start/end dates, active flag
|
||||
- **Worker** — profiles with salary, `daily_rate` property (monthly_salary / 20), photo, ID doc, PPE sizing (shoe, overall top, pants, tshirt), drivers license (boolean + file upload)
|
||||
- **Worker** — profiles with salary, `daily_rate` property (monthly_salary / 20), photo, ID doc, PPE sizing (shoe, overall top, pants, tshirt), drivers license (boolean + file upload). `pay_type` CharField (`'daily'` default | `'fixed'` = manager/salaried) + `is_salaried` property (True when `pay_type='fixed'`). Migration `0016_worker_pay_type` (defaults all existing workers to `'daily'`).
|
||||
- **Team** — groups of workers under a supervisor, with optional pay schedule (`pay_frequency`: weekly/fortnightly/monthly, `pay_start_date`: anchor date)
|
||||
- **WorkLog** — daily attendance: date, project, team, workers (M2M), supervisor, overtime, `priced_workers` (M2M)
|
||||
- **PayrollRecord** — completed payments linked to WorkLogs (M2M) and Worker (FK)
|
||||
@ -119,7 +119,7 @@ When writing ANY formula, filter, comparison, ORM query, test
|
||||
fixture, CSS class name, or `data-type=` attribute: use the
|
||||
DATABASE value (left column of the model).
|
||||
|
||||
- `ADDITIVE_TYPES = ['Bonus', 'Overtime', 'New Loan', 'Advance Payment']`
|
||||
- `ADDITIVE_TYPES = ['Bonus', 'Overtime', 'New Loan', 'Advance Payment', 'Salary']`
|
||||
in `views.py` uses DB values.
|
||||
- `if adj.type == 'New Loan':` checks the DB value.
|
||||
- `<span class="badge-type-{{ adj.type|type_slug }}">` produces
|
||||
@ -143,6 +143,16 @@ string comparisons across ~30 source locations.
|
||||
**Symptom of getting this wrong:** code that filters for
|
||||
`type='Loan'` returns zero rows. Fix: use `type='New Loan'`.
|
||||
|
||||
**"Manager / Salaried" is a Path-A display label** (same pattern as
|
||||
"New Loan"→"Loan"): the model stays `Worker`, the discriminator is
|
||||
`Worker.pay_type` (`'daily'` | `'fixed'`). No `Manager` model/table.
|
||||
|
||||
**Adding a value to `PayrollAdjustment.TYPE_CHOICES` DOES generate an
|
||||
`AlterField` migration in this codebase** — Django tracks `choices` in
|
||||
migration state, so `makemigrations --check` flags it; always commit
|
||||
the generated migration. Precedent `0012`; this feature added `0017`
|
||||
(no-op AlterField for the new `'Salary'` choice).
|
||||
|
||||
## Key Business Rules
|
||||
- All business logic lives in the `core/` app — do not create additional Django apps
|
||||
- Workers have a `daily_rate` property: `monthly_salary / Decimal('20.00')`
|
||||
@ -226,9 +236,35 @@ is the single authority for "which absences can this user see/touch". Admin
|
||||
sees all; supervisor sees absences for workers in any team they supervise
|
||||
(`worker__teams__supervisor=user`).
|
||||
|
||||
## Manager / Salaried pay (May 2026)
|
||||
|
||||
Managers are just `Worker(pay_type='fixed')` (Path-A — no new model;
|
||||
"Manager / Salaried" is a display-only label, discriminator is
|
||||
`pay_type`). They are excluded from **attendance + absence pickers
|
||||
ONLY** (`AttendanceLogForm`, `_build_team_workers_map`, attendance
|
||||
cost-estimate loop; `AbsenceLogForm`, `AbsenceEditForm`).
|
||||
|
||||
**CRITICAL invariant:** managers MUST stay selectable in payroll
|
||||
modals (`PayrollAdjustmentForm`, the Add-Adjustment modal) and
|
||||
`TeamForm`. Safety rationale: a manager can never reach a `WorkLog`,
|
||||
so all WorkLog-derived money math (daily-wage / outstanding /
|
||||
per-project labour cost) is provably unchanged — managers are paid
|
||||
purely through the `Salary` adjustment.
|
||||
|
||||
Paid via the `Salary` adjustment type through `add_adjustment`:
|
||||
project-required; "Pay Immediately" → isolated `PayrollRecord` (exact
|
||||
New Loan pattern); unpaid → generic pending row, netted later by
|
||||
`_process_single_payment` (NOT modified). Report `_build_report_context`
|
||||
exposes `salaried_cost_by_project` shown as a separate per-project
|
||||
"Management / Salaried Cost" card — NEVER merged into WorkLog-derived
|
||||
daily labour cost. Clean payslip layout via an `is_salary` flag that
|
||||
mirrors `is_advance`/`is_loan` across `pdf/payslip_pdf.html`,
|
||||
`email/payslip_email.html`, `core/payslip.html`. `--badge-salary-*`
|
||||
CSS (dark+light) + `.badge-type-salary`.
|
||||
|
||||
## Payroll Constants
|
||||
Defined at top of views.py — used in dashboard calculations and payment processing:
|
||||
- **ADDITIVE_TYPES** = `['Bonus', 'Overtime', 'New Loan', 'Advance Payment']` — increase worker's net pay
|
||||
- **ADDITIVE_TYPES** = `['Bonus', 'Overtime', 'New Loan', 'Advance Payment', 'Salary']` — increase worker's net pay
|
||||
- **DEDUCTIVE_TYPES** = `['Deduction', 'Loan Repayment', 'Advance Repayment']` — decrease net pay
|
||||
|
||||
## Django ORM gotcha — M2M filter + aggregate inflation
|
||||
@ -268,6 +304,7 @@ OR-union — "adjustments where either FK points to project P".
|
||||
- **Overtime** — links to `WorkLog` via `adj.work_log` FK; managed by `price_overtime()` view
|
||||
- **Loan Repayment** — links to `Loan` (loan_type='loan') via `adj.loan` FK; loan balance changes during payment processing
|
||||
- **Advance Repayment** — auto-created when an advance is paid; deducts from advance balance during `process_payment()`. If partial repayment, remaining balance converts advance to regular loan (`loan_type` changes from 'advance' to 'loan'). Editable by admin (amount can be reduced before payday).
|
||||
- **Salary** — manager / fixed monthly pay (additive, project-required). Has a "Pay Immediately" path mirroring New Loan (isolated `PayrollRecord`); unpaid nets via `_process_single_payment`. Only for `Worker(pay_type='fixed')` — see "Manager / Salaried pay" section.
|
||||
|
||||
## Outstanding Payments Logic (Dashboard)
|
||||
The dashboard's outstanding amount uses **per-worker** checking, not per-log:
|
||||
|
||||
@ -35,6 +35,76 @@ collectstatic — pure template + view change.
|
||||
|
||||
---
|
||||
|
||||
## ⏸ Paused — implemented locally, awaiting Konrad's verification (not pushed)
|
||||
|
||||
### Manager / Salaried Pay
|
||||
|
||||
**Status:** Brainstormed + designed + planned + **fully
|
||||
implemented** (Tasks 1-7), each task two-stage code-reviewed,
|
||||
execution complete but **HARD STOPPED before push** pending
|
||||
Konrad's manual local verification (Konrad's call, 15 May 2026).
|
||||
Design doc `docs/plans/2026-05-15-manager-salaried-pay-design.md`
|
||||
(local commit `325c59d`), plan
|
||||
`docs/plans/2026-05-15-manager-salaried-pay-plan.md` (local commit
|
||||
`4dadb7c`). **The design + plan + all 7 implementation/polish
|
||||
commits are local-only on `ai-dev`, NOT pushed to origin** — Konrad
|
||||
wants nothing reaching the working app until he's verified it
|
||||
locally. **201/201 tests green locally.**
|
||||
|
||||
**What it does:** Lets a manager / salaried worker be paid a fixed
|
||||
monthly amount without ever logging attendance. A new
|
||||
`Worker.pay_type` (`'daily'` default | `'fixed'`) marks managers;
|
||||
they're excluded from attendance + absence pickers ONLY (NOT payroll
|
||||
modals / TeamForm — they must stay payable), and paid via a new
|
||||
`Salary` `PayrollAdjustment` type (project-required; "Pay
|
||||
Immediately" → isolated PayrollRecord like New Loan; unpaid nets via
|
||||
`_process_single_payment`). The report shows a separate per-project
|
||||
"Management / Salaried Cost" line — never merged into WorkLog-derived
|
||||
daily labour cost, so all existing money math is provably unchanged.
|
||||
"Manager / Salaried" is a Path-A display-only label (model stays
|
||||
`Worker`). Migrations `0016_worker_pay_type`, `0017_alter_payrolladjustment_type`.
|
||||
|
||||
**To resume:** Konrad runs the manual verification checklist
|
||||
("Verification (manual, local — Konrad) — HARD STOP before any
|
||||
push", section in
|
||||
`docs/plans/2026-05-15-manager-salaried-pay-design.md`) on a local
|
||||
instance, then **explicitly approves a push**. Only after that
|
||||
explicit approval does anything get pushed to `origin/ai-dev`. No
|
||||
code changes pending — implementation is complete; this is purely a
|
||||
human verify-then-approve gate.
|
||||
|
||||
#### Follow-ups from code review (parked, out of scope for this feature)
|
||||
|
||||
1. **Atomicity hardening (cross-cutting, pre-existing).**
|
||||
`add_adjustment`'s three immediate-payment branches — `New Loan`,
|
||||
`Advance Payment`, and now `Salary` — create a `PayrollAdjustment`
|
||||
+ `PayrollRecord` and call `_send_payslip_email` WITHOUT a
|
||||
wrapping `transaction.atomic()`, and `_send_payslip_email`
|
||||
re-raises on email failure. A mid-sequence failure can therefore
|
||||
orphan a payment row / 500 after commit. This is **PRE-EXISTING**
|
||||
(Salary just consistently matches the New Loan / Advance pattern,
|
||||
it did not introduce the gap) — flagged by Task 5 code review as a
|
||||
separate hardening ticket, **NOT a Manager/Salaried defect**.
|
||||
Recommended future cross-cutting fix: wrap each immediate-branch
|
||||
body in `transaction.atomic()` and swallow/log email failures the
|
||||
way `process_payment` already does. Out of scope for this feature.
|
||||
2. **Pre-existing flaky test.** `AbsenceListViewTests` (in
|
||||
`core/tests.py`) has an `assertContains`/`assertNotContains`
|
||||
against the `/absences/` list HTML that intermittently failed
|
||||
~1-in-3 in one run during this feature's execution, then passed on
|
||||
rerun; 3+ subsequent full-suite runs were clean. A reviewer's
|
||||
isolation analysis concluded it's pre-existing environmental
|
||||
nondeterminism (proper Django `TestCase` transactional isolation;
|
||||
the Manager/Salaried tests cannot perturb it). Low priority:
|
||||
investigate a possible date/locale or response-rendering
|
||||
nondeterminism in that test. **NOT introduced by this feature.**
|
||||
|
||||
> Note: the **Post-Attendance Flow v2** paused entry above is STILL
|
||||
> paused / unchanged — this Manager/Salaried entry does not affect
|
||||
> it.
|
||||
|
||||
---
|
||||
|
||||
## Production status — ✅ fully caught up (15 May 2026)
|
||||
|
||||
Production (`https://foxlog.flatlogic.app/`) is deployed at
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user