# Missio ERP + Gusto Payroll Integration Plan

**Project:** City Schools of Decatur (CSD) - K-12 ERP  
**RFP:** 26-001  
**Branch:** `csdecatur`  
**Date:** April 7, 2026  
**Status:** Phase 2 Complete — Benefits Elections, Compensation Statement, Enriched Demo Data

---

## Table of Contents

1. [Executive Summary](#1-executive-summary)
2. [Current System Inventory](#2-current-system-inventory)
3. [Gap Analysis](#3-gap-analysis)
4. [Integration Architecture](#4-integration-architecture)
5. [Data Model Changes](#5-data-model-changes)
6. [Gusto API Integration Spec](#6-gusto-api-integration-spec)
7. [Data Flow Diagrams](#7-data-flow-diagrams)
8. [QuickBooks GL Posting](#8-quickbooks-gl-posting)
9. [Required Code Changes](#9-required-code-changes)
10. [Implementation Phases](#10-implementation-phases)
11. [Risks and Mitigations](#11-risks-and-mitigations)
12. [Open Questions](#12-open-questions)

---

## 1. Executive Summary

### Problem
The Missio ERP platform has a mature employee/HR module, a fully functional time management system, and a fund-based budget/accounting system with QuickBooks Online integration. However, **no payroll processing engine exists**. The current payroll, position control, and benefits pages are **static demo UIs** with hardcoded data built for the RFP presentation.

### Recommendation
Adopt a **hybrid architecture** where:
- **Missio** owns employee master data, time/attendance, position control, salary schedules, and GL posting
- **Gusto** handles payroll calculation, tax withholding, direct deposit, compliance filing (W-2, 941), and benefits administration
- **QuickBooks Online** remains the system of record for General Ledger (already integrated)

### Why Gusto (vs ADP, Paychex, etc.)
| Factor | Gusto | ADP Workforce Now |
|--------|-------|-------------------|
| API quality | Modern REST, well-documented | SOAP/REST hybrid, complex onboarding |
| K-12 support | Supports multi-EIN, multi-location | Better for large enterprise |
| Integration effort | 4-6 weeks | 8-12 weeks |
| Cost per employee | ~$6-12/mo/ee | ~$15-25/mo/ee |
| Benefits admin | Built-in | Separate module |
| Minimum size | No minimum | 50+ employees typical |
| Tax filing | All 50 states + federal | All 50 states + federal |

> **Note:** Final provider selection should include a technical spike and pricing negotiation. Gusto is recommended for speed-to-market but ADP may be preferred for districts with 1000+ employees.

---

## 2. Current System Inventory

### 2.1 What EXISTS and is FUNCTIONAL

#### Employee Data Layer
| Component | File | Key Fields |
|-----------|------|------------|
| Employee profile | `app/Models/EmployeeDetails.php` | employee_id, hourly_rate, department_id, designation_id, joining_date, employment_type, reporting_to |
| User account | `app/Models/User.php` | name, email, mobile, gender, country_id, status |
| Departments | `app/Models/Team.php` | team_name, parent_id (hierarchical) |
| Designations | `app/Models/Designation.php` | name, parent_id (hierarchical) |
| Employee documents | `app/Models/EmployeeDocument.php` | File storage per employee |
| Emergency contacts | `app/Models/EmergencyContact.php` | name, phone, relation |
| Immigration/visa | `app/Models/Passport.php`, `app/Models/VisaDetail.php` | passport_number, visa_number, expiry |
| Skills | `app/Models/EmployeeSkill.php` | Skill tagging |
| Contracts | `app/Models/Contract.php` | amount, start_date, end_date, contract_type_id |

**Employee Profile URL:** `/account/employees/{id}` with 14 tabs (Profile, Projects, Tasks, Leaves, Leaves Quota, Time Logs, Documents, Emergency Contacts, Tickets, Appreciation, Shifts, Permissions, Activity, Immigration)

#### Time & Attendance Module
| Component | File | Key Fields |
|-----------|------|------------|
| Time logs | `app/Models/ProjectTimeLog.php` | start_time, end_time, total_hours, hourly_rate, earnings, approved, approved_by |
| Breaks | `app/Models/ProjectTimeLogBreak.php` | start_time, end_time, reason, total_minutes |
| Attendance | `app/Models/Attendance.php` | clock_in_time, clock_out_time, late, half_day, shift times, GPS (lat/lng) |
| Shifts | `app/Models/EmployeeShift.php` | Shift definitions with schedule assignments |
| Shift schedules | `app/Models/EmployeeShiftSchedule.php` | Per-employee shift calendar |
| Leave requests | `app/Models/Leave.php` | leave_type_id, status (pending/approved/rejected), paid, approved_by |
| Leave types | `app/Models/LeaveType.php` | type_name, no_of_leaves, paid, eligibility rules (gender, dept, role) |
| Leave quotas | `app/Models/EmployeeLeaveQuota.php` | Per-employee leave balances |
| Settings | `app/Models/LogTimeFor.php` | approval_required, auto_timer_stop, tracker_reminder |
| Settings | `app/Models/AttendanceSetting.php` | office hours, late_mark_duration, radius_check, IP restrictions |

**Key Routes:**
- `/account/timelogs` — Time log CRUD + timer start/stop/pause/resume
- `/account/attendances` — Clock in/out + bulk mark + import/export
- `/account/leaves` — Leave requests with approval workflow
- `/account/timelogs/approve_timelog` — Timelog approval endpoint

**Approval Workflow:** Timelogs have `approved` (boolean) + `approved_by` (user_id). Leaves have full status workflow with notifications (7 notification classes).

#### Budget & Fund Accounting
| Component | File | Key Fields |
|-----------|------|------------|
| Budgets | `app/Models/Budget.php` | fiscal_year, status (active/frozen/closed) |
| Allocations | `app/Models/BudgetAllocation.php` | **fund_code**, **account_code**, category, allocated/expended/encumbered amounts |
| Transactions | `app/Models/BudgetTransaction.php` | type (encumbrance/expenditure), reference_type, reference_id |
| Transfers | `app/Models/BudgetTransfer.php` | from/to allocation, amount, approval workflow |
| Grant sources | Migration: `create_grant_funding_sources_table` | Grant/funding source tracking |

**Fund Accounting Structure:** `BudgetAllocation` uses `fund_code` (VARCHAR 50) and `account_code` (VARCHAR 50) for K-12 fund-based accounting. This maps directly to the district's Chart of Accounts (General Fund 100, Title I 200, IDEA 250, etc.).

#### Finance & Procurement
| Component | File | Status |
|-----------|------|--------|
| Invoices | `app/Models/Invoice.php` | Full lifecycle (draft/unpaid/partial/paid) |
| Payments | `app/Models/Payment.php` | Multi-gateway |
| Expenses | `app/Models/Expense.php` | Category-based with approval |
| Requisitions | `app/Models/Requisition.php` | Req→PO workflow linked to BudgetAllocation |
| Purchase orders | `app/Models/PurchaseOrder.php` | Full procurement cycle |
| Vendors | `app/Models/Vendor.php` | With 1099 eligibility tracking |
| 1099 tracking | `app/Http/Controllers/Tracking1099Controller.php` | TIN verification, W-9 status, YTD income |
| Approval inbox | `app/Http/Controllers/ApprovalController.php` | Centralized approve/reject/delegate/escalate |

#### QuickBooks Online Integration (EXISTING)
| Component | File | Sync Direction |
|-----------|------|----------------|
| QB controller | `app/Http/Controllers/QuickbookController.php` | Missio → QB |
| QB settings | `app/Models/QuickBooksSetting.php` | OAuth2 tokens, realmId |
| Invoice sync | `InvoiceObserver` (lines ~195-201) | Auto-create QB invoice on Missio invoice create |
| Payment sync | `PaymentObserver` (lines ~96-102) | Auto-create QB payment on Missio payment create |

**Important clarification:** There is **NO ERPNext/Frappe integration** in the codebase. The only "Frappe" references are to `frappe-charts` and `frappe-gantt` JavaScript UI libraries. QuickBooks Online is the accounting system.

### 2.2 What EXISTS but is DEMO ONLY (Static HTML, No Backend)

| Demo Page | File | What It Shows |
|-----------|------|---------------|
| Payroll dashboard | `resources/views/payroll/demo.blade.php` | 876 employees, $3.28M last payroll, $29.5M YTD, pay period status, payroll run history, exceptions |
| Position control | `resources/views/position-control/demo.blade.php` | 924 authorized positions, 876 filled, 48 vacant, 898.5 FTE, dept breakdown with budget allocation vs actual cost |
| Benefits admin | `resources/views/benefits/demo.blade.php` | 831 enrolled, $1.24M/mo employer cost, Georgia SHBP Health/Dental/Vision, TRS retirement, FSA |
| AP dashboard | `resources/views/accounts-payable/demo.blade.php` | AP overview |
| AR dashboard | `resources/views/accounts-receivable/demo.blade.php` | AR overview |

These demos are served by `BudgetDemoController` via routes in `routes/budget.php`.

---

## 3. Gap Analysis

### 3.1 CRITICAL GAPS (No code, no models, no migrations)

| # | Gap | Description | Blocked By |
|---|-----|-------------|------------|
| G1 | **Payroll processing engine** | No PayrollRun, PayPeriod, Paycheck models. No batch calculation. | Need Gusto integration or custom engine |
| G2 | **Salary schedules** | No step/lane tables. K-12 certified teachers are placed on salary matrices (e.g., Step 5, Lane M+30 = $52,450). Only `hourly_rate` exists on EmployeeDetails. | Must build custom — K-12 specific |
| G3 | **Deductions management** | No Deduction, EmployeeDeduction models. No pre-tax/post-tax categorization. No garnishment handling. | Gusto handles calculation; Missio needs tracking models |
| G4 | **Benefits enrollment backend** | Demo UI exists. No BenefitPlan, EmployeeBenefitEnrollment, BenefitRate models. | Gusto handles; Missio needs sync models |
| G5 | **Position control backend** | Demo UI exists. No Position model linking designation + department + fund_code + FTE + budget. | Must build custom |
| G6 | **Direct deposit / banking info** | No employee bank account storage. | Gusto handles; never store in Missio |
| G7 | **Pay stubs** | No PayStub model, no PDF generation for earnings statements. | Pull from Gusto API or generate from payroll results |
| G8 | **Tax withholding engine** | Only basic Tax model (name + rate_percent). No federal/state/FICA/Medicare calculation. No W-4 data. | Gusto handles all tax calc |
| G9 | **Payroll → GL journal entries** | BudgetAllocation has fund_code/account_code. No automated journal entry creation from payroll results. | Must build: pull Gusto results → generate entries → post to QB |
| G10 | **Multi-fund salary split** | BudgetAllocations exist per entity. No way to split one employee's salary across funds (e.g., 60% General Fund, 40% Title I). | Must build custom |
| G11 | **Retirement (TRS) contribution calc** | Benefits demo shows Georgia TRS. No integration. | Gusto can handle contribution %; Missio needs reporting |
| G12 | **Pay calendar / pay schedules** | No PayCalendar model (semi-monthly, monthly, biweekly). | Must build — drives payroll cycle |
| G13 | **Year-end processing** | No W-2 generation, no annual reconciliation. | Gusto generates W-2s; Missio needs to surface them |
| G14 | **Payroll approval workflow** | ApprovalController exists but payroll is not an approvable entity type. | Extend existing approval system |

### 3.2 Partial Gaps (Exist but need extension)

| # | Gap | Current State | Needed |
|---|-----|---------------|--------|
| P1 | Employee compensation data | Only `hourly_rate` on EmployeeDetails | Add annual_salary, pay_type (hourly/salary), salary_schedule_id, step, lane |
| P2 | Time → payroll aggregation | Timelogs are project/task-oriented | Service to aggregate approved hours by employee by pay period |
| P3 | Overtime calculation | Total hours tracked but no OT rules | OT rules engine (40hr/week threshold, FLSA compliance) |
| P4 | Substitute teacher pay | Attendance tracks substitute status | Need sub pay rates and auto-calc |
| P5 | Stipend management | No stipend tracking | Coaching, mentoring, additional duty stipends common in K-12 |

---

## 4. Integration Architecture

### 4.1 System Ownership Model

```
+------------------------------------------------------------------+
|                    MISSIO ERP (System of Record)                  |
|                                                                    |
|  OWNS:                              CONSUMES FROM GUSTO:          |
|  - Employee master data              - Calculated gross/net pay   |
|  - Org structure (dept/desig)         - Tax withholdings (fed/    |
|  - Position control                     state/FICA/Medicare)      |
|  - Salary schedules (step/lane)       - Deduction amounts         |
|  - Time & attendance                  - Direct deposit status     |
|  - Leave management                  - W-2 / tax forms            |
|  - Budget & fund accounting          - Benefits enrollment data   |
|  - GL journal entries                - Compliance filing status   |
|  - Approval workflows                                             |
|  - Reporting & dashboards                                         |
+------------------------------------------------------------------+
         |                    |                      |
         | Employee sync      | Payroll results      | Journal entries
         | (Missio → Gusto)   | (Gusto → Missio)     | (Missio → QB)
         |                    |                      |
         v                    v                      v
+------------------+  +------------------+  +------------------+
|   GUSTO API      |  |   GUSTO API      |  |  QUICKBOOKS      |
|                  |  |                  |  |  ONLINE           |
|  - Payroll calc  |  |  - Pay stubs     |  |                  |
|  - Tax engine    |  |  - Tax forms     |  |  - Chart of Accts|
|  - Direct deposit|  |  - Compliance    |  |  - Journal entries|
|  - Benefits      |  |  - Garnishments  |  |  - GL reports    |
|  - W-2/941/940   |  |  - Workers' comp |  |  - Fund balances |
+------------------+  +------------------+  +------------------+
```

### 4.2 Integration Touchpoints

| # | Integration | Direction | Trigger | Frequency |
|---|------------|-----------|---------|-----------|
| I1 | Employee create/update | Missio → Gusto | Employee saved in Missio | Real-time (Observer) |
| I2 | Hours & earnings submission | Missio → Gusto | Payroll run initiated | Per pay period |
| I3 | Payroll results retrieval | Gusto → Missio | Payroll processed in Gusto | Per pay period (webhook + poll) |
| I4 | Benefits enrollment sync | Gusto → Missio | Open enrollment / life event | Event-driven |
| I5 | GL journal entry posting | Missio → QuickBooks | After payroll results received | Per pay period |
| I6 | Tax form availability | Gusto → Missio | Year-end | Annual |
| I7 | Pay stub access | Gusto → Missio | After each payroll | Per pay period |
| I8 | Deduction setup | Missio → Gusto | HR configures deductions | As needed |

---

## 5. Data Model Changes

### 5.1 New Models Required

#### Position Control Module

```php
// Migration: create_positions_table
Schema::create('positions', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('company_id');
    $table->string('position_number', 20)->unique();      // e.g., "INST-0442"
    $table->string('title');                                // e.g., "5th Grade Teacher"
    $table->unsignedBigInteger('department_id');            // FK → teams
    $table->unsignedBigInteger('designation_id')->nullable(); // FK → designations
    $table->string('fund_code', 50);                       // e.g., "100" (General Fund)
    $table->string('account_code', 50);                    // e.g., "6110" (Cert Salaries)
    $table->unsignedBigInteger('budget_allocation_id')->nullable(); // FK → budget_allocations
    $table->unsignedBigInteger('salary_schedule_id')->nullable();   // FK → salary_schedules
    $table->decimal('fte', 5, 2)->default(1.00);           // 1.0 = full-time, 0.5 = half
    $table->decimal('budgeted_salary', 12, 2)->default(0);
    $table->enum('status', ['authorized', 'filled', 'vacant', 'frozen', 'abolished'])->default('authorized');
    $table->unsignedBigInteger('employee_id')->nullable(); // FK → users (who fills it)
    $table->date('effective_date');
    $table->date('end_date')->nullable();
    $table->string('location', 100)->nullable();           // School/building
    $table->boolean('grant_funded')->default(false);
    $table->string('grant_code', 50)->nullable();
    $table->text('notes')->nullable();
    $table->unsignedBigInteger('created_by')->nullable();
    $table->unsignedBigInteger('updated_by')->nullable();
    $table->timestamps();
    $table->softDeletes();

    $table->index(['company_id', 'status']);
    $table->index(['department_id']);
    $table->index(['fund_code', 'account_code']);
});
```

#### Salary Schedules Module

```php
// Migration: create_salary_schedules_table
Schema::create('salary_schedules', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('company_id');
    $table->string('name');                                 // e.g., "Certified Teacher Schedule"
    $table->string('fiscal_year', 9);                      // e.g., "2025-2026"
    $table->enum('type', ['certified', 'classified', 'administrative', 'substitute']);
    $table->enum('status', ['draft', 'board_approved', 'active', 'archived'])->default('draft');
    $table->date('effective_date');
    $table->date('board_approval_date')->nullable();
    $table->text('notes')->nullable();
    $table->unsignedBigInteger('created_by')->nullable();
    $table->timestamps();
    $table->softDeletes();
});

// Migration: create_salary_schedule_steps_table
Schema::create('salary_schedule_steps', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('salary_schedule_id');       // FK → salary_schedules
    $table->integer('step');                                // Years of experience (0-30+)
    $table->string('lane', 20);                            // Education level: "B", "B+20", "M", "M+30", "Ed.S", "Ed.D"
    $table->decimal('annual_amount', 12, 2);               // e.g., $52,450.00
    $table->decimal('daily_rate', 8, 2)->nullable();       // annual / contract_days
    $table->decimal('hourly_rate', 8, 2)->nullable();      // daily / hours_per_day
    $table->timestamps();

    $table->unique(['salary_schedule_id', 'step', 'lane']);
    $table->index(['salary_schedule_id']);
});

// Migration: create_employee_salary_placements_table
Schema::create('employee_salary_placements', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('company_id');
    $table->unsignedBigInteger('employee_id');              // FK → users
    $table->unsignedBigInteger('salary_schedule_id');       // FK → salary_schedules
    $table->integer('step');
    $table->string('lane', 20);
    $table->decimal('base_salary', 12, 2);                 // From schedule lookup
    $table->decimal('total_salary', 12, 2);                // Base + stipends
    $table->enum('pay_type', ['salary', 'hourly'])->default('salary');
    $table->integer('contract_days')->default(190);        // K-12: typically 190 teacher days
    $table->decimal('daily_rate', 8, 2)->nullable();
    $table->date('effective_date');
    $table->date('end_date')->nullable();
    $table->json('stipends')->nullable();                  // [{"name":"Coaching","amount":3000},...]
    $table->text('notes')->nullable();
    $table->unsignedBigInteger('created_by')->nullable();
    $table->timestamps();
    $table->softDeletes();

    $table->index(['employee_id', 'effective_date']);
});
```

#### Payroll Core Module

```php
// Migration: create_pay_calendars_table
Schema::create('pay_calendars', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('company_id');
    $table->string('name');                                // e.g., "Semi-Monthly Certified"
    $table->enum('frequency', ['weekly', 'biweekly', 'semi_monthly', 'monthly']);
    $table->string('fiscal_year', 9);
    $table->integer('total_periods');                       // 24 for semi-monthly, 26 for biweekly
    $table->boolean('is_default')->default(false);
    $table->timestamps();
});

// Migration: create_pay_periods_table
Schema::create('pay_periods', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('pay_calendar_id');
    $table->integer('period_number');                       // 1-24 for semi-monthly
    $table->date('start_date');
    $table->date('end_date');
    $table->date('pay_date');
    $table->date('time_entry_deadline')->nullable();       // When timesheets are due
    $table->enum('status', ['upcoming', 'open', 'closed', 'processing', 'paid'])->default('upcoming');
    $table->timestamps();

    $table->unique(['pay_calendar_id', 'period_number']);
});

// Migration: create_payroll_runs_table
Schema::create('payroll_runs', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('company_id');
    $table->unsignedBigInteger('pay_period_id');
    $table->string('gusto_payroll_id')->nullable();        // External provider reference
    $table->enum('status', [
        'draft',              // Collecting data
        'pending_review',     // Ready for review
        'pending_approval',   // Submitted for approval
        'approved',           // Approved, ready to send to Gusto
        'submitted',          // Sent to Gusto
        'processing',         // Gusto is calculating
        'calculated',         // Results received from Gusto
        'gl_posted',          // Journal entries created
        'completed',          // Fully processed
        'voided'              // Cancelled
    ])->default('draft');
    $table->integer('employee_count')->default(0);
    $table->decimal('total_gross', 14, 2)->default(0);
    $table->decimal('total_deductions', 14, 2)->default(0);
    $table->decimal('total_employer_taxes', 14, 2)->default(0);
    $table->decimal('total_employer_benefits', 14, 2)->default(0);
    $table->decimal('total_net', 14, 2)->default(0);
    $table->decimal('total_employer_cost', 14, 2)->default(0);
    $table->unsignedBigInteger('submitted_by')->nullable();
    $table->timestamp('submitted_at')->nullable();
    $table->unsignedBigInteger('approved_by')->nullable();
    $table->timestamp('approved_at')->nullable();
    $table->timestamp('processed_at')->nullable();
    $table->text('notes')->nullable();
    $table->json('exceptions')->nullable();                // Any errors/warnings from Gusto
    $table->timestamps();
    $table->softDeletes();

    $table->index(['company_id', 'status']);
    $table->index(['pay_period_id']);
});

// Migration: create_paychecks_table
Schema::create('paychecks', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('payroll_run_id');
    $table->unsignedBigInteger('employee_id');              // FK → users
    $table->string('gusto_paycheck_id')->nullable();
    $table->enum('pay_type', ['salary', 'hourly']);
    $table->decimal('regular_hours', 8, 2)->default(0);
    $table->decimal('overtime_hours', 8, 2)->default(0);
    $table->decimal('pto_hours', 8, 2)->default(0);
    $table->decimal('sick_hours', 8, 2)->default(0);
    $table->decimal('gross_pay', 12, 2)->default(0);
    $table->decimal('federal_tax', 10, 2)->default(0);
    $table->decimal('state_tax', 10, 2)->default(0);
    $table->decimal('social_security_employee', 10, 2)->default(0);
    $table->decimal('medicare_employee', 10, 2)->default(0);
    $table->decimal('total_deductions', 12, 2)->default(0);
    $table->decimal('net_pay', 12, 2)->default(0);
    $table->decimal('employer_social_security', 10, 2)->default(0);
    $table->decimal('employer_medicare', 10, 2)->default(0);
    $table->decimal('employer_benefits', 10, 2)->default(0);
    $table->decimal('total_employer_cost', 12, 2)->default(0);
    $table->json('deductions_detail')->nullable();          // Itemized deductions from Gusto
    $table->json('earnings_detail')->nullable();            // Itemized earnings from Gusto
    $table->json('fund_allocations')->nullable();           // Fund split percentages applied
    $table->timestamps();

    $table->index(['payroll_run_id']);
    $table->index(['employee_id']);
});
```

#### Employee Fund Allocation (Multi-Fund Salary Split)

```php
// Migration: create_employee_fund_allocations_table
Schema::create('employee_fund_allocations', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('company_id');
    $table->unsignedBigInteger('employee_id');
    $table->string('fund_code', 50);                       // e.g., "100" (General Fund)
    $table->string('account_code', 50);                    // e.g., "6110" (Cert Salaries)
    $table->unsignedBigInteger('budget_allocation_id')->nullable();
    $table->decimal('percentage', 5, 2);                   // e.g., 60.00 = 60%
    $table->date('effective_date');
    $table->date('end_date')->nullable();
    $table->text('notes')->nullable();
    $table->timestamps();

    $table->index(['employee_id', 'effective_date']);
    // Percentages for an employee must sum to 100%
});
```

#### Payroll GL Journal Entries

```php
// Migration: create_payroll_journal_entries_table
Schema::create('payroll_journal_entries', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('company_id');
    $table->unsignedBigInteger('payroll_run_id');
    $table->string('fund_code', 50);
    $table->string('account_code', 50);
    $table->unsignedBigInteger('budget_allocation_id')->nullable();
    $table->string('entry_type', 30);                      // 'salary_expense', 'tax_expense', 'benefit_expense', 'payable', 'cash'
    $table->decimal('debit', 14, 2)->default(0);
    $table->decimal('credit', 14, 2)->default(0);
    $table->string('description');
    $table->string('quickbooks_journal_id')->nullable();   // QB JournalEntry ID after posting
    $table->boolean('posted_to_qb')->default(false);
    $table->timestamp('posted_at')->nullable();
    $table->timestamps();

    $table->index(['payroll_run_id']);
    $table->index(['fund_code', 'account_code']);
});
```

### 5.2 Existing Model Changes

#### EmployeeDetails — Add Payroll Fields
```php
// Migration: add_payroll_fields_to_employee_details_table
Schema::table('employee_details', function (Blueprint $table) {
    $table->decimal('annual_salary', 12, 2)->nullable()->after('hourly_rate');
    $table->enum('pay_type', ['salary', 'hourly'])->default('salary')->after('annual_salary');
    $table->unsignedBigInteger('position_id')->nullable()->after('designation_id');
    $table->unsignedBigInteger('salary_schedule_id')->nullable();
    $table->integer('salary_step')->nullable();
    $table->string('salary_lane', 20)->nullable();
    $table->unsignedBigInteger('pay_calendar_id')->nullable();
    $table->string('gusto_employee_id')->nullable();       // Gusto external reference
    $table->string('gusto_sync_status', 20)->nullable();   // synced, pending, error
    $table->timestamp('gusto_last_synced_at')->nullable();
});
```

---

## 6. Gusto API Integration Spec

### 6.1 API Overview

**Base URL:** `https://api.gusto.com/v1`  
**Auth:** OAuth 2.0 (company-level tokens)  
**Rate Limits:** 50 requests/second  
**Webhooks:** Available for payroll events  

### 6.2 Required Gusto API Endpoints

| # | Endpoint | Method | Purpose | Missio Trigger |
|---|----------|--------|---------|----------------|
| 1 | `/v1/companies/{id}/employees` | POST | Create employee in Gusto | EmployeeObserver::created() |
| 2 | `/v1/employees/{id}` | PUT | Update employee in Gusto | EmployeeObserver::updated() |
| 3 | `/v1/employees/{id}` | GET | Fetch employee details | Sync job |
| 4 | `/v1/companies/{id}/payrolls` | GET | List payrolls for company | PayrollRun listing |
| 5 | `/v1/companies/{id}/payrolls` | POST | Create off-cycle payroll | Off-cycle pay |
| 6 | `/v1/companies/{id}/payrolls/{id}` | PUT | Submit payroll with hours | PayrollRun approved |
| 7 | `/v1/companies/{id}/payrolls/{id}` | GET | Get payroll results | After processing |
| 8 | `/v1/payrolls/{id}/employees/{id}/pay_stubs` | GET | Get pay stub PDF | Employee self-service |
| 9 | `/v1/companies/{id}/earning_types` | GET/POST | Manage earning types | Setup |
| 10 | `/v1/companies/{id}/benefits` | GET/POST | Manage benefit plans | Benefits setup |
| 11 | `/v1/employees/{id}/benefits` | GET/POST | Employee benefit enrollment | Open enrollment |
| 12 | `/v1/companies/{id}/tax_requirements` | GET | Get tax filing requirements | Compliance check |
| 13 | `/v1/employees/{id}/federal_taxes` | GET/PUT | W-4 data | Employee onboarding |
| 14 | `/v1/employees/{id}/state_taxes` | GET/PUT | State withholding | Employee onboarding |

### 6.3 New Service Classes

```
app/Services/Gusto/
├── GustoClient.php                  # HTTP client wrapper (OAuth, retry, logging)
├── GustoEmployeeService.php         # Employee CRUD sync
├── GustoPayrollService.php          # Payroll submission & results
├── GustoBenefitsService.php         # Benefits enrollment sync
├── GustoTaxService.php              # Tax form retrieval
├── GustoWebhookHandler.php          # Incoming webhook processor
└── GustoSyncManager.php             # Orchestrates full sync cycle
```

### 6.4 New Controllers

```
app/Http/Controllers/
├── PayrollController.php            # Payroll runs CRUD + dashboard (replaces demo)
├── PayCalendarController.php        # Pay calendar management
├── PositionController.php           # Position control CRUD (replaces demo)
├── SalaryScheduleController.php     # Salary schedule CRUD
├── BenefitPlanController.php        # Benefits management (replaces demo)
├── PaycheckController.php           # Pay stub viewer
├── EmployeeFundAllocationController.php  # Fund split management
├── PayrollJournalEntryController.php     # GL posting management
└── GustoSettingsController.php      # Gusto OAuth + config (like QuickbookSettingsController)
```

### 6.5 New Jobs (Background Processing)

```
app/Jobs/
├── SyncEmployeeToGusto.php          # Push employee data to Gusto
├── SyncEmployeeFromGusto.php        # Pull employee updates from Gusto
├── SubmitPayrollToGusto.php         # Send approved payroll to Gusto
├── FetchPayrollResults.php          # Pull calculated payroll results
├── GeneratePayrollJournalEntries.php # Create GL entries from payroll results
├── PostPayrollToQuickBooks.php      # Push journal entries to QB
├── AggregateTimesheetHours.php      # Sum approved hours per employee per period
└── SyncBenefitsFromGusto.php        # Pull benefits enrollment data
```

### 6.6 New Observers

```
app/Observers/
├── PayrollRunObserver.php           # Status change events, approval triggers
├── PositionObserver.php             # Budget validation on position create
└── EmployeeSalaryPlacementObserver.php  # Update Gusto on salary change
```

### 6.7 Config & Environment

```env
# .env additions
GUSTO_CLIENT_ID=
GUSTO_CLIENT_SECRET=
GUSTO_ENVIRONMENT=sandbox          # sandbox or production
GUSTO_COMPANY_ID=
GUSTO_WEBHOOK_SECRET=
GUSTO_ACCESS_TOKEN=
GUSTO_REFRESH_TOKEN=
```

```php
// config/gusto.php
return [
    'client_id' => env('GUSTO_CLIENT_ID'),
    'client_secret' => env('GUSTO_CLIENT_SECRET'),
    'environment' => env('GUSTO_ENVIRONMENT', 'sandbox'),
    'base_url' => env('GUSTO_ENVIRONMENT') === 'production'
        ? 'https://api.gusto.com'
        : 'https://api.gusto-demo.com',
    'webhook_secret' => env('GUSTO_WEBHOOK_SECRET'),
    'sync_interval' => 15,  // minutes between sync checks
];
```

---

## 7. Data Flow Diagrams

### 7.1 Payroll Run Lifecycle

```
Step 1: PREPARE
┌──────────────┐     ┌───────────────┐     ┌──────────────────┐
│ Pay Period    │────>│ Aggregate     │────>│ PayrollRun       │
│ opens         │     │ Approved      │     │ (status: draft)  │
│               │     │ Timesheets    │     │                  │
└──────────────┘     │ + Attendance  │     │ employee_count   │
                     │ + Leave       │     │ exceptions[]     │
                     └───────────────┘     └────────┬─────────┘
                                                     │
Step 2: REVIEW & APPROVE                             │
┌──────────────────────────────────────────────────────┘
│
▼
┌──────────────────┐     ┌──────────────────┐     ┌──────────────────┐
│ Payroll Manager  │────>│ PayrollRun       │────>│ Approval         │
│ reviews          │     │ (pending_review) │     │ Workflow         │
│ exceptions       │     │                  │     │ (existing        │
│                  │     │ Fix any issues   │     │  ApprovalCtrl)   │
└──────────────────┘     └──────────────────┘     └────────┬─────────┘
                                                            │
Step 3: SUBMIT TO GUSTO                                     │
┌───────────────────────────────────────────────────────────┘
│
▼
┌──────────────────┐     ┌──────────────────┐     ┌──────────────────┐
│ PayrollRun       │────>│ SubmitPayroll    │────>│ Gusto API        │
│ (approved)       │     │ ToGusto Job      │     │ PUT /payrolls    │
│                  │     │                  │     │                  │
│ Sends:           │     │ Maps hours,      │     │ Returns:         │
│ - Regular hours  │     │ salary, stipends │     │ - Calculated pay │
│ - OT hours       │     │ per employee     │     │ - Tax amounts    │
│ - Leave hours    │     │                  │     │ - Deductions     │
│ - Salary amounts │     │                  │     │ - Net pay        │
└──────────────────┘     └──────────────────┘     └────────┬─────────┘
                                                            │
Step 4: RECEIVE RESULTS                                     │
┌───────────────────────────────────────────────────────────┘
│
▼
┌──────────────────┐     ┌──────────────────┐     ┌──────────────────┐
│ FetchPayroll     │────>│ Store Paychecks  │────>│ Generate GL      │
│ Results Job      │     │ per employee     │     │ Journal Entries  │
│                  │     │                  │     │                  │
│ Polls Gusto      │     │ gross, net, tax, │     │ Split by         │
│ or webhook       │     │ deductions       │     │ fund_code +      │
│                  │     │                  │     │ account_code     │
└──────────────────┘     └──────────────────┘     └────────┬─────────┘
                                                            │
Step 5: POST TO GL                                          │
┌───────────────────────────────────────────────────────────┘
│
▼
┌──────────────────┐     ┌──────────────────┐     ┌──────────────────┐
│ Payroll Journal  │────>│ PostPayroll      │────>│ QuickBooks       │
│ Entries          │     │ ToQuickBooks Job │     │ Journal Entry    │
│                  │     │                  │     │                  │
│ Grouped by       │     │ Extends existing │     │ Updates GL       │
│ fund + account   │     │ QB controller    │     │ balances         │
│                  │     │                  │     │                  │
│ Updates Budget   │     │                  │     │                  │
│ Allocation       │     │                  │     │                  │
│ expended_amount  │     │                  │     │                  │
└──────────────────┘     └──────────────────┘     └──────────────────┘
```

### 7.2 Employee Sync Flow

```
┌─────────────────────────────────────────────────────┐
│                 MISSIO (Source of Truth)              │
│                                                       │
│  Employee Created/Updated                             │
│  EmployeeDetails Observer fires                       │
│           │                                           │
│           ▼                                           │
│  ┌─────────────────────┐                             │
│  │ SyncEmployeeToGusto │ (dispatched as Job)         │
│  │         Job          │                             │
│  └──────────┬──────────┘                             │
└─────────────┼─────────────────────────────────────────┘
              │
              │ Maps Missio fields → Gusto fields:
              │   user.name         → first_name, last_name
              │   user.email        → email
              │   details.address   → home_address
              │   details.ssn*      → ssn (encrypted, never stored in Missio)
              │   details.dob       → date_of_birth
              │   placement.salary  → compensation.rate
              │   placement.pay_type→ compensation.payment_unit
              │
              ▼
┌─────────────────────────────────────────────────────┐
│                    GUSTO API                          │
│                                                       │
│  POST /v1/companies/{id}/employees                   │
│  or PUT /v1/employees/{gusto_id}                     │
│                                                       │
│  Returns: gusto_employee_id                           │
│           │                                           │
│           ▼                                           │
│  Stored in employee_details.gusto_employee_id         │
└─────────────────────────────────────────────────────┘
```

### 7.3 GL Journal Entry Generation (Fund Split Example)

```
Employee: Jane Smith (Teacher, 60% General Fund / 40% Title I)
Gross Pay: $4,500.00 (semi-monthly)

employee_fund_allocations:
  ┌──────────────┬──────────────┬────────────┐
  │ fund_code    │ account_code │ percentage │
  ├──────────────┼──────────────┼────────────┤
  │ 100 (General)│ 6110 (Cert)  │ 60%        │
  │ 200 (Title I)│ 6110 (Cert)  │ 40%        │
  └──────────────┴──────────────┴────────────┘

Generated payroll_journal_entries:
  ┌──────────┬──────────────┬──────────────┬──────────┬──────────┐
  │ type     │ fund_code    │ account_code │ debit    │ credit   │
  ├──────────┼──────────────┼──────────────┼──────────┼──────────┤
  │ salary   │ 100          │ 6110         │ $2,700   │          │
  │ salary   │ 200          │ 6110         │ $1,800   │          │
  │ fed_tax  │ 100          │ 2110         │          │ $324     │
  │ fed_tax  │ 200          │ 2110         │          │ $216     │
  │ state_tax│ 100          │ 2120         │          │ $162     │
  │ state_tax│ 200          │ 2120         │          │ $108     │
  │ fica_ee  │ 100          │ 2130         │          │ $167.40  │
  │ fica_ee  │ 200          │ 2130         │          │ $111.60  │
  │ med_ee   │ 100          │ 2140         │          │ $39.15   │
  │ med_ee   │ 200          │ 2140         │          │ $26.10   │
  │ net_pay  │ 100          │ 1010         │          │ $1,204.47│
  │ net_pay  │ 200          │ 1010         │          │ $802.98  │
  │ fica_er  │ 100          │ 6210         │ $167.40  │          │
  │ fica_er  │ 200          │ 6210         │ $111.60  │          │
  │ fica_er  │ 100          │ 2130         │          │ $167.40  │
  │ fica_er  │ 200          │ 2130         │          │ $111.60  │
  └──────────┴──────────────┴──────────────┴──────────┴──────────┘

  → Updates BudgetAllocation.expended_amount for fund 100 and 200
  → Posts to QuickBooks as JournalEntry (extends QuickbookController)
```

---

## 8. QuickBooks GL Posting

### 8.1 Extending Existing Integration

The current QuickBooks integration (`app/Http/Controllers/QuickbookController.php`) handles invoices and payments. We extend this for payroll journal entries.

```php
// New method in QuickbookController.php (or new PayrollQuickbookService.php)
public function createPayrollJournalEntry(PayrollRun $payrollRun): ?string
{
    // Group payroll_journal_entries by fund_code
    // Create one QB JournalEntry per fund
    // Each entry has Line items for debits (expenses) and credits (payables/cash)
    //
    // Uses existing QB DataService pattern:
    //   $dataService = $this->getDataService();
    //   $journalEntry = JournalEntry::create([...]);
    //   $result = $dataService->Add($journalEntry);
    //
    // Returns QB JournalEntry ID, stored in payroll_journal_entries.quickbooks_journal_id
}
```

### 8.2 Account Mapping

| Missio account_code | QB Account | Description |
|---------------------|------------|-------------|
| 6110 | Certified Salaries | Teacher/admin salary expense |
| 6120 | Classified Salaries | Staff salary expense |
| 6210 | Employer FICA | Employer SS/Medicare expense |
| 6220 | Employer Benefits | Health/dental/vision expense |
| 6230 | Employer Retirement | TRS contribution expense |
| 2110 | Federal Tax Payable | Liability |
| 2120 | State Tax Payable | Liability |
| 2130 | FICA Payable | Liability |
| 2140 | Medicare Payable | Liability |
| 2150 | Benefits Payable | Liability |
| 1010 | Cash - Payroll | Bank account |

> **Setup required:** Map Missio `account_code` values to QuickBooks `Account.Id` values. Store in a mapping table or config.

---

## 9. Required Code Changes

### 9.1 Files to CREATE (New)

| File | Purpose |
|------|---------|
| `app/Models/Position.php` | Position control model |
| `app/Models/SalarySchedule.php` | Salary schedule model |
| `app/Models/SalaryScheduleStep.php` | Step/lane matrix model |
| `app/Models/EmployeeSalaryPlacement.php` | Employee salary placement |
| `app/Models/PayCalendar.php` | Pay frequency model |
| `app/Models/PayPeriod.php` | Pay period model |
| `app/Models/PayrollRun.php` | Payroll run model |
| `app/Models/Paycheck.php` | Individual paycheck model |
| `app/Models/EmployeeFundAllocation.php` | Multi-fund split model |
| `app/Models/PayrollJournalEntry.php` | GL entry model |
| `app/Models/GustoSetting.php` | Gusto OAuth config (like QuickBooksSetting) |
| `app/Services/Gusto/GustoClient.php` | API client |
| `app/Services/Gusto/GustoEmployeeService.php` | Employee sync |
| `app/Services/Gusto/GustoPayrollService.php` | Payroll operations |
| `app/Services/Gusto/GustoBenefitsService.php` | Benefits sync |
| `app/Services/Gusto/GustoWebhookHandler.php` | Webhook processing |
| `app/Services/PayrollOrchestrationService.php` | Main payroll workflow |
| `app/Services/PayrollJournalEntryService.php` | GL entry generation |
| `app/Services/TimesheetAggregationService.php` | Hours aggregation |
| `app/Http/Controllers/PayrollController.php` | Payroll UI + API |
| `app/Http/Controllers/PayCalendarController.php` | Pay calendar CRUD |
| `app/Http/Controllers/PositionController.php` | Position control CRUD |
| `app/Http/Controllers/SalaryScheduleController.php` | Salary schedule CRUD |
| `app/Http/Controllers/BenefitPlanController.php` | Benefits management |
| `app/Http/Controllers/PaycheckController.php` | Pay stub viewer |
| `app/Http/Controllers/EmployeeFundAllocationController.php` | Fund split |
| `app/Http/Controllers/PayrollJournalEntryController.php` | GL posting |
| `app/Http/Controllers/GustoSettingsController.php` | Gusto config |
| `app/Jobs/SyncEmployeeToGusto.php` | Employee push |
| `app/Jobs/SubmitPayrollToGusto.php` | Payroll submission |
| `app/Jobs/FetchPayrollResults.php` | Results retrieval |
| `app/Jobs/GeneratePayrollJournalEntries.php` | GL generation |
| `app/Jobs/PostPayrollToQuickBooks.php` | QB posting |
| `app/Jobs/AggregateTimesheetHours.php` | Hours aggregation |
| `app/Observers/PayrollRunObserver.php` | Payroll events |
| `app/Events/PayrollRunEvent.php` | Payroll lifecycle events |
| `app/Listeners/PayrollRunListener.php` | Notification handler |
| `app/DataTables/PayrollRunDataTable.php` | Payroll listing |
| `app/DataTables/PositionDataTable.php` | Position listing |
| `app/DataTables/SalaryScheduleDataTable.php` | Schedule listing |
| `config/gusto.php` | Gusto configuration |
| `routes/payroll.php` | Payroll route group |
| `database/migrations/xxxx_create_positions_table.php` | |
| `database/migrations/xxxx_create_salary_schedules_table.php` | |
| `database/migrations/xxxx_create_salary_schedule_steps_table.php` | |
| `database/migrations/xxxx_create_employee_salary_placements_table.php` | |
| `database/migrations/xxxx_create_pay_calendars_table.php` | |
| `database/migrations/xxxx_create_pay_periods_table.php` | |
| `database/migrations/xxxx_create_payroll_runs_table.php` | |
| `database/migrations/xxxx_create_paychecks_table.php` | |
| `database/migrations/xxxx_create_employee_fund_allocations_table.php` | |
| `database/migrations/xxxx_create_payroll_journal_entries_table.php` | |
| `database/migrations/xxxx_create_gusto_settings_table.php` | |
| `database/migrations/xxxx_add_payroll_fields_to_employee_details.php` | |
| `resources/views/payroll/index.blade.php` | Real payroll dashboard |
| `resources/views/payroll/run.blade.php` | Payroll run detail |
| `resources/views/positions/index.blade.php` | Position control |
| `resources/views/salary-schedules/index.blade.php` | Salary grid |
| `resources/views/gusto-settings/index.blade.php` | Gusto config |

### 9.2 Files to MODIFY (Existing)

| File | Change |
|------|--------|
| `app/Models/EmployeeDetails.php` | Add relationships: position(), salaryPlacement(), fundAllocations(), payCalendar() |
| `app/Observers/EmployeeDetailObserver.php` | Dispatch SyncEmployeeToGusto on create/update |
| `app/Http/Controllers/QuickbookController.php` | Add createPayrollJournalEntry() method |
| `app/Http/Controllers/ApprovalController.php` | Add PayrollRun as approvable entity type |
| `app/Http/Controllers/BudgetDemoController.php` | Replace demo routes with real controller references |
| `routes/budget.php` | Update payroll/benefits/position-control routes to real controllers |
| `resources/views/sections/menu.blade.php` | Add Payroll sub-menu items under HR or Finance |
| `resources/views/sections/menus/payroll.blade.php` | Update menu links to real routes |
| `resources/views/sections/menus/human_resources.blade.php` | Add Position Control, Salary Schedules links |
| `resources/views/employees/show.blade.php` | Add "Compensation" tab showing salary placement + fund allocation |
| `resources/views/employees/ajax/edit.blade.php` | Add pay_type, position, salary_schedule dropdowns |
| `database/seeders/` | Add payroll permissions seeder |
| `app/Console/Kernel.php` | Schedule Gusto sync jobs |

### 9.3 Permissions to Add

```php
// Add to permission seeder / SetupBudgetProcurementPermissions command
$payrollPermissions = [
    'view_payroll',           // View payroll runs and dashboard
    'add_payroll',            // Create/initiate payroll runs
    'edit_payroll',           // Modify payroll data
    'approve_payroll',        // Approve payroll for processing
    'manage_pay_calendars',   // Configure pay calendars
    'view_positions',         // View position control
    'add_positions',          // Create positions
    'edit_positions',         // Modify positions
    'delete_positions',       // Remove positions
    'manage_salary_schedules',// Create/edit salary schedules
    'view_paychecks',         // View pay stubs (self or all)
    'manage_benefits',        // Manage benefit plans
    'manage_fund_allocations',// Set employee fund splits
    'view_payroll_reports',   // Access payroll reports
    'manage_gusto_settings',  // Configure Gusto integration
];
```

---

## 10. Implementation Phases

### Phase 1: Foundation (Weeks 1-4)

**Goal:** Core data models and position control

| Week | Tasks | Deliverables |
|------|-------|-------------|
| 1 | Create Position, SalarySchedule, SalaryScheduleStep models + migrations | DB tables, models, relationships |
| 1 | Create EmployeeSalaryPlacement model + migration | Employee → salary schedule link |
| 2 | Build PositionController + views (replace demo) | Working position control CRUD |
| 2 | Build SalaryScheduleController + step/lane grid editor | Salary matrix UI |
| 3 | Build PayCalendar, PayPeriod models + migrations | Pay schedule infrastructure |
| 3 | Add payroll fields to EmployeeDetails + migration | Compensation tab on employee profile |
| 4 | Build EmployeeFundAllocation model + controller | Multi-fund salary split config |
| 4 | Seed CSD salary schedule data + permission setup | Demo data populated |

**Dependencies:** None (all internal)  
**Risk:** Low  

### Phase 2: Gusto Integration (Weeks 5-8)

**Goal:** Connect to Gusto API, employee sync

| Week | Tasks | Deliverables |
|------|-------|-------------|
| 5 | Gusto developer account setup, OAuth flow | GustoClient, GustoSettingsController |
| 5 | Build GustoEmployeeService (create/update employees) | Employee push to Gusto |
| 6 | Build EmployeeObserver integration (auto-sync on save) | Real-time employee sync |
| 6 | Build SyncEmployeeToGusto job + error handling | Background sync with retry |
| 7 | Build GustoPayrollService (list payrolls, submit hours) | Payroll submission |
| 7 | Build TimesheetAggregationService (approved hours → payroll) | Time → payroll bridge |
| 8 | Build FetchPayrollResults job + Paycheck storage | Payroll results in Missio |
| 8 | Integration testing in Gusto sandbox | End-to-end payroll flow |

**Dependencies:** Gusto API access, sandbox credentials  
**Risk:** Medium (API complexity, data mapping)  

### Phase 3: Payroll Processing & GL (Weeks 9-12)

**Goal:** Full payroll run workflow with GL posting

| Week | Tasks | Deliverables |
|------|-------|-------------|
| 9 | Build PayrollController + dashboard (replace demo) | Real payroll dashboard |
| 9 | Build PayrollRun status workflow (draft → completed) | Payroll lifecycle |
| 10 | Build PayrollOrchestrationService | End-to-end payroll run |
| 10 | Integrate with ApprovalController for payroll approval | Approval workflow |
| 11 | Build PayrollJournalEntryService (fund split → GL entries) | Journal entry generation |
| 11 | Build PostPayrollToQuickBooks job (extend QB controller) | QB GL posting |
| 12 | Update BudgetAllocation.expended_amount on payroll post | Budget impact tracking |
| 12 | Payroll exception handling + error reporting | Production hardening |

**Dependencies:** Phase 2 complete, QB account mapping defined  
**Risk:** High (GL accuracy critical, fund split logic complex)  

### Phase 4: Benefits, Reporting & Polish (Weeks 13-16)

**Goal:** Benefits admin, pay stubs, reporting

| Week | Tasks | Deliverables |
|------|-------|-------------|
| 13 | Build BenefitPlanController (replace demo) | Real benefits admin |
| 13 | Build GustoBenefitsService (enrollment sync) | Benefits integration |
| 14 | Build PaycheckController + pay stub PDF viewer | Employee self-service pay stubs |
| 14 | Build payroll reports (by fund, by department, YTD) | Management reports |
| 15 | Build payroll history + audit trail | Compliance |
| 15 | Year-end W-2 access via Gusto API | Tax form access |
| 16 | UAT, bug fixes, CSD-specific configurations | Production readiness |
| 16 | Documentation + training materials | Handoff |

**Dependencies:** Phase 3 complete  
**Risk:** Low-Medium  

### Timeline Summary

```
Week:  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16
       ├──────────────┤
       Phase 1: Foundation
                      ├──────────────┤
                      Phase 2: Gusto Integration
                                     ├──────────────┤
                                     Phase 3: Payroll + GL
                                                    ├──────────────┤
                                                    Phase 4: Benefits + Reports
```

**Total estimated effort:** 16 weeks (4 months)  
**Team requirement:** 1-2 backend developers + 1 frontend developer  

---

## 11. Risks and Mitigations

| # | Risk | Likelihood | Impact | Mitigation |
|---|------|------------|--------|------------|
| R1 | **QuickBooks account limits** — QB Online has CoA and JournalEntry limits that may not support large K-12 fund accounting | Medium | High | Evaluate QB Enterprise. Consider batching journal entries by fund rather than per-employee. |
| R2 | **Gusto API changes** — Gusto periodically updates API endpoints | Low | Medium | Pin API version. Build abstraction layer (GustoClient). Monitor changelog. |
| R3 | **Tax compliance errors** — Incorrect withholding calculations | Low | Critical | All tax calc handled by Gusto (licensed tax engine). Never calculate taxes in Missio. |
| R4 | **Data sync failures** — Employee data out of sync between Missio and Gusto | Medium | High | Implement reconciliation job (daily). Store gusto_sync_status on EmployeeDetails. Alert on failures. |
| R5 | **Fund split complexity** — Employees funded by 3+ grants with mid-year changes | Medium | Medium | Support effective_date/end_date on allocations. Validate percentages sum to 100%. Log all changes for audit. |
| R6 | **Demo expectation gap** — Client saw demo UI and expects immediate functionality | High | Medium | Clear RFP response scoping what's demo vs functional. Phased delivery plan. |
| R7 | **Payroll deadline pressure** — Payroll must run on schedule; system failures block pay | High | Critical | Gusto has manual fallback (web portal). Missio submits data; Gusto is the processor. If Missio is down, payroll can still run via Gusto directly. |
| R8 | **PII/sensitive data** — SSN, bank accounts, salary data | High | Critical | SSN entered directly in Gusto (never stored in Missio). Bank info managed in Gusto. Salary data encrypted at rest. Role-based access with `view_payroll` permission. |
| R9 | **Concurrent payroll modifications** — Multiple admins editing same payroll run | Medium | Medium | Lock payroll run during processing. Status-based state machine prevents invalid transitions. |
| R10 | **Historical data migration** — CSD may need prior year payroll data imported | Medium | Low | Gusto supports historical payroll import. Coordinate during onboarding. |

---

## 12. Open Questions

| # | Question | Decision Needed By | Owner |
|---|----------|-------------------|-------|
| Q1 | **Gusto vs ADP vs Paylocity** — Final payroll provider selection. Recommend Gusto for speed but ADP may be required for district size. | Before Phase 2 (Week 5) | Project Manager / CSD |
| Q2 | **QuickBooks plan** — Is CSD using QB Online Simple/Plus/Advanced? Advanced needed for journal entries. | Before Phase 3 (Week 9) | Finance team |
| Q3 | **CSD Chart of Accounts** — Need complete list of fund codes, function codes, and object codes used by the district. | Before Phase 1 (Week 1) | CSD Finance Director |
| Q4 | **Salary schedule data** — Need current certified, classified, and admin salary schedules for FY 2025-2026. | Before Phase 1 (Week 1) | CSD HR Director |
| Q5 | **Pay frequency** — Confirm semi-monthly for certified staff, biweekly for classified? Different calendars? | Before Phase 1 (Week 3) | CSD HR Director |
| Q6 | **Georgia TRS reporting** — Does Gusto handle TRS reporting natively, or do we need a separate TRS file export? | Before Phase 4 (Week 13) | CSD Finance / Gusto rep |
| Q7 | **Benefits provider** — Is CSD using Gusto for benefits admin, or keeping existing Georgia SHBP enrollment separate? | Before Phase 4 (Week 13) | CSD HR Director |
| Q8 | **Audit requirements** — What specific audit trail requirements does CSD have for payroll? SOC compliance? | Before Phase 3 (Week 9) | CSD Internal Audit |
| Q9 | **Employee self-service scope** — Should employees view pay stubs in Missio, or redirect to Gusto portal? | Before Phase 4 (Week 14) | Product team |
| Q10 | **Substitute teacher handling** — Are subs on a different pay system, or run through the same payroll? | Before Phase 2 (Week 5) | CSD HR Director |

---

## 13. Current Implementation Status (as of April 7, 2026)

### 13.1 Phase 1 — COMPLETE (Foundation + Demo Data)

All foundational code from Sections 5, 6, and 9 has been implemented:

| Category | Count | Status |
|----------|-------|--------|
| **Migrations** | 13 | All run — includes `benefit_elections` table |
| **Models** | 12 | Position, SalarySchedule, SalaryScheduleStep, EmployeeSalaryPlacement, PayCalendar, PayPeriod, PayrollRun, Paycheck, EmployeeFundAllocation, PayrollJournalEntry, GustoSetting, BenefitElection |
| **Controllers** | 9 | PayrollController, PositionController, SalaryScheduleController, PayCalendarController, PaycheckController, EmployeeFundAllocationController, PayrollJournalEntryController, GustoSettingsController, BenefitPlanController |
| **Services** | 8 | GustoClient, GustoEmployeeService, GustoPayrollService, GustoBenefitsService, GustoWebhookHandler, PayrollOrchestrationService, PayrollJournalEntryService, TimesheetAggregationService |
| **Jobs** | 7 | SyncEmployeeToGusto, SyncEmployeeFromGusto, SubmitPayrollToGusto, FetchPayrollResults, GeneratePayrollJournalEntries, PostPayrollToQuickBooks, AggregateTimesheetHours |
| **Blade Views** | 26 | Dashboard, run detail, reports, positions CRUD, salary grid, pay calendars, paychecks, fund allocations (index+show), journal entries (index+show), Gusto settings, benefits admin, benefits enrollment, benefit election show, compensation statement |
| **DataTable Classes** | 5 | PositionsDataTable, SalarySchedulesDataTable, PayCalendarsDataTable, PaycheckDataTable, PayrollJournalEntriesDataTable |
| **Routes** | 80 lines | `routes/payroll.php` — all payroll routes incl. compensation statement |
| **Config** | 1 | `config/gusto.php` |
| **Observer** | 1 | PayrollRunObserver (auto-dispatches jobs on status changes) |
| **Seeder** | 2 | PayrollPermissionSeeder (12 permissions), CsdPayrollDemoSeeder (full demo data + benefit elections) |
| **Sidebar Menu** | 9 items | Payroll Management, Position Control, Salary Schedules, Pay Calendars, Pay Stubs, Fund Allocations, Journal Entries, Gusto Integration, Documents & Compliance |

### 13.2 Phase 2 — COMPLETE (Benefits Elections, Compensation Statement, Enriched Data)

| Item | What Was Done |
|------|---------------|
| **BenefitElection model + migration** | New `benefit_elections` table with employee_id, enrollment_event, effective_date, benefit_name/type, election status, cost details, provider/plan info |
| **11 benefit election records seeded** | All records from `employee_benefits_elections.csv` — Initial Enrollment (5), Life Event - Married (3), Open Enrollment (3) |
| **Benefits enrollment view** | Replaced placeholder with data-driven view showing elections grouped by enrollment event, with timeline, type badges, cost details |
| **Benefit election show page** | Detail view with summary cards, election details, cost info, employee timeline history |
| **Annual compensation statement** | New view at `/account/benefits/compensation-statement/{employee}` showing direct compensation, employer taxes, employer benefits with exact CSV values for Samantha |
| **Enriched paycheck deductions** | Samantha's `deductions_detail` now includes FSA ($108.33/semi-monthly) and 403(b) (3%) alongside TRS, Health, Dental, and IRS Garnishment |
| **Substitute employees already seeded** | Lauren David (CSD-2001, $175/day certified sub) and Bri Matthew (CSD-2002, $150/day non-cert sub) were already in Phase 1 seeder |
| **Bus monitor hours already seeded** | 10 attendance records for Samantha (Aug 18-29, 2.0-3.5 hrs/day) with correction on Aug 25 were already in Phase 1 seeder |
| **$500 Staff Development Stipend** | Already in Phase 1 seeder — stored in `employee_salary_placements.stipends` JSON and reflected in paycheck `earnings_detail` |

### 13.3 Demo Data Seeded (CsdPayrollDemoSeeder — company_id=105)

| Data | Count | Details |
|------|-------|---------|
| Departments | 10 | CSD K-12 departments (Instruction, Administration, Student Services, etc.) |
| Designations | 20 | K-12 titles (Teacher, Principal, Counselor, Bus Driver, etc.) |
| Employees | 12 | Samantha Lowell (from CSV) + 11 more staff members incl. 2 substitutes |
| Positions | 13 | 10 filled + 3 vacant |
| Salary Schedules | 3 | Certified (26x6 grid), Classified (21x4), Administrative (16x4) = 304 total steps |
| Salary Placements | 10 | One per filled position |
| Pay Calendar | 1 | Semi-monthly, 24 periods, FY 2025-2026 |
| Payroll Runs | 1 | Completed run with full amounts |
| Paychecks | 10 | One per employee in the run, with detailed tax/deduction breakdowns |
| Fund Allocations | 11 | Multi-fund splits for employees |
| Journal Entries | 10 | GL entries split by fund code |
| Benefit Elections | 11 | Samantha's full enrollment history (3 events) |
| Leave Records | per employee | Seeded via CsdHrDemoSeeder |
| Attendance | 210+ records | Seeded via CsdHrDemoSeeder + bus monitor hours |
| Shifts | 5 | Teacher, Admin, Bus, Cafeteria, Day Off (180 schedules) |

### 13.4 Demo CSV Data Alignment

The demo scripts PDF (`public/csv/erp_demo_data_full/`) contains payroll-specific data that represents the CSD demo scenarios. Here is the alignment status:

| CSV File | What It Contains | Seeded? | Gap |
|----------|-----------------|---------|-----|
| `payroll_employee_master.csv` | Samantha Lowell — Media Specialist, Decatur HS, hired 2025-07-18 | **Yes** | Fully seeded |
| `payroll_deductions_and_garnishments.csv` | 6 deductions: GTRS 6%, Health $327.15, Dental $32.27, FSA $108.33, 403(b) 3%, IRS Garnishment $12.50 | **Yes** | All deductions in paycheck `deductions_detail` JSON |
| `payroll_special_payments.csv` | Staff Development Stipend $500 for Samantha | **Yes** | In `employee_salary_placements.stipends` + `earnings_detail` |
| `payroll_substitute_hours.csv` | Lauren David (cert sub $175/day, 2 days=$350), Bri Matthew (non-cert $150/day, 1 day=$150) | **Yes** | Both employees seeded with hourly rates |
| `payroll_bus_monitor_hours_original.csv` | Samantha 10 days bus monitor hours (2.0-3.5 hrs/day) | **Yes** | 10 attendance records seeded |
| `payroll_bus_monitor_hours_correction.csv` | Samantha hour correction: 2.0→3.5 hrs on 2025-08-25, +1.5 adj, off-cycle payroll | **Partial** | Correction applied to attendance record. Off-cycle payroll run not seeded. |
| `employee_benefits_elections.csv` | 11 election records: initial enrollment, life event (marriage 2025-08-15), open enrollment | **Yes** | All 11 records in `benefit_elections` table |
| `employee_benefits_leave_events.csv` | Bereavement (Sept 17-22), PTO (Oct 20-24) | **Yes** | Leave records seeded |
| `employee_benefits_statement_2025.csv` | Annual compensation statement: $33,010 salary + $1,737 PTO + $750 stipend + $11,258 employer costs = $46,755 total | **Yes** | Compensation statement view at `/account/benefits/compensation-statement/{id}` |

---

## 14. Remaining Work — Prioritized by Demo Impact

### Priority 1: CRITICAL for Demo (Must-Have) — ALL COMPLETE

| # | Item | Status | Notes |
|---|------|--------|-------|
| R1 | **Seed substitute employees + paychecks** | **DONE** | Lauren David (CSD-2001) and Bri Matthew (CSD-2002) seeded with hourly rates |
| R2 | **Seed Samantha's detailed deductions** | **DONE** | `deductions_detail` includes TRS 6%, Health $163.58, Dental $32.27, FSA $108.33, 403(b) 3%, IRS Garnishment $12.50 |
| R3 | **Seed Samantha's bus monitor timelogs** | **DONE** | 10 attendance records (Aug 18-29) with correction on Aug 25 |
| R4 | **Seed special payment/stipend** | **DONE** | $500 Staff Development Stipend in `stipends` JSON + `earnings_detail` |
| R5 | **Benefits elections page** | **DONE** | `benefit_elections` migration + model + 11 seeded records. Enrollment view with grouped events, show page with timeline, type badges |
| R6 | **Annual compensation statement** | **DONE** | View at `/account/benefits/compensation-statement/{employee}` showing direct/indirect/employer-paid breakdown matching CSV |

### Priority 2: Important for Production Readiness

| # | Item | Description | Effort |
|---|------|-------------|--------|
| R7 | **Permission gating on all controllers** | Add `permission()` checks to all payroll controllers. Currently removed/missing — fine for demo but required for production. | Medium |
| R8 | **QuickBooks journal entry posting** | `PostPayrollToQuickBooks` job marks entries as posted but doesn't call QB API. Hook into existing `QuickbookController` pattern. | Medium |
| R9 | **Payroll approval workflow** | Integrate PayrollRun into existing `ApprovalController` as an approvable entity type. | Medium |
| R10 | **Bus monitor hour correction + off-cycle payroll** | Support hour adjustments and off-cycle payroll runs. CSV shows a correction scenario (2.0→3.5 hrs). | Medium |
| R11 | **PDF pay stub generation** | `downloadPdf()` returns a printable HTML view. Needs DomPDF or Snappy for actual PDF generation. | Small |

### Priority 3: Nice-to-Have / Future Phases

| # | Item | Description | Effort |
|---|------|-------------|--------|
| R12 | **Gusto sandbox testing** | Actually test OAuth flow and API calls with Gusto developer sandbox account. | Large |
| R13 | **Employee compensation tab** | Add "Compensation" tab to employee profile showing salary placement, fund allocation, pay history. | Medium |
| R14 | **Payroll reports (by fund, by department, YTD)** | Enhanced reporting beyond the basic reports view. | Medium |
| R15 | **W-2 / tax form access** | Surface W-2s from Gusto API for year-end. | Small |
| R16 | **GUSTO_WEBHOOK_SECRET** | Set in `.env` before production. | Small |
| R17 | **Deduction/garnishment management UI** | CRUD for managing deductions/garnishments separate from Gusto. Currently relies on Gusto for all deduction calc. | Large |
| R18 | **Georgia TRS reporting** | Check if Gusto handles TRS natively or need separate export. | Medium |

---

## Appendix A: Existing File Reference Map

### Models (Reusable)
```
app/Models/EmployeeDetails.php       ← Employee master (extend)
app/Models/User.php                  ← User account
app/Models/Team.php                  ← Departments
app/Models/Designation.php           ← Job titles
app/Models/ProjectTimeLog.php        ← Time entries
app/Models/Attendance.php            ← Clock in/out
app/Models/Leave.php                 ← Leave requests
app/Models/LeaveType.php             ← Leave definitions
app/Models/EmployeeLeaveQuota.php    ← Leave balances
app/Models/EmployeeShift.php         ← Shift definitions
app/Models/Budget.php                ← Budget master
app/Models/BudgetAllocation.php      ← Fund accounting (fund_code, account_code)
app/Models/BudgetTransaction.php     ← Encumbrance/expenditure ledger
app/Models/Tax.php                   ← Tax rates
app/Models/Contract.php              ← Employee contracts
app/Models/QuickBooksSetting.php     ← QB OAuth config
```

### Controllers (Extend or Reference)
```
app/Http/Controllers/EmployeeController.php           ← Employee CRUD
app/Http/Controllers/TimelogController.php             ← Time management
app/Http/Controllers/AttendanceController.php          ← Attendance
app/Http/Controllers/LeaveController.php               ← Leave workflow
app/Http/Controllers/BudgetController.php              ← Budget management
app/Http/Controllers/BudgetAllocationController.php    ← Fund allocations
app/Http/Controllers/ApprovalController.php            ← Approval inbox (extend for payroll)
app/Http/Controllers/QuickbookController.php           ← QB sync (extend for journal entries)
app/Http/Controllers/BudgetDemoController.php          ← Demo routes (replace)
app/Http/Controllers/Tracking1099Controller.php        ← 1099 compliance
```

### Views (Replace Demos)
```
resources/views/payroll/demo.blade.php                 ← Replace with real dashboard
resources/views/position-control/demo.blade.php        ← Replace with real CRUD
resources/views/benefits/demo.blade.php                ← Replace with real admin
resources/views/employees/show.blade.php               ← Add Compensation tab
resources/views/employees/ajax/edit.blade.php          ← Add payroll fields
resources/views/sections/menus/payroll.blade.php       ← Update navigation
resources/views/sections/menus/human_resources.blade.php ← Add links
```

### Routes
```
routes/budget.php       ← Contains current demo routes (modify)
routes/web.php          ← Employee, timelog, attendance routes (reference)
routes/payroll.php      ← NEW: all payroll routes
```

---

## Appendix B: Gusto API Quick Reference

**Documentation:** https://docs.gusto.com/embedded-payroll/reference  
**Sandbox:** https://api.gusto-demo.com  
**Production:** https://api.gusto.com  

**Key Concepts:**
- **Company:** Maps to Missio Company (one Gusto company per district)
- **Employee:** Maps to Missio User + EmployeeDetails
- **Payroll:** Maps to Missio PayrollRun
- **Earning Type:** Maps to salary, hourly, stipend, overtime categories
- **Benefit:** Maps to benefit plan enrollment
- **Contractor:** Maps to 1099 vendors (already tracked in Missio)

**Webhook Events to Subscribe:**
- `employee.created`, `employee.updated`
- `payroll.processed`, `payroll.reversed`
- `benefit.enrollment.created`, `benefit.enrollment.updated`

---

*Document prepared for internal planning. Subject to revision based on provider selection and CSD requirements confirmation.*
