swiftops-backend / docs /agent /implementation-notes /PAYROLL_QUICK_REFERENCE.md
kamau1's picture
feat(project): add complete project setup workflow with service methods and API endpoints for regions, roles, subcontractors, and finalization including validation and authorization
4835b24

Payroll System - Quick Reference Guide

Overview

Weekly payroll generation system that automatically calculates worker compensation from timesheets and ticket assignments.

Key Concepts

Pay Period

  • Duration: Weekly (Monday to Sunday)
  • Generation: Monday morning (6 AM) for previous week
  • Calculation: Aggregates timesheets + ticket assignments

Compensation Formula

total_amount = flat_rate + ticket_earnings + bonus - deductions

where:
  flat_rate = Weekly base rate from project_role
  ticket_earnings = (base_amount * tickets_closed) + commission
  commission = (base_amount * tickets_closed) * (commission_percentage / 100)

API Endpoints

Generate Payroll (Manual)

POST /api/v1/payroll/generate
Authorization: Bearer {token}
Content-Type: application/json

{
  "user_id": "uuid",
  "project_id": "uuid",
  "period_start_date": "2024-12-09",  // Must be Monday
  "period_end_date": "2024-12-15"     // Must be Sunday
}

Response: PayrollGenerateResponse
{
  "success": true,
  "payroll": { ... },
  "message": "Payroll generated successfully",
  "skipped_reason": null
}

Generate Batch Payroll

POST /api/v1/payroll/generate-batch
Authorization: Bearer {token}
Content-Type: application/json

{
  "target_date": "2024-12-09",  // Any date in target week
  "project_id": null            // Optional: filter by project
}

Response: PayrollBatchGenerateResponse
{
  "success": true,
  "total_processed": 25,
  "total_generated": 20,
  "total_skipped": 5,
  "payrolls": [...],
  "errors": [],
  "period_start_date": "2024-12-09",
  "period_end_date": "2024-12-15"
}

Get Payroll Details

GET /api/v1/payroll/{payroll_id}
Authorization: Bearer {token}

Response: PayrollResponse
{
  "id": "uuid",
  "user_id": "uuid",
  "project_id": "uuid",
  "period_start_date": "2024-12-09",
  "period_end_date": "2024-12-15",
  "period_label": "Dec 09 - Dec 15, 2024",
  "tickets_closed": 12,
  "hours_worked": 40.0,
  "days_worked": 5,
  "flat_rate_amount": 5000.00,
  "ticket_earnings": 6600.00,
  "bonus_amount": 0.00,
  "deductions": 0.00,
  "total_amount": 11600.00,
  "net_earnings": 11600.00,
  "is_paid": false,
  "can_recalculate": true,
  "is_current_week": false,
  "user_name": "John Doe",
  "user_email": "john@example.com",
  "project_name": "FTTH Rollout",
  ...
}

List Payrolls (Filtered)

GET /api/v1/payroll?user_id={uuid}&is_paid=false&page=1&page_size=20
Authorization: Bearer {token}

Query Parameters:
- user_id: Filter by specific worker
- project_id: Filter by project
- is_paid: Filter by payment status (true/false)
- period_start_date: Filter by period start (>=)
- period_end_date: Filter by period end (<=)
- page: Page number (default: 1)
- page_size: Items per page (default: 20, max: 100)
- sort_by: Sort field (period_start_date, total_amount, created_at, paid_at)
- sort_order: Sort order (asc/desc)

Response: PayrollListResponse
{
  "items": [...],
  "total": 50,
  "page": 1,
  "page_size": 20,
  "has_more": true
}

Recalculate Payroll

POST /api/v1/payroll/{payroll_id}/recalculate
Authorization: Bearer {token}

Response: PayrollGenerateResponse
{
  "success": true,
  "payroll": { ... },
  "message": "Payroll recalculated successfully"
}

Note: Only works on unpaid payrolls

Mark as Paid

POST /api/v1/payroll/{payroll_id}/mark-paid
Authorization: Bearer {token}
Content-Type: application/json

{
  "payment_method": "mobile_money",  // mobile_money, bank_transfer, cash, cheque
  "payment_reference": "ABC123XYZ"   // Optional: M-Pesa ID, bank ref, etc.
}

