- Fix outstanding payments: check per-worker (not per-log) to handle partially-paid WorkLogs - Fix adjustment math: deductions now subtract from outstanding instead of adding - Fix conflict resolution: use explicit worker ID list (QueryDict.getlist) instead of broken form.data.workers iteration - Add missing migration 0003 for Project start_date/end_date fields - Add CLAUDE.md project documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
10 KiB
10 KiB
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)
- xhtml2pdf for PDF generation (payslips, receipts)
- 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 xhtml2pdf import)
views.py — All 17 view functions (~1700 lines)
management/commands/ — setup_groups, setup_test_data, import_production_data
templates/ — base.html + 7 page templates + 2 email + 2 PDF + login
ai/ — Flatlogic AI proxy client (not used in app logic)
static/css/ — custom.css (CSS variables, component styles)
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_rateproperty (monthly_salary / 20), photo, ID doc - Team — groups of workers under a supervisor
- 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 advances with principal and remaining_balance tracking
- ExpenseReceipt / ExpenseLineItem — business expense records with VAT handling
Key Business Rules
- All business logic lives in the
core/app — do not create additional Django apps - Workers have a
daily_rateproperty:monthly_salary / Decimal('20.00') - Admin =
is_stafforis_superuser(checked viais_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']— increase worker's net pay - DEDUCTIVE_TYPES =
['Deduction', 'Loan Repayment', 'Advance Payment']— decrease net pay
PayrollAdjustment Type Handling
- Bonus / Deduction — standalone, require a linked Project
- New Loan — creates a
Loanrecord; editing syncs loan amount/balance/reason; deleting cascades to Loan + unpaid repayments - Overtime — links to
WorkLogviaadj.work_logFK; managed byprice_overtime()view - Loan Repayment — links to
Loanviaadj.loanFK; loan balance changes during payment processing - Advance Payment — requires a linked Project; reduces net pay
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_idsfrom 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
# 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=trueenvironment variable - Preview server config:
.claude/launch.json→ runsrun_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()usesselect_for_update()to prevent duplicate payments
URL Routes
| Path | View | Purpose |
|---|---|---|
/ |
index |
Dashboard (admin stats / supervisor work view) |
/attendance/log/ |
attendance_log |
Log daily work with date range support |
/history/ |
work_history |
Work logs table with filters |
/history/export/ |
export_work_log_csv |
Download filtered logs as CSV |
/workers/export/ |
export_workers_csv |
Admin: export all workers to CSV |
/toggle/<model>/<id>/ |
toggle_active |
Admin: AJAX toggle active status |
/payroll/ |
payroll_dashboard |
Admin: pending payments, loans, charts |
/payroll/pay/<worker_id>/ |
process_payment |
Admin: process payment (atomic) |
/payroll/price-overtime/ |
price_overtime |
Admin: AJAX price unpriced OT entries |
/payroll/adjustment/add/ |
add_adjustment |
Admin: create adjustment |
/payroll/adjustment/<id>/edit/ |
edit_adjustment |
Admin: edit unpaid adjustment |
/payroll/adjustment/<id>/delete/ |
delete_adjustment |
Admin: delete unpaid adjustment |
/payroll/preview/<worker_id>/ |
preview_payslip |
Admin: AJAX JSON payslip preview |
/payroll/payslip/<pk>/ |
payslip_detail |
Admin: view completed payslip |
/receipts/create/ |
create_receipt |
Staff: expense receipt with line items |
/import-data/ |
import_data |
Setup: run import command from browser |
Frontend Design Conventions
- CSS variables in
static/css/custom.css:root— always usevar(--name):--primary-dark: #0f172a(navbar),--primary: #1e293b(headers),--accent: #10b981(brand green)--text-main: #334155,--text-secondary: #64748b,--background: #f1f5f9
- Icons: Font Awesome 6 only (
fas fa-*). Do NOT use Bootstrap Icons (bi bi-*) - CTA buttons:
btn-accent(green) for primary actions.btn-primary(dark slate) for modal Save/Submit - Page titles:
{% block title %}Page Name | Fox Fitt{% endblock %} - Fonts: Inter (body) + Poppins (headings) loaded in base.html via Google Fonts CDN
- Cards: Borderless with
box-shadow: 0 4px 6px rgba(0,0,0,0.1). Stat cards usebackdrop-filter: blur - Email/PDF payslips: Worker name dominant — do NOT add prominent FoxFitt branding
Permission Groups
Created by setup_groups management command:
- Admin — full CRUD on all core models
- Work Logger — add/change/view WorkLog; view-only on Project/Worker/Team
Authentication
- Django's built-in auth (
django.contrib.auth) - Login:
/accounts/login/→ redirects to/(home) - Logout: POST to
/accounts/logout/→ redirects to login - All views use
@login_requiredexceptimport_data() - No PIN auth in v5 (simplified from v2)
Environment Variables
DJANGO_SECRET_KEY, DJANGO_DEBUG, HOST_FQDN, CSRF_TRUSTED_ORIGIN
DB_NAME, DB_USER, DB_PASS, DB_HOST (default: 127.0.0.1), DB_PORT (default: 3306)
USE_SQLITE # "true" → use SQLite instead of MySQL
EMAIL_HOST_USER, EMAIL_HOST_PASSWORD (Gmail App Password — 16 chars)
DEFAULT_FROM_EMAIL, SPARK_RECEIPT_EMAIL
PROJECT_DESCRIPTION, PROJECT_IMAGE_URL # Flatlogic branding
Flatlogic/AppWizzy Deployment
- Branches:
ai-dev= development (Flatlogic AI + Claude Code).master= deploy target. - Workflow: Push to
ai-dev→ Flatlogic auto-detects → "Pull Latest" → app rebuilds (~5 min) - Deploy from Git (Settings): Full rebuild from
master— use for production - Migrations: Run automatically during Flatlogic rebuild
- Never edit
ai-devdirectly on GitHub — Flatlogic pushes overwrite it - Gemini gotcha: Flatlogic's Gemini AI reads
__pycache__/*.pycand gets confused. Tell it: "Do NOT read .pyc files. Only work with .py source files." - Sequential workflow: Don't edit in Flatlogic and Claude Code at the same time
Security Notes
- Production:
SESSION_COOKIE_SECURE=True,CSRF_COOKIE_SECURE=True,SameSite=None(cross-origin for Flatlogic iframe) - Local dev: Secure cookies disabled when
USE_SQLITE=true - X-Frame-Options middleware disabled (required for Flatlogic preview)
- Email App Password should be in env var, not hardcoded in settings.py
Important Context
- The owner (Konrad) is not a developer — explain changes clearly and avoid unnecessary complexity
- This system handles real payroll for field workers — accuracy is critical
render_to_pdf()uses lazy import of xhtml2pdf to prevent app crash if library missing- Django admin is available at
/admin/with full model registration and search/filter