From 81009be0c622067e1d8078e909655b992adb9e1c Mon Sep 17 00:00:00 2001 From: Konrad du Plessis Date: Mon, 20 Apr 2026 13:10:46 +0200 Subject: [PATCH] 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 --- CLAUDE.md | 2 +- core/admin.py | 17 +++++++- ...nse_worker_has_drivers_license_and_more.py | 43 +++++++++++++++++++ core/models.py | 12 ++++++ core/views.py | 9 +++- 5 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 core/migrations/0006_worker_drivers_license_worker_has_drivers_license_and_more.py diff --git a/CLAUDE.md b/CLAUDE.md index 2a9be43..da41dd8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,7 +37,7 @@ staticfiles/ — Collected static assets (Bootstrap, admin) ## Key Models - **UserProfile** — extends Django User (OneToOne); minimal, no extra fields in v5 - **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) - **WorkLog** — daily attendance: date, project, team, workers (M2M), supervisor, overtime, `priced_workers` (M2M) - **PayrollRecord** — completed payments linked to WorkLogs (M2M) and Worker (FK) diff --git a/core/admin.py b/core/admin.py index 307b99f..eb5a830 100644 --- a/core/admin.py +++ b/core/admin.py @@ -20,9 +20,24 @@ class ProjectAdmin(admin.ModelAdmin): @admin.register(Worker) class WorkerAdmin(admin.ModelAdmin): list_display = ('name', 'id_number', 'monthly_salary', 'active') - list_filter = ('active',) + list_filter = ('active', 'has_drivers_license') 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) class TeamAdmin(admin.ModelAdmin): list_display = ('name', 'supervisor', 'pay_frequency', 'pay_start_date', 'active') diff --git a/core/migrations/0006_worker_drivers_license_worker_has_drivers_license_and_more.py b/core/migrations/0006_worker_drivers_license_worker_has_drivers_license_and_more.py new file mode 100644 index 0000000..9deefbe --- /dev/null +++ b/core/migrations/0006_worker_drivers_license_worker_has_drivers_license_and_more.py @@ -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), + ), + ] diff --git a/core/models.py b/core/models.py index 5a52f9c..381ab0e 100644 --- a/core/models.py +++ b/core/models.py @@ -44,6 +44,18 @@ class Worker(models.Model): notes = models.TextField(blank=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 def daily_rate(self): # monthly salary divided by 20 working days diff --git a/core/views.py b/core/views.py index 2274fe2..ae55eff 100644 --- a/core/views.py +++ b/core/views.py @@ -769,7 +769,9 @@ def export_workers_csv(request): writer = csv.writer(response) writer.writerow([ '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: @@ -782,6 +784,11 @@ def export_workers_csv(request): w.employment_date.strftime('%Y-%m-%d') if w.employment_date else '', 'Yes' if w.active else 'No', 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