# FoxFitt LabourPay v5 ## Coding Style - Always add clear section header comments using the format: # === SECTION NAME === - Add plain English comments explaining what complex logic does - The project owner is not a programmer — comments should be understandable by a non-technical person - When creating or editing code, maintain the existing comment structure ## Project Overview Django payroll management system for FoxFitt Construction, a civil works contractor specializing in solar farm foundation installations. Manages field worker attendance, payroll processing, employee loans, and business expenses for solar farm projects. This is v5 — a fresh export from Flatlogic/AppWizzy, rebuilt from the v2 codebase with simplified models and cleaner structure. ## Tech Stack - Django 5.2.7, Python 3.13, MySQL (production on Flatlogic Cloud Run) / SQLite (local dev) - Bootstrap 5.3.3 (CDN), Font Awesome 6.5.1 (CDN), Google Fonts (Inter + Poppins) - WeasyPrint for PDF generation (payroll report, payslips, receipts) — migrated from xhtml2pdf; browser-grade HTML/CSS rendering with flexbox, grid, @font-face, shadows, and proper CSS cascade - Gmail SMTP for automated document delivery - Hosted on Flatlogic/AppWizzy platform (Apache on Debian, e2-micro VM) ## Project Structure ``` config/ — Django project settings, URLs, WSGI/ASGI core/ — Single main app: ALL business logic, models, views, forms, templates context_processors.py — Injects deployment_timestamp (cache-busting), Flatlogic branding vars forms.py — AttendanceLogForm, PayrollAdjustmentForm, ExpenseReceiptForm + formset models.py — All 10 database models utils.py — render_to_pdf() helper (lazy WeasyPrint import + Windows GTK3 DLL registration) views.py — All view functions (~52 functions, ~3,800 lines) — dashboard, attendance, payroll, reports, worker/team/project CRUD forms.py — All form classes + validators (WorkerForm, TeamForm, ProjectForm, AttendanceLogForm, PayrollAdjustmentForm, ExpenseReceiptForm, WorkerCertificate/WarningFormSet, 5MB file validator) admin.py — Django admin registrations for all core models + WorkerCertificate/Warning inlines on Worker templatetags/ — format_tags.py (money filter for ZAR formatting) management/commands/ — setup_groups, setup_test_data, import_production_data templates/ base.html — App shell (topbar + mobile menu + bottom tab bar) core/ — Page templates: index, attendance_log, work_history, payroll_dashboard, report, create_receipt, payslip, login, _report_config_modal (partial) core/workers/ — 4 templates: list, detail, edit, batch_report core/teams/ — 4 templates: list, detail, edit, batch_report core/projects/— 4 templates: list, detail, edit, batch_report core/pdf/ — 4 PDF templates: report_pdf, payslip_pdf, receipt_pdf, workers_report_pdf core/email/ — 2 HTML email templates admin/ — base_site.html override (adds admin CSS tweaks, e.g. taller M2M pickers) ai/ — Flatlogic AI proxy client (not used in app logic) static/css/ — custom.css (CSS variables, component styles, tooltip overrides) 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, 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) - **PayrollAdjustment** — bonuses, deductions, overtime, loans; linked to project (FK, optional), worker, and optionally to a Loan or WorkLog - **Loan** — worker loans AND advances with principal, remaining_balance, `loan_type` ('loan' or 'advance') - **ExpenseReceipt / ExpenseLineItem** — business expense records with VAT handling - **WorkerCertificate** — per-worker certifications (Skills, PDP, First Aid, Medical, Work at Height) with `valid_until` expiry and optional document upload. Unique per (worker, cert_type). Has `is_expired` and `expires_soon` (≤30 days) properties. - **WorkerWarning** — disciplinary records per worker with severity (verbal/written/final), reason, optional document. Ordered -date. ## Key Business Rules - All business logic lives in the `core/` app — do not create additional Django apps - Workers have a `daily_rate` property: `monthly_salary / Decimal('20.00')` - Admin = `is_staff` or `is_superuser` (checked via `is_admin(user)` helper in views.py) - Supervisors see only their assigned projects, teams, and workers - Admins have full access to payroll, adjustments, and resource management - WorkLog is the central attendance record — links workers to projects on specific dates - Attendance logging includes conflict detection (prevents double-logging same worker+date+project) - Loans have automated repayment deductions during payroll processing - Cascading deletes use SET_NULL for supervisors/teams to preserve historical data ## Payroll Constants Defined at top of views.py — used in dashboard calculations and payment processing: - **ADDITIVE_TYPES** = `['Bonus', 'Overtime', 'New Loan', 'Advance Payment']` — increase worker's net pay - **DEDUCTIVE_TYPES** = `['Deduction', 'Loan Repayment', 'Advance Repayment']` — decrease net pay ## PayrollAdjustment Type Handling - **Bonus / Deduction** — standalone, require a linked Project - **New Loan** — creates a `Loan` record (`loan_type='loan'`); has a "Pay Immediately" checkbox (checked by default) that auto-processes the loan (creates PayrollRecord, sends payslip to Spark, marks as paid). When unchecked, the loan sits in Pending Payments for the next pay cycle. Editing syncs loan amount/balance/reason; deleting cascades to Loan + unpaid repayments - **Advance Payment** — **auto-processed immediately** (never sits in Pending): creates `Loan` (`loan_type='advance'`), creates PayrollRecord, sends payslip email, and auto-creates an "Advance Repayment" for the next salary. Requires a Project (for cost tracking) and at least one unpaid work log (otherwise use New Loan). - **Overtime** — links to `WorkLog` via `adj.work_log` FK; managed by `price_overtime()` view - **Loan Repayment** — links to `Loan` (loan_type='loan') via `adj.loan` FK; loan balance changes during payment processing - **Advance Repayment** — auto-created when an advance is paid; deducts from advance balance during `process_payment()`. If partial repayment, remaining balance converts advance to regular loan (`loan_type` changes from 'advance' to 'loan'). Editable by admin (amount can be reduced before payday). ## Outstanding Payments Logic (Dashboard) The dashboard's outstanding amount uses **per-worker** checking, not per-log: - For each WorkLog, get the set of `paid_worker_ids` from linked PayrollRecords - A worker is "unpaid for this log" only if their ID is NOT in that set - This correctly handles partially-paid logs (e.g., one worker paid, another not) - Unpaid adjustments: additive types increase outstanding, deductive types decrease it ## Commands ```bash # Local development (SQLite) set USE_SQLITE=true && python manage.py runserver 0.0.0.0:8000 # Or use run_dev.bat which sets the env var python manage.py migrate # Apply database migrations python manage.py setup_groups # Create Admin + Work Logger permission groups python manage.py setup_test_data # Populate sample workers, projects, logs python manage.py import_production_data # Import real production data (14 workers) python manage.py collectstatic # Collect static files for production python manage.py check # System check ``` ## Development Workflow - Active development branch: `ai-dev` (PR target: `master`) - Local dev uses SQLite: set `USE_SQLITE=true` environment variable - Preview server config: `.claude/launch.json` → runs `run_dev.bat` - Admin check in views: `is_admin(request.user)` helper (top of views.py) - "Unpaid" adjustment = `payroll_record__isnull=True` (no linked PayrollRecord) - POST-only mutation views pattern: check `request.method != 'POST'` → redirect - Template UI: Bootstrap 5 modals with dynamic JS, data-* attributes on clickable elements - Atomic transactions: `process_payment()` uses `select_for_update()` to prevent duplicate payments - Payslip Preview modal ("Worker Payment Hub"): shows earnings, adjustments, net pay, **plus** active loans/advances with inline repayment forms. Uses `refreshPreview()` JS function that re-fetches after AJAX repayment submission. Repayment POSTs to `add_repayment_ajax` which creates a PayrollAdjustment (balance deduction only happens during `process_payment()`) - Advance Payment auto-processing: `add_adjustment` immediately creates PayrollRecord + sends payslip when an advance is created. Also auto-creates an "Advance Repayment" adjustment for the next salary cycle. Uses `_send_payslip_email()` helper (shared with `process_payment`) - Advance-to-loan conversion: When an Advance Repayment is only partially paid, `process_payment` changes the Loan's `loan_type` from 'advance' to 'loan' so the remainder is tracked as a regular loan - Split Payslip: Preview modal has checkboxes on work logs and adjustments (all checked by default). `process_payment()` accepts optional `selected_log_ids` / `selected_adj_ids` POST params to pay only selected items. Falls back to "pay all" if no IDs provided (backward compatible with the quick Pay button). - Team Pay Schedules: Teams have optional `pay_frequency` + `pay_start_date` fields. `get_pay_period(team)` calculates current period boundaries by stepping forward from the anchor date. The preview modal shows a "Split at Pay Date" button that auto-unchecks items after the `cutoff_date` (end of last completed period — includes ALL overdue work, not just one period). `get_worker_active_team(worker)` returns the worker's first active team. - Pay period calculation: `pay_start_date` is an anchor (never needs updating). Weekly=7 days, Fortnightly=14 days, Monthly=calendar month stepping. Uses `calendar.monthrange()` for month-length edge cases (no `dateutil` dependency). - Batch Pay: "Batch Pay" button on payroll dashboard opens a modal with two radio modes — **"Until Last Paydate"** (default, splits at last completed pay period per team schedule) and **"Pay All"** (includes all unpaid items regardless of date). Preview fetches from `batch_pay_preview` with `?mode=schedule|all`. Workers without team pay schedules are skipped in schedule mode but included in Pay All mode. `batch_pay` POST endpoint processes each worker in independent atomic transactions; emails are sent after all payments complete. Uses `_process_single_payment()` shared helper (same logic as individual `process_payment`). Modal includes team filter dropdown and 3-option loan filter (All / With loans only / Without loans). - Pending Payments Table: Shows overdue badges (red) for workers with unpaid work from completed pay periods, and loan badges (yellow) for workers with active loans/advances. Filter bar has: team dropdown, "Overdue only" checkbox, and loan dropdown (All Workers / With loans only / Without loans). Overdue detection uses `get_pay_period()` cutoff logic. - Quick Adjust Button: Each pending payments row has an "Adjust" button (slider icon) that opens the Add Adjustment modal with that worker pre-checked and their most recent project pre-selected. The header "Add Adjustment" button resets the modal to a clean state. Uses `_quickAdjustOpen` flag to distinguish between the two open paths. - Worker Lookup Modal: Clicking any worker name on the payroll dashboard (or using the "Worker Lookup" button) opens a modal with a comprehensive report card — amount payable, outstanding loans, paid this month/year, loans this year, recent activity (last payslip, loan, repayment, advance), active loans table, current project + days on project, PPE sizing, drivers license, and notes. Uses `worker_lookup_ajax` AJAX endpoint. Worker dropdown in modal allows switching workers without closing. - Team & Project Management UIs: Friendlier alternatives to `/admin/core/team/` and `/admin/core/project/`. Reachable via the "Resources" dropdown in the topbar (admin only). **Team pages**: `/teams/` (list + search/filter), `/teams//` (detail with Profile/Pay Schedule/Workers/History tabs — Pay Schedule tab uses the existing `get_pay_period()` helper to show current + next 2 periods), `/teams//edit/` (single-page form for name, supervisor, pay schedule, and workers M2M). **Project pages**: `/projects/`, `/projects//` (tabs: Profile/Supervisors/Teams/Workers/History), `/projects//edit/` (form for name, description, dates, supervisors M2M). Uses `TeamForm` and `ProjectForm` from `core/forms.py` (both simple ModelForms, no inline formsets). Batch reports at `/teams/report/` and `/projects/report/` with CSV exports; PDF exports deferred as a follow-up. Dashboard "Manage Resources" card now has "Manage All Workers/Projects/Teams" footer links on each tab. Django admin remains fully functional as a fallback. - Worker Management UI: A friendlier alternative to `/admin/core/worker/`. Reachable via the "Resources" topbar dropdown → Workers (admin-only). Pages: `/workers/` (list with search + status filter), `/workers//` (detail with Profile/Certifications/Warnings/History tabs), `/workers//edit/` or `/workers/new/` (single-page form with sections for Personal & Pay, PPE, Documents, Driver's License, plus inline formsets for certifications and warnings). Uses `WorkerForm`, `WorkerCertificateFormSet`, `WorkerWarningFormSet` from `core/forms.py`. The "+ Add Certification" / "+ Add Warning" buttons clone a `