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>
135 lines
5.2 KiB
Python
135 lines
5.2 KiB
Python
from django.contrib import admin
|
|
from .models import (
|
|
UserProfile, Project, Worker, Team, WorkLog,
|
|
PayrollRecord, Loan, PayrollAdjustment,
|
|
ExpenseReceipt, ExpenseLineItem,
|
|
WorkerCertificate, WorkerWarning,
|
|
)
|
|
|
|
@admin.register(UserProfile)
|
|
class UserProfileAdmin(admin.ModelAdmin):
|
|
list_display = ('user',)
|
|
search_fields = ('user__username', 'user__first_name', 'user__last_name')
|
|
|
|
@admin.register(Project)
|
|
class ProjectAdmin(admin.ModelAdmin):
|
|
list_display = ('name', 'active')
|
|
list_filter = ('active',)
|
|
search_fields = ('name', 'description')
|
|
filter_horizontal = ('supervisors',)
|
|
|
|
# === INLINE ADMINS FOR WORKER ===
|
|
# Let admins manage a worker's certifications and warnings directly
|
|
# from the Worker change page, without navigating to a separate screen.
|
|
class WorkerCertificateInline(admin.TabularInline):
|
|
model = WorkerCertificate
|
|
extra = 0 # no blank rows by default — admin clicks "Add another" to create
|
|
readonly_fields = ('created_at',)
|
|
fields = ('cert_type', 'document', 'issued_date', 'valid_until', 'notes', 'created_at')
|
|
|
|
|
|
class WorkerWarningInline(admin.TabularInline):
|
|
model = WorkerWarning
|
|
extra = 0
|
|
readonly_fields = ('created_at',)
|
|
fields = ('date', 'severity', 'reason', 'description', 'issued_by', 'document', 'created_at')
|
|
|
|
|
|
@admin.register(Worker)
|
|
class WorkerAdmin(admin.ModelAdmin):
|
|
list_display = ('name', 'id_number', 'monthly_salary', 'active')
|
|
list_filter = ('active', 'has_drivers_license')
|
|
search_fields = ('name', 'id_number', 'phone_number')
|
|
|
|
# Inline sections for certs + warnings appear below the main Worker form
|
|
inlines = [WorkerCertificateInline, WorkerWarningInline]
|
|
|
|
# === FIELDSETS ===
|
|
# Organise the worker edit form into clear sections.
|
|
# Banking & Tax fields (UIF, Bank, Acc No.) live inside Personal Info
|
|
# per product requirement — help_text strings render as hints under
|
|
# each field in admin (and as tooltips on the friendly edit page).
|
|
fieldsets = (
|
|
('Personal Info', {
|
|
'fields': ('name', 'id_number', 'phone_number', 'monthly_salary',
|
|
'tax_number', 'uif_number',
|
|
'bank_name', 'bank_account_number',
|
|
'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', 'drivers_license_code'),
|
|
}),
|
|
)
|
|
|
|
|
|
# === STANDALONE ADMINS FOR CERTS + WARNINGS ===
|
|
# Separate pages for bulk operations across workers — "show me all
|
|
# certs expiring this month" or "show me all final warnings".
|
|
@admin.register(WorkerCertificate)
|
|
class WorkerCertificateAdmin(admin.ModelAdmin):
|
|
list_display = ('worker', 'cert_type', 'issued_date', 'valid_until', 'is_expired')
|
|
list_filter = ('cert_type',)
|
|
search_fields = ('worker__name', 'worker__id_number')
|
|
date_hierarchy = 'valid_until'
|
|
|
|
|
|
@admin.register(WorkerWarning)
|
|
class WorkerWarningAdmin(admin.ModelAdmin):
|
|
list_display = ('worker', 'date', 'severity', 'reason', 'issued_by')
|
|
list_filter = ('severity',)
|
|
search_fields = ('worker__name', 'reason', 'description')
|
|
date_hierarchy = 'date'
|
|
|
|
@admin.register(Team)
|
|
class TeamAdmin(admin.ModelAdmin):
|
|
list_display = ('name', 'supervisor', 'pay_frequency', 'pay_start_date', 'active')
|
|
list_editable = ('pay_frequency', 'pay_start_date')
|
|
list_filter = ('active', 'supervisor', 'pay_frequency')
|
|
search_fields = ('name',)
|
|
filter_horizontal = ('workers',)
|
|
|
|
@admin.register(WorkLog)
|
|
class WorkLogAdmin(admin.ModelAdmin):
|
|
list_display = ('date', 'project', 'supervisor', 'overtime_amount')
|
|
list_filter = ('date', 'project', 'supervisor')
|
|
search_fields = ('project__name', 'notes')
|
|
filter_horizontal = ('workers', 'priced_workers')
|
|
|
|
@admin.register(PayrollRecord)
|
|
class PayrollRecordAdmin(admin.ModelAdmin):
|
|
list_display = ('worker', 'date', 'amount_paid')
|
|
list_filter = ('date', 'worker')
|
|
search_fields = ('worker__name',)
|
|
filter_horizontal = ('work_logs',)
|
|
|
|
@admin.register(Loan)
|
|
class LoanAdmin(admin.ModelAdmin):
|
|
list_display = ('worker', 'principal_amount', 'remaining_balance', 'date', 'active')
|
|
list_filter = ('active', 'date', 'worker')
|
|
search_fields = ('worker__name', 'reason')
|
|
|
|
@admin.register(PayrollAdjustment)
|
|
class PayrollAdjustmentAdmin(admin.ModelAdmin):
|
|
list_display = ('worker', 'type', 'amount', 'date')
|
|
list_filter = ('type', 'date', 'worker')
|
|
search_fields = ('worker__name', 'description')
|
|
|
|
class ExpenseLineItemInline(admin.TabularInline):
|
|
model = ExpenseLineItem
|
|
extra = 1
|
|
|
|
@admin.register(ExpenseReceipt)
|
|
class ExpenseReceiptAdmin(admin.ModelAdmin):
|
|
list_display = ('vendor_name', 'date', 'total_amount', 'user')
|
|
list_filter = ('date', 'payment_method', 'vat_type')
|
|
search_fields = ('vendor_name', 'description')
|
|
inlines = [ExpenseLineItemInline]
|
|
|
|
@admin.register(ExpenseLineItem)
|
|
class ExpenseLineItemAdmin(admin.ModelAdmin):
|
|
list_display = ('product_name', 'amount', 'receipt')
|
|
search_fields = ('product_name', 'receipt__vendor_name') |