Response: PayrollResponse
{
  "id": "uuid",
  "is_paid": true,
  "paid_at": "2024-12-16T08:30:00Z",
  "paid_by_user_id": "uuid",
  "payment_method": "mobile_money",
  "payment_reference": "ABC123XYZ",
  ...
}

Note: Cannot be recalculated after marking as paid

CLI Commands

Generate Weekly Payroll

# Default: Previous week
python -m app.tasks.payroll_tasks

# Dry run (test without committing)
python -m app.tasks.payroll_tasks --dry-run

# Specific date (calculates week containing this date)
python -m app.tasks.payroll_tasks --date 2024-12-09

# Specific project only
python -m app.tasks.payroll_tasks --project-id abc123...

# Ad-hoc week (catch-up for missed week)
python -m app.tasks.payroll_tasks --start 2024-12-02 --end 2024-12-08

Output Example

===== PAYROLL GENERATION JOB STARTED =====
Target date: 2024-12-08
Project filter: ALL
Processing payroll for week: 2024-12-02 to 2024-12-08

===== PAYROLL GENERATION SUMMARY =====
Total processed: 25
Total generated: 20
Total skipped: 5
Errors: 0

===== PAYROLL GENERATION JOB COMPLETED =====

Scheduling

Linux/Mac (Cron)

# Edit crontab
crontab -e

# Add line: Every Monday at 6 AM
0 6 * * 1 cd /path/to/project && python -m app.tasks.payroll_tasks

Windows (Task Scheduler)

  1. Open Task Scheduler
  2. Create Task: "Weekly Payroll Generation"
  3. Trigger: Weekly, Monday, 6:00 AM
  4. Action: Start Program
    • Program: python.exe
    • Arguments: -m app.tasks.payroll_tasks
    • Working Directory: D:\atomio\swiftops-backend

Common Use Cases

1. Review Unpaid Payrolls (Weekly)

# API call
curl http://localhost:8000/api/v1/payroll?is_paid=false \
  -H "Authorization: Bearer $TOKEN"

# Response: List of all unpaid payrolls
# Review, adjust bonuses/deductions if needed

2. Correct Timesheet (Triggers Auto-Recalculation)

# Manager updates timesheet
# System automatically recalculates affected payroll (if unpaid)

# No manual action needed!

3. Add Bonus to Payroll

# Not yet implemented in API - use direct DB update
UPDATE user_payroll
SET bonus_amount = 1000,
    calculation_notes = calculation_notes || ' | Bonus: Performance award',
    total_amount = total_amount + 1000
WHERE id = 'payroll-uuid' AND is_paid = false;

# Future: POST /api/v1/payroll/{id}/update endpoint

4. Pay All Workers for Week

# 1. List unpaid payrolls for week
curl "http://localhost:8000/api/v1/payroll?is_paid=false&period_start_date=2024-12-09"

# 2. For each payroll, mark as paid
curl -X POST http://localhost:8000/api/v1/payroll/{id}/mark-paid \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"payment_method": "mobile_money", "payment_reference": "MPESA123"}'

5. Generate Payroll for Missed Week

# Run ad-hoc generation
python -m app.tasks.payroll_tasks --start 2024-11-25 --end 2024-12-01

# Or via API
curl -X POST http://localhost:8000/api/v1/payroll/generate-batch \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"target_date": "2024-11-27"}'

6. Recalculate After Data Correction

# If timesheet correction didn't trigger auto-recalc (paid payroll)
# Manual recalculation needed

curl -X POST http://localhost:8000/api/v1/payroll/{id}/recalculate \
  -H "Authorization: Bearer $TOKEN"

Authorization

Who Can Do What?

Platform Admins:

  • βœ… Generate payroll (manual/batch)
  • βœ… View all payrolls
  • βœ… Recalculate any unpaid payroll
  • βœ… Mark payroll as paid

Project Managers:

  • βœ… Generate payroll for their projects
  • βœ… View payrolls for their projects
  • βœ… Recalculate unpaid payrolls in their projects
  • βœ… Mark payroll as paid in their projects

Dispatchers:

  • βœ… View payrolls (read-only)
  • ❌ Cannot generate/modify

