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:
parent
0a4b12108e
commit
e51a2f6d1d
44
CLAUDE.md
44
CLAUDE.md
@ -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')`
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user