Complete working state of the session. Will be split into two deploy phases (safety scaffolding then feature release) before merging to ai-dev. Includes: - Security fixes (email creds / SECRET_KEY / DEBUG / CSRF) - Backup + restore management commands and browser endpoints - WeasyPrint migration (replaces xhtml2pdf) - New Worker fields + WorkerCertificate + WorkerWarning models - Worker / Team / Project friendly management UIs - Dashboard cert-expiry card + Manage All buttons - Bootstrap tooltips (global init + theme-aware CSS) - Django admin template override (taller M2M pickers) - Money filter for ZAR currency formatting - Resources dropdown nav - Massive CLAUDE.md expansion + deploy plan docs Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
664 lines
24 KiB
Markdown
664 lines
24 KiB
Markdown
# Worker Management Expansion — Design & Plan
|
||
|
||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. REQUIRED SUB-SKILL: Use superpowers:test-driven-development where tests are called for.
|
||
|
||
**Goal:** Extend the Worker model with certifications + disciplinary records, add a friendly in-app worker-edit form (replacing the need to use Django admin for common edits), and build a batch worker report showing projects, teams, days worked, and first/last payslip dates.
|
||
|
||
**Architecture:**
|
||
- Two new models (`WorkerCertificate`, `WorkerWarning`) with ForeignKey to existing `Worker`.
|
||
- A worker list page + worker edit page, both admin-only, replacing most everyday admin needs.
|
||
- A batch-report page (HTML + CSV + PDF) summarising each worker's full work history.
|
||
- All new work lives inside the existing `core/` app — no new apps. Reuses existing patterns: `is_admin()` gating, `@login_required`, Bootstrap modals, `render_to_pdf()`, inline formsets.
|
||
|
||
**Tech Stack:** Django 5.2.7, Python 3.13, SQLite (local) / MySQL (prod), Bootstrap 5, WeasyPrint for PDFs, existing `format_tags.money` filter.
|
||
|
||
---
|
||
|
||
## Context
|
||
|
||
**Why this is being built:**
|
||
The owner (Konrad) currently manages workers via `/admin/core/worker/` — which is functional but lacks:
|
||
- Certification tracking (Skills, PDP, First Aid, Medical, Work at Height) with expiry dates and document uploads
|
||
- A disciplinary/warnings record per worker
|
||
- A friendlier edit UI than Django admin's default form
|
||
- A consolidated worker-history report (projects worked, days, payslips) for review or regulatory/auditor purposes
|
||
|
||
This plan closes all four gaps in one coherent feature.
|
||
|
||
**Outcomes when complete:**
|
||
1. Per worker, the admin can see and maintain: all 5 cert types, all warnings, in one page.
|
||
2. The admin has a worker list and worker edit page that are easier than Django admin.
|
||
3. The admin can generate a batch report of all workers' project/team/day/payslip history — viewable, CSV-exportable, PDF-exportable.
|
||
|
||
---
|
||
|
||
## Scope (explicit)
|
||
|
||
### In scope
|
||
- New models: `WorkerCertificate`, `WorkerWarning`
|
||
- Django admin registrations for both (inline on Worker)
|
||
- Worker list page (admin-only): `/workers/`
|
||
- Worker edit page (admin-only): `/workers/<id>/edit/` and `/workers/new/`
|
||
- Worker detail page (admin-only, read-only view with history): `/workers/<id>/`
|
||
- Batch worker report: `/workers/report/` (HTML), `/workers/report/csv/`, `/workers/report/pdf/`
|
||
- Nav links added to base.html
|
||
- Migrations for the two new models
|
||
- Updates to `CLAUDE.md` documenting the new pieces
|
||
|
||
### Out of scope (not in this plan — can be follow-ups)
|
||
- Cert expiry email alerts
|
||
- Worker self-service portal (only admins use this)
|
||
- Photo cropping / file optimisation
|
||
- Bulk cert upload (edit one worker at a time)
|
||
|
||
---
|
||
|
||
## Files to Create / Modify
|
||
|
||
| File | Action | Purpose |
|
||
|------|--------|---------|
|
||
| `core/models.py` | Modify | Add `WorkerCertificate` and `WorkerWarning` model classes |
|
||
| `core/migrations/0009_worker_certificates_warnings.py` | Create (auto) | Schema changes |
|
||
| `core/admin.py` | Modify | Register new models; add inlines to WorkerAdmin |
|
||
| `core/forms.py` | Modify | `WorkerForm`, `WorkerCertificateFormSet`, `WorkerWarningFormSet` |
|
||
| `core/views.py` | Modify | 6 new views: worker_list, worker_detail, worker_edit, worker_batch_report, worker_batch_report_csv, worker_batch_report_pdf |
|
||
| `core/urls.py` | Modify | 6 new routes |
|
||
| `core/templates/core/workers/list.html` | Create | Worker list with search + filters + "Add Worker" button |
|
||
| `core/templates/core/workers/edit.html` | Create | Section-based edit form with inline certs + warnings |
|
||
| `core/templates/core/workers/detail.html` | Create | Read-only worker profile with history tabs |
|
||
| `core/templates/core/workers/batch_report.html` | Create | HTML batch report (extends base.html) |
|
||
| `core/templates/core/pdf/workers_report_pdf.html` | Create | PDF version of batch report (uses WeasyPrint) |
|
||
| `core/templates/base.html` | Modify | Add "Workers" nav link (admin only) |
|
||
| `CLAUDE.md` | Modify | Document new models, URLs, admin patterns |
|
||
|
||
---
|
||
|
||
## Data Model
|
||
|
||
### `WorkerCertificate`
|
||
|
||
```python
|
||
class WorkerCertificate(models.Model):
|
||
"""A certification held by a worker (Skills, PDP, First Aid, etc.).
|
||
|
||
One row per (worker, cert_type) — existence of the row means the
|
||
worker currently holds this certification. Delete the row to record
|
||
that they no longer hold it. Use `valid_until` to track expiry.
|
||
"""
|
||
CERT_TYPES = [
|
||
('skills', 'Skills Certificate'),
|
||
('pdp', 'PDP (Professional Driving Permit)'),
|
||
('first_aid', 'First Aid'),
|
||
('medical', 'Medical'),
|
||
('work_at_height', 'Work at Height'),
|
||
]
|
||
|
||
worker = models.ForeignKey(
|
||
Worker, related_name='certificates', on_delete=models.CASCADE,
|
||
)
|
||
cert_type = models.CharField(max_length=30, choices=CERT_TYPES)
|
||
document = models.FileField(
|
||
upload_to='workers/certificates/', blank=True, null=True,
|
||
help_text='Scan or photo of the certificate',
|
||
)
|
||
issued_date = models.DateField(blank=True, null=True)
|
||
valid_until = models.DateField(
|
||
blank=True, null=True,
|
||
help_text='Expiry date — leave blank if the cert does not expire',
|
||
)
|
||
notes = models.TextField(blank=True)
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
|
||
class Meta:
|
||
unique_together = [('worker', 'cert_type')]
|
||
ordering = ['worker', 'cert_type']
|
||
|
||
def __str__(self):
|
||
return f'{self.worker.name} — {self.get_cert_type_display()}'
|
||
|
||
@property
|
||
def is_expired(self):
|
||
if not self.valid_until:
|
||
return False
|
||
return self.valid_until < timezone.now().date()
|
||
|
||
@property
|
||
def expires_soon(self):
|
||
"""True if the cert expires within the next 30 days."""
|
||
if not self.valid_until:
|
||
return False
|
||
today = timezone.now().date()
|
||
return today <= self.valid_until <= today + datetime.timedelta(days=30)
|
||
```
|
||
|
||
### `WorkerWarning`
|
||
|
||
```python
|
||
class WorkerWarning(models.Model):
|
||
"""A disciplinary warning issued to a worker."""
|
||
SEVERITY_CHOICES = [
|
||
('verbal', 'Verbal Warning'),
|
||
('written', 'Written Warning'),
|
||
('final', 'Final Warning'),
|
||
]
|
||
|
||
worker = models.ForeignKey(
|
||
Worker, related_name='warnings', on_delete=models.CASCADE,
|
||
)
|
||
date = models.DateField(default=timezone.now)
|
||
severity = models.CharField(max_length=20, choices=SEVERITY_CHOICES)
|
||
reason = models.CharField(
|
||
max_length=200,
|
||
help_text='Short summary — e.g. "Repeated lateness"',
|
||
)
|
||
description = models.TextField(
|
||
blank=True,
|
||
help_text='Full context of what happened',
|
||
)
|
||
issued_by = models.ForeignKey(
|
||
User, on_delete=models.SET_NULL, null=True, blank=True,
|
||
related_name='warnings_issued',
|
||
)
|
||
document = models.FileField(
|
||
upload_to='workers/warnings/', blank=True, null=True,
|
||
help_text='Signed warning form (optional)',
|
||
)
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
|
||
class Meta:
|
||
ordering = ['-date']
|
||
|
||
def __str__(self):
|
||
return f'{self.worker.name} — {self.get_severity_display()} ({self.date})'
|
||
```
|
||
|
||
### Migration
|
||
|
||
`python manage.py makemigrations core` → produces `0009_worker_certificates_warnings.py`.
|
||
Safe migration: two new tables, no changes to existing ones. Reversible.
|
||
|
||
---
|
||
|
||
## URL Routes (new)
|
||
|
||
Add to `core/urls.py` before the `# === EXPENSE RECEIPTS ===` section:
|
||
|
||
```python
|
||
# === WORKERS ===
|
||
# Admin-friendly worker management UI (alternative to /admin/core/worker/)
|
||
path('workers/', views.worker_list, name='worker_list'),
|
||
path('workers/new/', views.worker_edit, name='worker_new'),
|
||
path('workers/<int:worker_id>/', views.worker_detail, name='worker_detail'),
|
||
path('workers/<int:worker_id>/edit/', views.worker_edit, name='worker_edit'),
|
||
# Batch report (table of all workers with aggregated history)
|
||
path('workers/report/', views.worker_batch_report, name='worker_batch_report'),
|
||
path('workers/report/csv/', views.worker_batch_report_csv, name='worker_batch_report_csv'),
|
||
path('workers/report/pdf/', views.worker_batch_report_pdf, name='worker_batch_report_pdf'),
|
||
```
|
||
|
||
---
|
||
|
||
## Views (new)
|
||
|
||
All admin-gated via `@login_required` + `is_admin(request.user)` → 403 if not admin.
|
||
|
||
### `worker_list(request)`
|
||
- Fetches all Workers (not just active).
|
||
- Filters: `?q=search_term`, `?status=active|inactive|all`.
|
||
- Template: `core/workers/list.html`.
|
||
- Shows table: name, ID, phone, salary, days worked, active toggle.
|
||
|
||
### `worker_detail(request, worker_id)`
|
||
- Worker profile (read-only).
|
||
- Shows: personal info, PPE, license, certs (with expiry highlights), warnings, and a "History" tab with projects/teams/days/payslips.
|
||
- Template: `core/workers/detail.html`.
|
||
|
||
### `worker_edit(request, worker_id=None)`
|
||
- GET: renders form pre-filled (or blank if `worker_id is None`).
|
||
- POST: validates + saves Worker + inline formsets for certs + warnings.
|
||
- Redirect on success: → `worker_detail`.
|
||
- Template: `core/workers/edit.html`.
|
||
|
||
### `worker_batch_report(request)`
|
||
- Builds per-worker aggregates using a shared `_build_worker_report_context()` helper (parallel to `_build_report_context`).
|
||
- Filters: `?status=`, `?project=`, `?team=`.
|
||
- Template: `core/workers/batch_report.html`.
|
||
|
||
### `worker_batch_report_csv(request)`
|
||
- Same context builder; streams a CSV with all columns.
|
||
|
||
### `worker_batch_report_pdf(request)`
|
||
- Same context builder; uses `render_to_pdf('core/pdf/workers_report_pdf.html', context)`.
|
||
|
||
### Shared helper `_build_worker_report_context(status=None, project_id=None, team_id=None)`
|
||
Returns a list of dicts, one per worker:
|
||
```python
|
||
{
|
||
'worker': worker_obj,
|
||
'projects': ['Solar Farm Alpha', 'Solar Farm Beta'], # distinct project names
|
||
'teams': ['Team Alpha'], # distinct team names
|
||
'days_worked': 47, # distinct WorkLog dates
|
||
'first_payslip_date': date(2025, 3, 14) or None,
|
||
'last_payslip_date': date(2026, 4, 5) or None,
|
||
'total_paid_lifetime': Decimal('127450.00'),
|
||
'payslip_count': 12,
|
||
'active_certs': 3,
|
||
'expiring_certs': 1, # expires within 30 days
|
||
'expired_certs': 0,
|
||
'active_warnings_count': 1, # warnings issued in last 12 months
|
||
}
|
||
```
|
||
|
||
Aggregation approach (efficient — one query per aggregate, not per worker):
|
||
```python
|
||
qs = Worker.objects.annotate(
|
||
days_worked=Count('work_logs__date', distinct=True),
|
||
first_payslip_date=Min('payroll_records__date'),
|
||
last_payslip_date=Max('payroll_records__date'),
|
||
total_paid_lifetime=Sum('payroll_records__amount_paid'),
|
||
payslip_count=Count('payroll_records', distinct=True),
|
||
)
|
||
# then a separate prefetch for projects/teams
|
||
```
|
||
|
||
---
|
||
|
||
## Forms (new)
|
||
|
||
```python
|
||
# core/forms.py
|
||
|
||
class WorkerForm(forms.ModelForm):
|
||
"""Main worker edit form — covers the flat fields on Worker."""
|
||
class Meta:
|
||
model = Worker
|
||
fields = [
|
||
'name', 'id_number', 'phone_number', 'monthly_salary',
|
||
'employment_date', 'active', 'notes',
|
||
'shoe_size', 'overall_top_size', 'pants_size', 'tshirt_size',
|
||
'photo', 'id_document',
|
||
'has_drivers_license', 'drivers_license',
|
||
]
|
||
widgets = {
|
||
'employment_date': forms.DateInput(attrs={'type': 'date'}),
|
||
'notes': forms.Textarea(attrs={'rows': 3}),
|
||
}
|
||
|
||
|
||
WorkerCertificateFormSet = inlineformset_factory(
|
||
Worker, WorkerCertificate,
|
||
fields=['cert_type', 'document', 'issued_date', 'valid_until', 'notes'],
|
||
extra=0, # no blank rows by default
|
||
can_delete=True,
|
||
widgets={
|
||
'issued_date': forms.DateInput(attrs={'type': 'date'}),
|
||
'valid_until': forms.DateInput(attrs={'type': 'date'}),
|
||
'notes': forms.Textarea(attrs={'rows': 2}),
|
||
},
|
||
)
|
||
|
||
WorkerWarningFormSet = inlineformset_factory(
|
||
Worker, WorkerWarning,
|
||
fields=['date', 'severity', 'reason', 'description', 'document'],
|
||
extra=0,
|
||
can_delete=True,
|
||
widgets={
|
||
'date': forms.DateInput(attrs={'type': 'date'}),
|
||
'description': forms.Textarea(attrs={'rows': 3}),
|
||
},
|
||
)
|
||
```
|
||
|
||
In the edit view, `request.POST`/`request.FILES` flow through all three (form + two formsets); all must be valid before saving.
|
||
|
||
---
|
||
|
||
## Templates (new)
|
||
|
||
### `core/workers/list.html`
|
||
- Search box (name/ID) + status filter dropdown
|
||
- Table columns: Name, ID, Phone, Salary, Days Worked, Active, Actions (View / Edit / Toggle)
|
||
- Buttons: "Add Worker", "Batch Report", "Export CSV"
|
||
- Styled with existing stat-card / resource-row patterns from index.html
|
||
|
||
### `core/workers/edit.html`
|
||
- Section-based layout (no tabs — long-form scroll for easier visual review):
|
||
1. **Personal & Pay** — name, id_number, phone, salary, employment_date, active, notes
|
||
2. **PPE Sizing** — shoe, overall top, pants, t-shirt
|
||
3. **Documents** — photo, id_document
|
||
4. **Driver's License** — has_drivers_license, drivers_license file
|
||
5. **Certifications** (formset with + add button, × delete)
|
||
6. **Warnings & Disciplinary** (formset with + add button, × delete)
|
||
- Client-side JS: "Add Certification" / "Add Warning" buttons clone a hidden blank formset row and bump the TOTAL_FORMS counter (standard Django formset pattern)
|
||
- Submit button at the bottom; Cancel goes back to `worker_detail`
|
||
|
||
### `core/workers/detail.html`
|
||
- Header: worker photo, name, ID, active badge
|
||
- Tabs:
|
||
1. **Profile** — personal, PPE, license info
|
||
2. **Certifications** — list with colored badges: green (valid > 30 days), amber (expires within 30), red (expired)
|
||
3. **Warnings** — chronological list
|
||
4. **History** — projects worked, teams, days, last 10 payslips
|
||
- "Edit" button links to `worker_edit`
|
||
|
||
### `core/workers/batch_report.html`
|
||
- Report header + filter bar (status / project / team)
|
||
- Table with columns:
|
||
- Name | ID | Salary | Active | Days Worked | Projects | Teams | First Payslip | Last Payslip | Total Paid | Certs (n/m) | Warnings
|
||
- "Export CSV" + "Download PDF" buttons at top-right
|
||
- Row click → `worker_detail`
|
||
|
||
### `core/pdf/workers_report_pdf.html`
|
||
- Print-optimized A4 layout using WeasyPrint
|
||
- Header: "FoxFitt Construction — Worker Roster Report"
|
||
- Filter summary subhead
|
||
- Table (narrower columns, landscape orientation may be needed for many fields)
|
||
- Uses the same amber accent and typography as `report_pdf.html`
|
||
|
||
---
|
||
|
||
## Navigation
|
||
|
||
`base.html` desktop topbar: add a "Workers" link after "Receipts" and before "Admin" (admin-only):
|
||
|
||
```html
|
||
{% if user.is_staff %}
|
||
<a href="{% url 'worker_list' %}" class="topbar-nav__link {% if 'worker' in request.resolver_match.url_name %}active{% endif %}">
|
||
<i class="fas fa-hard-hat"></i><span>Workers</span>
|
||
</a>
|
||
{% endif %}
|
||
```
|
||
|
||
Also add matching entries to the mobile menu (the `.mobile-menu__nav` block) and the bottom tab bar (if room).
|
||
|
||
---
|
||
|
||
## Django Admin Enhancements
|
||
|
||
Register the new models:
|
||
|
||
```python
|
||
# core/admin.py
|
||
|
||
class WorkerCertificateInline(admin.TabularInline):
|
||
model = WorkerCertificate
|
||
extra = 0
|
||
|
||
class WorkerWarningInline(admin.TabularInline):
|
||
model = WorkerWarning
|
||
extra = 0
|
||
readonly_fields = ['created_at']
|
||
|
||
@admin.register(WorkerCertificate)
|
||
class WorkerCertificateAdmin(admin.ModelAdmin):
|
||
list_display = ('worker', 'cert_type', 'valid_until', 'is_expired')
|
||
list_filter = ('cert_type',)
|
||
search_fields = ('worker__name',)
|
||
|
||
@admin.register(WorkerWarning)
|
||
class WorkerWarningAdmin(admin.ModelAdmin):
|
||
list_display = ('worker', 'date', 'severity', 'reason')
|
||
list_filter = ('severity',)
|
||
search_fields = ('worker__name', 'reason')
|
||
```
|
||
|
||
Then update `WorkerAdmin` to include the inlines:
|
||
|
||
```python
|
||
class WorkerAdmin(admin.ModelAdmin):
|
||
# ...existing config...
|
||
inlines = [WorkerCertificateInline, WorkerWarningInline]
|
||
```
|
||
|
||
This means the Django admin ALSO gets the new sections — the in-app edit page is a better UX, but admin remains fully functional.
|
||
|
||
---
|
||
|
||
## Task-by-Task Execution Plan
|
||
|
||
Each task is 5–15 minutes. Execute in order.
|
||
|
||
### Task 1: Add the two new models + migration
|
||
|
||
**Files:**
|
||
- Modify: `core/models.py` (append `WorkerCertificate`, `WorkerWarning` classes at end of file)
|
||
- Create: `core/migrations/0009_worker_certificates_warnings.py` (auto-generated)
|
||
|
||
**Steps:**
|
||
1. Add the two model classes (code above)
|
||
2. `USE_SQLITE=true python manage.py makemigrations core`
|
||
3. `USE_SQLITE=true python manage.py migrate`
|
||
4. Verify: `python manage.py check` → no issues
|
||
5. Commit
|
||
|
||
### Task 2: Register models in Django admin
|
||
|
||
**Files:**
|
||
- Modify: `core/admin.py`
|
||
|
||
**Steps:**
|
||
1. Add `WorkerCertificateInline`, `WorkerWarningInline`, `WorkerCertificateAdmin`, `WorkerWarningAdmin` classes
|
||
2. Add `inlines = [...]` to `WorkerAdmin`
|
||
3. Verify in browser: `/admin/core/worker/<id>/change/` shows certs + warnings sections
|
||
4. Commit
|
||
|
||
### Task 3: Add WorkerForm and formsets
|
||
|
||
**Files:**
|
||
- Modify: `core/forms.py`
|
||
|
||
**Steps:**
|
||
1. Add `WorkerForm`, `WorkerCertificateFormSet`, `WorkerWarningFormSet`
|
||
2. Verify: `python manage.py shell` → import forms → no errors
|
||
3. Commit
|
||
|
||
### Task 4: Add worker_list view and template
|
||
|
||
**Files:**
|
||
- Modify: `core/views.py` (+ `import` updates)
|
||
- Modify: `core/urls.py`
|
||
- Create: `core/templates/core/workers/list.html`
|
||
|
||
**Steps:**
|
||
1. Add URL route `workers/` → `views.worker_list`
|
||
2. Add view `worker_list(request)` with search + status filter
|
||
3. Create template with search bar, table, action buttons
|
||
4. Verify: visit `/workers/` as admin → list shows workers
|
||
5. Commit
|
||
|
||
### Task 5: Add worker_edit view + template (the big one)
|
||
|
||
**Files:**
|
||
- Modify: `core/views.py`
|
||
- Modify: `core/urls.py`
|
||
- Create: `core/templates/core/workers/edit.html`
|
||
|
||
**Steps:**
|
||
1. Add two URL routes: `workers/new/`, `workers/<id>/edit/`
|
||
2. Add view `worker_edit(request, worker_id=None)` handling both create and update
|
||
3. Create section-based template with formsets for certs + warnings
|
||
4. Add JS for "+ Add Certification" / "+ Add Warning" buttons (formset clone pattern)
|
||
5. Verify: add a worker, edit a worker, add cert, add warning → all persist
|
||
6. Commit
|
||
|
||
### Task 6: Add worker_detail view + template
|
||
|
||
**Files:**
|
||
- Modify: `core/views.py`
|
||
- Modify: `core/urls.py`
|
||
- Create: `core/templates/core/workers/detail.html`
|
||
|
||
**Steps:**
|
||
1. Add URL `workers/<id>/`
|
||
2. Add view with tab-context (profile, certs, warnings, history)
|
||
3. Template with Bootstrap tabs; cert badges styled by expiry
|
||
4. Link "Edit" button to edit view
|
||
5. Verify
|
||
6. Commit
|
||
|
||
### Task 7: Add batch report (HTML, CSV, PDF)
|
||
|
||
**Files:**
|
||
- Modify: `core/views.py` (add `_build_worker_report_context`, 3 views)
|
||
- Modify: `core/urls.py`
|
||
- Create: `core/templates/core/workers/batch_report.html`
|
||
- Create: `core/templates/core/pdf/workers_report_pdf.html`
|
||
|
||
**Steps:**
|
||
1. Write `_build_worker_report_context()` helper with the annotate/prefetch pattern
|
||
2. Add 3 URLs + 3 views
|
||
3. Create HTML template with filter bar + table
|
||
4. Create PDF template (derive from existing `report_pdf.html` structure — cover, section headings, ledger-style table)
|
||
5. Verify HTML, CSV, PDF all render correctly
|
||
6. Commit
|
||
|
||
### Task 8: Add nav links
|
||
|
||
**Files:**
|
||
- Modify: `core/templates/base.html`
|
||
|
||
**Steps:**
|
||
1. Add desktop nav link (admin-only)
|
||
2. Add mobile menu link
|
||
3. Verify on desktop and mobile layouts
|
||
4. Commit
|
||
|
||
### Task 9: Update CLAUDE.md
|
||
|
||
**Files:**
|
||
- Modify: `CLAUDE.md`
|
||
|
||
**Steps:**
|
||
1. Add `WorkerCertificate` and `WorkerWarning` to Key Models section
|
||
2. Add 7 new URL routes to URL Routes table
|
||
3. Add "Worker Management" subsection under Development Workflow
|
||
4. Commit
|
||
|
||
### Task 10: Verification pass
|
||
|
||
- Visit every new page; click every button; upload a test file to each upload field
|
||
- Try edge cases: blank forms, duplicate cert_type for same worker (should fail unique constraint), expired certs
|
||
- Regenerate PDF; open to verify layout
|
||
- Run `python manage.py check`
|
||
- Smoke-test existing features (payroll report, payment, receipt) still work
|
||
|
||
---
|
||
|
||
## Open Questions for User
|
||
|
||
The following decisions need your input before I start executing. Defaults in brackets are what I'll use if you don't answer.
|
||
|
||
### Q1: Certification types list
|
||
I'm proposing these 5 types:
|
||
- Skills Certificate
|
||
- PDP (Professional Driving Permit)
|
||
- First Aid
|
||
- Medical
|
||
- Work at Height
|
||
|
||
**Any additions, removals, or renames?** [Default: use exactly these 5]
|
||
|
||
### Q2: Warning severity levels
|
||
I'm proposing: Verbal → Written → Final. **Accept, or add a fourth (e.g. "Informal"), or use different names?** [Default: Verbal/Written/Final]
|
||
|
||
### Q3: Navigation placement
|
||
**Where does the "Workers" link go?**
|
||
- (a) Top desktop nav, admin-only, after Receipts — prominent, 1-click access
|
||
- (b) Only accessible from a button on the Dashboard — less cluttered nav
|
||
- (c) Inside the Admin dropdown / submenu
|
||
|
||
[Default: (a) — matches how Payroll is currently linked]
|
||
|
||
### Q4: Worker list — default sort?
|
||
- (a) Alphabetical by name
|
||
- (b) By employment date (newest first)
|
||
- (c) By active status, then name
|
||
|
||
[Default: (a) alphabetical]
|
||
|
||
### Q5: Coexist with Django admin?
|
||
The new worker list/edit pages would be more user-friendly, but Django admin at `/admin/core/worker/` remains fully functional.
|
||
**Keep Django admin working as a fallback for power-user edits?** [Default: yes — keep both]
|
||
|
||
### Q6: Cert expiry alerts on dashboard
|
||
Would you like the Dashboard to show a stat card like "3 certs expiring in 30 days"?
|
||
- (a) Yes, show it — helpful operationally
|
||
- (b) No, keep dashboard unchanged for now
|
||
- (c) Show, but only if count > 0 (hide the card when everything's fine)
|
||
|
||
[Default: (c) conditional display]
|
||
|
||
### Q7: File upload size limit
|
||
Certificates and warnings support file uploads. Currently no limit. Should I add a max size to prevent someone accidentally uploading a 50MB scan?
|
||
- (a) No limit
|
||
- (b) 5 MB max
|
||
- (c) 10 MB max
|
||
|
||
[Default: (b) 5 MB]
|
||
|
||
### Q8: Batch report columns
|
||
I'm proposing this column set:
|
||
Name | ID | Salary | Active | Days Worked | Projects | Teams | First Payslip | Last Payslip | Total Paid | Certs (active/total) | Warnings
|
||
|
||
**Anything to add or remove?** Common additions could be: employment_date, phone, has_drivers_license, last-active-date.
|
||
|
||
[Default: use the list above]
|
||
|
||
### Q9: Scope bailout
|
||
If I discover during execution that any task is significantly more complex than the estimate (e.g. Task 5 turns out to need 2 hours instead of 30 minutes), should I:
|
||
- (a) Keep going, add a new task, push complete
|
||
- (b) Stop, flag the issue, let you decide
|
||
- (c) Deliver a smaller version (e.g. certs-only, no warnings) and flag warnings as follow-up
|
||
|
||
[Default: (b) stop and flag]
|
||
|
||
---
|
||
|
||
## Verification (end-to-end)
|
||
|
||
Run after all tasks complete:
|
||
|
||
```
|
||
USE_SQLITE=true python manage.py check
|
||
USE_SQLITE=true python manage.py migrate --plan # confirm no pending migrations
|
||
```
|
||
|
||
Browser smoke tests (as admin):
|
||
1. Visit `/workers/` — list renders, search works
|
||
2. Click "Add Worker" — blank form loads
|
||
3. Fill name/ID/salary, click Save → redirected to detail view
|
||
4. Click "Edit" on detail — form pre-filled
|
||
5. Click "+ Add Certification" → blank cert row appears
|
||
6. Fill cert (Medical, valid_until=next month), upload a test PDF, Save
|
||
7. Verify cert appears on detail page with green "valid" badge
|
||
8. Click "+ Add Warning", fill, Save — verify it appears on Warnings tab
|
||
9. Visit `/workers/report/` — table shows all workers with aggregates
|
||
10. Click "Export CSV" — downloads, opens in Excel/LibreOffice cleanly
|
||
11. Click "Download PDF" — renders with correct layout
|
||
12. Visit `/admin/core/worker/<id>/change/` — Django admin still works, inlines show
|
||
13. As a non-admin user, visit `/workers/` — should get 403
|
||
|
||
## Rollback
|
||
|
||
The change touches two new models with ForeignKey to Worker. If we need to undo:
|
||
1. `git reset --hard` to the last commit before this plan started
|
||
2. `python manage.py migrate core 0008` (the pre-existing migration) — drops the two new tables
|
||
3. Branches stay isolated until merged; worst case the whole thing sits on a local branch forever
|
||
|
||
---
|
||
|
||
## Estimated Timeline
|
||
|
||
- Task 1 (models + migration): 10 min
|
||
- Task 2 (admin): 5 min
|
||
- Task 3 (forms): 10 min
|
||
- Task 4 (list view): 15 min
|
||
- Task 5 (edit view + template): 40 min — biggest task; has formset JS
|
||
- Task 6 (detail view): 20 min
|
||
- Task 7 (batch report): 30 min
|
||
- Task 8 (nav): 5 min
|
||
- Task 9 (CLAUDE.md): 5 min
|
||
- Task 10 (verification): 15 min
|
||
|
||
**Total: ~2.5 hours of supervised execution**, or ~90 minutes if I can auto-execute with good test coverage.
|