diff --git a/CLAUDE.md b/CLAUDE.md index 42af622..3da3c13 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -906,3 +906,37 @@ Either works — pick one and stick to it per change to avoid divergence: - This system handles real payroll for field workers — accuracy is critical - `render_to_pdf()` uses lazy import of WeasyPrint to prevent app crash if library missing; on Windows it also auto-registers the GTK3 runtime's DLL directory so `ctypes.find_library()` can locate `gobject-2.0-0` (Python 3.8+ requires explicit `os.add_dll_directory()`) - Django admin is available at `/admin/` with full model registration and search/filter + +### `daily_rate` semantics — read before touching cost formulas + +**The value is live, not snapshotted.** `Worker.daily_rate` is a Python +property defined as `monthly_salary / Decimal('20.00')`. It is NEVER +stored on a WorkLog row. So every cost figure on the dashboard and +report — "Outstanding payments", "Labour Cost by Project", +"Company Avg / Working Day" — reflects whatever the worker's CURRENT +`monthly_salary` is. If you give a worker a raise today, every +historical cost total goes up retroactively. Same direction applies +to demotions. + +Where this matters in practice: +- The "Company Avg / Working Day" and "Company Avg / Month" hero + cards on `/report/` therefore explicitly subline "at current pay + rates" so the reader knows historical comparisons aren't apples + to apples after a raise. +- The same caveat applies (but is not labelled) on every other + cost-vs-time figure. If we ever need a true snapshot-of-the-day + cost, we'd add a `daily_rate_snapshot` DecimalField to WorkLog and + back-fill from history — a much bigger change deliberately deferred. + +**Two code paths compute it. They can drift.** The property does it +in Python; `_get_labour_costs` and `_company_cost_velocity` use a SQL +aggregate `Sum(F('workers__monthly_salary') / Decimal('20'))`. They +SHOULD produce identical results because the formula is the same — +but Python `Decimal` arithmetic and the underlying DB's NUMERIC +division can round differently in the last digit. If a test ever +catches a 1-cent discrepancy, that's why. The +`CompanyCostVelocitySQLAggregateTests` regression test compares the +two paths on a small fixture and would catch a real divergence. +Don't unify the two paths blindly — the property is used in many +template contexts where loading every Worker via a queryset would +be overkill.