Workers:

  • βœ… View their own payroll only
  • ❌ Cannot generate/modify

Troubleshooting

Payroll Not Generated?

Check:

  1. Is worker in project_team? (active member)
  2. Does worker have project_role assigned?
  3. Are there timesheets for that week?
  4. Was payroll already generated? (check logs)

Payroll Amount is Zero?

Check:

  1. Worker's role has flat_rate_amount set?
  2. Worker's role has base_amount and commission_percentage set?
  3. Worker closed any tickets that week?
  4. Timesheets show PRESENT status?

Cannot Recalculate?

Check:

  1. Is payroll already paid? (cannot recalculate paid)
  2. Check is_paid and can_recalculate fields

Background Job Not Running?

Check:

  1. Cron/Task Scheduler configured correctly?
  2. Python environment activated?
  3. Working directory set correctly?
  4. Check logs: payroll_job_YYYYMMDD_HHMMSS.log

Data Sources

Timesheets

-- Aggregated data
SELECT 
    user_id,
    project_id,
    COUNT(*) FILTER (WHERE status = 'present') as days_worked,
    SUM(hours_worked) FILTER (WHERE status = 'present') as hours_worked
FROM timesheets
WHERE work_date BETWEEN '2024-12-09' AND '2024-12-15'
GROUP BY user_id, project_id;

Ticket Assignments

-- Aggregated data
SELECT 
    user_id,
    COUNT(*) as tickets_closed
FROM ticket_assignments
WHERE action = 'complete'
  AND ended_at BETWEEN '2024-12-09 00:00:00' AND '2024-12-15 23:59:59'
GROUP BY user_id;

Compensation Rates

-- From project_team + project_role
SELECT 
    pt.user_id,
    pt.project_id,
    pr.flat_rate_amount,
    pr.base_amount,
    pr.commission_percentage,
    pr.role_name
FROM project_team pt
JOIN project_roles pr ON pt.project_role_id = pr.id
WHERE pt.user_id = ? AND pt.project_id = ?;

Best Practices

1. Weekly Workflow

  1. Monday morning: Background job generates payroll automatically
  2. Monday-Tuesday: Review generated payrolls, add bonuses/deductions
  3. Wednesday: Mark payrolls as paid after disbursement
  4. Thursday-Friday: Workers check their payroll via app

2. Data Integrity

  • Ensure timesheets generated daily (don't wait until week end)
  • Ensure ticket assignments properly closed
  • Ensure all workers have roles assigned
  • Validate compensation rates are up-to-date

3. Error Handling

  • Check batch job logs every Monday
  • Investigate skipped workers
  • Fix missing data (roles, timesheets)
  • Re-run for specific workers if needed

4. Testing

  • Always dry-run first in new environment
  • Test with one worker before batch
  • Verify calculations manually first time
  • Monitor closely for first 2-3 weeks

Example Workflow

Complete Payroll Cycle

# 1. Monday 6 AM: Background job runs (automated)
# Generates payroll for previous week

# 2. Monday 9 AM: Manager reviews unpaid payrolls
curl http://localhost:8000/api/v1/payroll?is_paid=false

# 3. Manager adds bonus for top performer
# (Future API endpoint - currently manual DB update)

# 4. Tuesday: Manager corrects timesheet for sick leave
# System auto-recalculates affected payroll

# 5. Wednesday: Disburse payments (M-Pesa, bank transfer)
# Then mark as paid
curl -X POST http://localhost:8000/api/v1/payroll/{id}/mark-paid \
  -d '{"payment_method": "mobile_money", "payment_reference": "MPESA123"}'

# 6. Workers check their payroll via mobile app
# GET /api/v1/payroll (auto-filtered to their own)

Support

Get Help

  • Documentation: docs/agent/PAYROLL_IMPLEMENTATION_COMPLETE.md
  • Logs: payroll_job_YYYYMMDD_HHMMSS.log
  • Database: user_payroll table
  • API Docs: http://localhost:8000/docs (Swagger UI)

Report Issues

Include:

  • Payroll ID (if applicable)
  • User ID and Project ID
  • Period dates
  • Expected vs actual amounts
  • Relevant logs