ux(labels): shorter adjustment type labels (display-only rename)

Path A rename - DB values untouched, only TYPE_CHOICES display
labels change:
  'New Loan'          -> shown as 'Loan'
  'Advance Payment'   -> shown as 'Advance'
  'Advance Repayment' -> shown as 'Advance Repaid'

Templates that render the type as visible text switched from
{{ adj.type }} to {{ adj.get_type_display }}. Data attributes and
CSS class slugs keep the raw DB value (identifiers, not labels).

Zero data migration. Zero changes to ADDITIVE_TYPES / DEDUCTIVE_TYPES
constants, hardcoded string comparisons, CSS class names, test
fixtures, or any other code that references the canonical DB value.
Every historic PayrollAdjustment row keeps type='New Loan' /
'Advance Payment' / 'Advance Repayment' as stored.

Django's makemigrations generated a no-op AlterField migration to
record the choices-metadata change.

Tests: 69/69.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Konrad du Plessis 2026-04-24 09:49:26 +02:00
parent e51a2f6d1d
commit c1d9014fe1
5 changed files with 36 additions and 11 deletions

View File

@ -0,0 +1,18 @@
# Generated by Django 5.2.7 on 2026-04-24 07:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0011_worker_tax_number'),
]
operations = [
migrations.AlterField(
model_name='payrolladjustment',
name='type',
field=models.CharField(choices=[('Bonus', 'Bonus'), ('Overtime', 'Overtime'), ('Deduction', 'Deduction'), ('Loan Repayment', 'Loan Repayment'), ('New Loan', 'Loan'), ('Advance Payment', 'Advance'), ('Advance Repayment', 'Advance Repaid')], max_length=50),
),
]

View File

@ -187,14 +187,21 @@ class Loan(models.Model):
return f"{self.worker.name} - {label} - {self.date}" return f"{self.worker.name} - {label} - {self.date}"
class PayrollAdjustment(models.Model): class PayrollAdjustment(models.Model):
# === PayrollAdjustment TYPE_CHOICES - canonical DB value | display label ===
# Path A rename (24 Apr 2026): DB values are PRESERVED as-is. Only the
# second tuple element (the human label) changes for three types, so
# users see shorter labels in tables while every historic row, formula,
# constant, test fixture, CSS class, and data-attribute KEEP WORKING
# UNCHANGED because they all key off the DB value on the left.
# See CLAUDE.md "UI-vs-DB naming drift" section for the full rule.
TYPE_CHOICES = [ TYPE_CHOICES = [
('Bonus', 'Bonus'), ('Bonus', 'Bonus'),
('Overtime', 'Overtime'), ('Overtime', 'Overtime'),
('Deduction', 'Deduction'), ('Deduction', 'Deduction'),
('Loan Repayment', 'Loan Repayment'), ('Loan Repayment', 'Loan Repayment'),
('New Loan', 'New Loan'), ('New Loan', 'Loan'),
('Advance Payment', 'Advance Payment'), ('Advance Payment', 'Advance'),
('Advance Repayment', 'Advance Repayment'), ('Advance Repayment', 'Advance Repaid'),
] ]
worker = models.ForeignKey(Worker, on_delete=models.CASCADE, related_name='adjustments') worker = models.ForeignKey(Worker, on_delete=models.CASCADE, related_name='adjustments')

View File

@ -34,7 +34,7 @@ Row actions differ by paid status:
</td> </td>
{# --- Type badge (colour comes from the .badge-type-<slug> CSS class) --- #} {# --- Type badge (colour comes from the .badge-type-<slug> CSS class) --- #}
<td><span class="badge-type-{{ adj.type|type_slug }}">{{ adj.type }}</span></td> <td><span class="badge-type-{{ adj.type|type_slug }}">{{ adj.get_type_display }}</span></td>
{# --- Amount (sign reflects additive vs deductive) --- #} {# --- Amount (sign reflects additive vs deductive) --- #}
<td class="text-end" style="font-variant-numeric: tabular-nums;"> <td class="text-end" style="font-variant-numeric: tabular-nums;">

View File

@ -367,7 +367,7 @@
data-adj-project="{{ adj.project_id|default:'' }}" data-adj-project="{{ adj.project_id|default:'' }}"
data-adj-worker="{{ adj.worker.name }}"> data-adj-worker="{{ adj.worker.name }}">
{% if adj.type == 'Bonus' or adj.type == 'Overtime' or adj.type == 'New Loan' or adj.type == 'Advance Payment' %}+{% else %}-{% endif %}R{{ adj.amount|floatformat:2 }} {% if adj.type == 'Bonus' or adj.type == 'Overtime' or adj.type == 'New Loan' or adj.type == 'Advance Payment' %}+{% else %}-{% endif %}R{{ adj.amount|floatformat:2 }}
{{ adj.type }} {{ adj.get_type_display }}
{% if adj.project %}({{ adj.project.name }}){% endif %} {% if adj.project %}({{ adj.project.name }}){% endif %}
</span> </span>
{% endfor %} {% endfor %}
@ -451,7 +451,7 @@
<td class="align-middle d-none d-lg-table-cell"> <td class="align-middle d-none d-lg-table-cell">
{% for adj in record.adjustments.all %} {% for adj in record.adjustments.all %}
<span class="badge {% if adj.type == 'Bonus' or adj.type == 'Overtime' or adj.type == 'Loan Repayment' or adj.type == 'Advance Repayment' %}bg-success{% elif adj.type == 'New Loan' or adj.type == 'Advance Payment' %}bg-warning{% else %}bg-danger{% endif %} me-1"> <span class="badge {% if adj.type == 'Bonus' or adj.type == 'Overtime' or adj.type == 'Loan Repayment' or adj.type == 'Advance Repayment' %}bg-success{% elif adj.type == 'New Loan' or adj.type == 'Advance Payment' %}bg-warning{% else %}bg-danger{% endif %} me-1">
{{ adj.type }}: R {{ adj.amount|floatformat:2 }} {{ adj.get_type_display }}: R {{ adj.amount|floatformat:2 }}
</span> </span>
{% empty %} {% empty %}
<span class="text-muted">-</span> <span class="text-muted">-</span>

View File

@ -128,7 +128,7 @@
<tbody> <tbody>
{% for adj in adjustments %} {% for adj in adjustments %}
<tr> <tr>
<td>{{ adj.type }}</td> <td>{{ adj.get_type_display }}</td>
<td><a href="{% url 'worker_detail' adj.worker.id %}" class="text-decoration-none">{{ adj.worker.name }}</a></td> <td><a href="{% url 'worker_detail' adj.worker.id %}" class="text-decoration-none">{{ adj.worker.name }}</a></td>
<td class="text-end">R {{ adj.amount|money }}</td> <td class="text-end">R {{ adj.amount|money }}</td>
<td> <td>