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:
parent
803f8696e7
commit
81009be0c6
@ -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)
|
||||||
|
|||||||
@ -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')
|
||||||
|
|||||||
@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user