Add PPE sizing and drivers license fields to Worker model

New fields: shoe_size, overall_top_size, pants_size, tshirt_size,
has_drivers_license (boolean), drivers_license (file upload).
Admin organised into 3 fieldsets. CSV export updated with new columns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Konrad du Plessis 2026-04-20 13:10:46 +02:00
parent 803f8696e7
commit 81009be0c6
5 changed files with 80 additions and 3 deletions

View File

@ -37,7 +37,7 @@ staticfiles/ — Collected static assets (Bootstrap, admin)
## Key Models ## Key Models
- **UserProfile** — extends Django User (OneToOne); minimal, no extra fields in v5 - **UserProfile** — extends Django User (OneToOne); minimal, no extra fields in v5
- **Project** — work sites with supervisor assignments (M2M User), start/end dates, active flag - **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 - **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)
- **Team** — groups of workers under a supervisor, with optional pay schedule (`pay_frequency`: weekly/fortnightly/monthly, `pay_start_date`: anchor date) - **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) - **WorkLog** — daily attendance: date, project, team, workers (M2M), supervisor, overtime, `priced_workers` (M2M)
- **PayrollRecord** — completed payments linked to WorkLogs (M2M) and Worker (FK) - **PayrollRecord** — completed payments linked to WorkLogs (M2M) and Worker (FK)

View File

@ -20,9 +20,24 @@ class ProjectAdmin(admin.ModelAdmin):
@admin.register(Worker) @admin.register(Worker)
class WorkerAdmin(admin.ModelAdmin): class WorkerAdmin(admin.ModelAdmin):
list_display = ('name', 'id_number', 'monthly_salary', 'active') list_display = ('name', 'id_number', 'monthly_salary', 'active')
list_filter = ('active',) list_filter = ('active', 'has_drivers_license')
search_fields = ('name', 'id_number', 'phone_number') search_fields = ('name', 'id_number', 'phone_number')
# === FIELDSETS ===
# Organise the worker edit form into clear sections
fieldsets = (
('Personal Info', {
'fields': ('name', 'id_number', 'phone_number', 'monthly_salary',
'employment_date', 'active', 'notes'),
}),
('Sizing', {
'fields': ('shoe_size', 'overall_top_size', 'pants_size', 'tshirt_size'),
}),
('Documents & License', {
'fields': ('photo', 'id_document', 'has_drivers_license', 'drivers_license'),
}),
)
@admin.register(Team) @admin.register(Team)
class TeamAdmin(admin.ModelAdmin): class TeamAdmin(admin.ModelAdmin):
list_display = ('name', 'supervisor', 'pay_frequency', 'pay_start_date', 'active') list_display = ('name', 'supervisor', 'pay_frequency', 'pay_start_date', 'active')

View File

@ -0,0 +1,43 @@
# Generated by Django 5.2.7 on 2026-04-20 10:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0005_team_pay_frequency_team_pay_start_date'),
]
operations = [
migrations.AddField(
model_name='worker',
name='drivers_license',
field=models.FileField(blank=True, null=True, upload_to='workers/documents/'),
),
migrations.AddField(
model_name='worker',
name='has_drivers_license',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='worker',
name='overall_top_size',
field=models.CharField(blank=True, max_length=10),
),
migrations.AddField(
model_name='worker',
name='pants_size',
field=models.CharField(blank=True, max_length=20),
),
migrations.AddField(
model_name='worker',
name='shoe_size',
field=models.CharField(blank=True, max_length=20),
),
migrations.AddField(
model_name='worker',
name='tshirt_size',
field=models.CharField(blank=True, max_length=10),
),
]

View File

@ -44,6 +44,18 @@ class Worker(models.Model):
notes = models.TextField(blank=True) notes = models.TextField(blank=True)
active = models.BooleanField(default=True) active = models.BooleanField(default=True)
# === SIZING ===
# Clothing and boot sizes for PPE (personal protective equipment) ordering
shoe_size = models.CharField(max_length=20, blank=True)
overall_top_size = models.CharField(max_length=10, blank=True)
pants_size = models.CharField(max_length=20, blank=True)
tshirt_size = models.CharField(max_length=10, blank=True)
# === DRIVERS LICENSE ===
# Track which workers have a valid drivers license and store a scanned copy
has_drivers_license = models.BooleanField(default=False)
drivers_license = models.FileField(upload_to='workers/documents/', blank=True, null=True)
@property @property
def daily_rate(self): def daily_rate(self):
# monthly salary divided by 20 working days # monthly salary divided by 20 working days

View File

@ -769,7 +769,9 @@ def export_workers_csv(request):
writer = csv.writer(response) writer = csv.writer(response)
writer.writerow([ writer.writerow([
'Name', 'ID Number', 'Phone Number', 'Monthly Salary', 'Name', 'ID Number', 'Phone Number', 'Monthly Salary',
'Daily Rate', 'Employment Date', 'Active', 'Notes' 'Daily Rate', 'Employment Date', 'Active', 'Notes',
'Shoe Size', 'Overall Top Size', 'Pants Size', 'T-Shirt Size',
'Has Drivers License',
]) ])
for w in workers: for w in workers:
@ -782,6 +784,11 @@ def export_workers_csv(request):
w.employment_date.strftime('%Y-%m-%d') if w.employment_date else '', w.employment_date.strftime('%Y-%m-%d') if w.employment_date else '',
'Yes' if w.active else 'No', 'Yes' if w.active else 'No',
w.notes, w.notes,
w.shoe_size,
w.overall_top_size,
w.pants_size,
w.tshirt_size,
'Yes' if w.has_drivers_license else 'No',
]) ])
return response return response