38686-vm/CLAUDE.md
Konrad du Plessis c3bbffe9c0 Update CLAUDE.md with Pay Immediately loan documentation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 10:00:39 +02:00

15 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 19 view functions (~2000 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_rate property (monthly_salary / 20), photo, ID doc
  • 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

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 Paymentauto-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

# 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.

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 (includes active loans)
/payroll/repayment/<worker_id>/ add_repayment_ajax Admin: AJAX add loan/advance repayment from 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
/payroll/batch-pay/preview/ batch_pay_preview Admin: AJAX JSON batch pay preview (?mode=schedule|all)
/payroll/batch-pay/ batch_pay Admin: POST process batch payments for multiple workers
/run-migrate/ run_migrate Setup: run pending DB migrations from browser

Frontend Design Conventions

  • CSS variables in static/css/custom.css :root — always use var(--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 use backdrop-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_required except import_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: Sometimes run automatically during rebuild, but NOT always reliable. If you get "Unknown column" errors after pulling latest, visit /run-migrate/ in the browser to apply pending migrations manually. This endpoint runs python manage.py migrate on the production MySQL database.
  • Never edit ai-dev directly on GitHub — Flatlogic pushes overwrite it
  • Gemini gotcha: Flatlogic's Gemini AI reads __pycache__/*.pyc and 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