docs(claude): UI-vs-DB naming drift note (pre-rename)

Adds a new CLAUDE.md section documenting the display/DB gap that
Path A of the UX Polish Pass creates: user sees 'Loan' / 'Advance'
/ 'Advance Repaid' while DB stores 'New Loan' / 'Advance Payment'
/ 'Advance Repayment'. Includes a lookup table, the rule for when
to use which (DB for logic, display for templates), and the failure
symptom so future Claude sessions don't chase ghost filters.

Ships BEFORE the rename so the doc is searchable from minute one.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Konrad du Plessis 2026-04-24 09:43:04 +02:00
parent 0a4b12108e
commit e51a2f6d1d

View File

@ -72,6 +72,50 @@ sessions; grep `core/models.py` before using any field you haven't used before:
- `PayrollRecord.amount_paid` (DecimalField) + `PayrollRecord.work_logs` (M2M reverse) — NOT `total_amount` / `days_worked` (easy to guess wrong when writing test fixtures)
- `Loan.principal_amount` — NOT `principal`. `Loan.save()` auto-sets `remaining_balance = principal_amount` on create, so tests rarely need to pass both.
## UI-vs-DB naming drift (Apr 2026) — READ BEFORE WRITING FORMULAS
`PayrollAdjustment.type` is DISPLAYED to users with short labels,
but the raw string stored in the database is always the long
legacy value:
| What the user SEES | What the DATABASE stores |
|---|---|
| Bonus | `'Bonus'` |
| Overtime | `'Overtime'` |
| Deduction | `'Deduction'` |
| Loan Repayment | `'Loan Repayment'` |
| Loan | `'New Loan'` ← mismatch |
| Advance | `'Advance Payment'` ← mismatch |
| Advance Repaid | `'Advance Repayment'` ← mismatch |
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']`
in `views.py` uses DB values.
- `if adj.type == 'New Loan':` checks the DB value.
- `<span class="badge-type-{{ adj.type|type_slug }}">` produces
`.badge-type-new-loan` from the DB value.
- `<tr data-type="{{ adj.type }}">` emits the DB value.
- Tests use `PayrollAdjustment.objects.create(type='New Loan', ...)`.
Only user-facing template TEXT uses the short label — via
`{{ adj.get_type_display }}`, Django's built-in choices lookup.
The label mapping lives in `PayrollAdjustment.TYPE_CHOICES`
(`core/models.py`).
**How this happened:** originally the adjustment-creation dropdown
said "New Loan" because that's what the action meant (_"log a new
loan"_). That label then propagated into every other view — tables,
badges, reports. On 24 Apr 2026 we renamed the user-visible labels
to be shorter and cleaner BUT deliberately kept the database values
untouched — to avoid breaking historic rows, tests, and hardcoded
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'`.
## 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')`