# LOA Tracker — Technical Design Document

**Project:** Leave of Absence (LOA) Tracking Tool
**Author:** Eric Dunn
**Date:** 2026-02-24
**Status:** In Development
**Last Updated:** 2026-02-24

---

## Part 1: Onboarding Tracker Analysis

### File Structure
```
app/HR/Onboarding/
├── onboarding.php        — main page (dashboard + inline edit)
├── onboarding_filter.php — filter logic (require'd into onboarding.php)
├── sync_onboarding.php   — AJAX endpoint (UPDATE + soft delete)
└── temp/                 — working drafts, ignore
```

### Authentication Pattern
- Bootstrap: `require __DIR__ . '/../../bootstrap_env.php'`
- Auth gate: `requireLogin()` — redirects to `login.php?returnUrl=...` if not logged in
- Session timeout: 10 hours, checked manually; redirects to `login.php?timeout=1`
- Permissions: query `V_employee_permissions` WHERE `form_name = 'Onboarding'` AND `id = $emp_id`
  - Label `'Edit'` → full edit (`$canEdit = true`)
  - Anything else → `'ReadOnly'` (`$canEdit = false`)
- Session vars injected into JS at top of `<body>`:
  ```js
  const SESSION_USER = ..., SESSION_NAME = ..., SESSION_ID = ..., CAN_EDIT = ...;
  ```

### DB Connection Pattern
Two styles in use (mixed — PDO for permissions, MySQLi for main queries):
```php
// Permissions (PDO)
$pdo = new \PDO("mysql:host=$servername;port=$port;dbname=$dbname;charset=utf8mb4", ...);

// Main queries (MySQLi)
$conn = new mysqli($servername, $username, $password, $dbname);
$conn->set_charset('utf8mb4');
```
Credentials via: `envv('new_servername')`, `envv('new_username')`, `envv('new_password')`, `envv('new_dbname')`, `envv('DB_PORT')`

**LOA tool should use MySQLi consistently** (PDO only needed for permission check).

### CSS / JS Libraries
| Asset | Source |
|---|---|
| `josh_style.css` | Local `../assets/css/` + CDN fallback from portal |
| `ewd_style.css` | Local `../assets/css/` |
| `message-multi.css` | Local + portal fallback |
| jQuery 3.7.1 | cdnjs |
| xlsx.full.min.js | cdnjs (for export) |

No Bootstrap. Custom CSS only. Inline `<style>` blocks used for page-specific overrides.

### Layout Structure
```
<div id="wrapper">
  <div id="containerbox">
    [logo] [H2 title] [welcome/username] [logout]
    <hr>
    [Save All button + count badge]  ← Edit mode only
    <div class="filterbar1">
      <form method="GET">
        <table> [filter columns with <details>/<summary> dropdowns] </table>
      </form>
    </div>
    [Active Filters banner — yellow #fff3cd]
    <table class="hoverTable table-clickable sticky_top">
      [thead][tbody — alternating row-even/row-odd]
    </table>
  </div>
</div>
```

### AJAX Save Pattern (sync_onboarding.php)
- `fetch('sync_onboarding.php', { method: 'POST', body: formData })`
- `FormData` built from all `input`/`select` in the changed row
- `editor = SESSION_USER` appended
- Field change tracking CSS: `field-changed` (yellow), `field-saved` (green flash), `field-error` (red flash)
- Sequential save loop: `saveNext(index)` recursive function
- Remove: soft delete — sets `employee_status = 'Inactive'`, appends to notes

### Key Difference for LOA
Onboarding = **view/edit only** (data fed from JazzHR). LOA = **CREATE + full CRUD**. This requires:
- A new record creation form
- Employee typeahead/search
- Auto-population from `employee_adp`
- INSERT logic (not just UPDATE)

---

## Part 2: LOA Database Analysis

### loa_form — Auto-Population Map
When employee is selected, these fields auto-populate from `employee_adp` + `locationInfo`:

| loa_form field | Source | Table |
|---|---|---|
| `full_name` | `employeeName` | employee_adp |
| `employee_id` | `employeeID` | employee_adp |
| `adp_associate_oid` | `associateOID` | employee_adp |
| `phone` | `phoneNumber` | employee_adp |
| `email` | `workEmail` | employee_adp |
| `job_title` | `jobTitle` | employee_adp |
| `location_id` | `locationCode` | employee_adp |
| `location_fk` | `locationID` | locationInfo |
| `location_name` | `locationName` | locationInfo |
| `manager_name` | `gmName` | locationInfo |

Join path: `employee_adp.locationCode = locationInfo.locationID`

> **Note:** `region`, `area_director`, `regional_director` are **not stored in `loa_form`**.
> They are sourced live from `locationInfo` via `v_loa_dashboard` JOIN on `location_fk`.
> Used for filtering on the dashboard only.

### loa_form — User-Entered Fields by Section

**Leave Details:**
`request_date`, `last_day_worked`, `start_date`, `expected_end_date`, `expected_return_date`, `leave_category` (enum), `leave_type`, `leave_type_code`, `leave_classification` (intermittent/continuous), `details` (reason)

**FMLA Tracking:**
`eligible_for_leave` (bool), `determination_date`, `certification_received` (yes/no/pending), `fmla_hours_used`, `fmla_hours_remaining`

**Benefits / Pay:**
`pto_balance`, `salary_hourly`, `pay_status` (enum), `payment_status_code`, `benefits`, `benefit_options`, `benefits_status` (enum), `cobra_notice_sent_date`

**Status / Admin:**
`leave_status` (enum: pending/approved/denied/active/returned/extended/cancelled), `return_status_code`, `actual_return_date`, `loa_duration_days`, `adp_leave_id`

### loa_documentation — Document Types (11)
`initial_request`, `medical_certification`, `doctors_note`, `return_to_work_letter`, `extension_request`, `cobra_notice`, `benefits_letter`, `fmla_designation_notice`, `fmla_rights_notice`, `intermittent_leave_cert`, `other`

### v_loa_dashboard View
- Joins: `loa_form` LEFT JOIN `employee_adp` (on `adp_associate_oid` COLLATE utf8mb4_general_ci) LEFT JOIN `locationInfo` (on `location_fk`)
- `region`, `area_director`, `regional_director` — sourced from `locationInfo` (li.region, li.adName, li.rdName); not stored in `loa_form`
- `general_manager` — sourced from `locationInfo.gmName`
- Computed: `tenure_days_at_leave_start`, `tenure_years_at_leave_start`
- Doc counts: `docs_received_count`, `docs_pending_count` (subqueries)
- Excludes `leave_status = 'cancelled'`
- Orders: `start_date DESC`

---

## Part 3: Proposed File Structure

```
app/HR/LOA/
├── loa.php                  — dashboard: list/search existing LOA cases
├── loa_new.php              — create new LOA case (employee select + form)
├── loa_edit.php             — edit existing LOA case (all sections + docs)
├── loa_save.php             — AJAX endpoint: INSERT (new) or UPDATE (edit)
├── loa_docs_save.php        — AJAX endpoint: INSERT/UPDATE loa_documentation rows
├── loa_employee_search.php  — AJAX: typeahead search → returns employee JSON
├── loa_filter.php           — filter logic (require'd into loa.php)
└── docs/
    └── Technical-Design-Document.md
```

Matches Onboarding pattern: main page + filter include + sync endpoint(s).

---

## Part 4: Page Layout & Navigation

```
loa.php (Dashboard)
  → "New LOA Case" button → loa_new.php
  → Click row → loa_edit.php?id=X

loa_new.php
  [Step 1] Employee search/select (typeahead)
  [Step 2] Auto-populated employee info (read-only display)
  [Step 3] Leave Details form (user input)
  → Submit → loa_save.php (INSERT) → redirect loa_edit.php?id=X

loa_edit.php?id=X
  [Section: Employee Info]     — read-only display
  [Section: Leave Details]     — inline edit
  [Section: FMLA Tracking]     — inline edit
  [Section: Benefits/Pay]      — inline edit
  [Section: Documentation]     — sub-table of loa_documentation rows
  [Section: Status & Notes]    — inline edit
  → field changes → loa_save.php (UPDATE via AJAX)
  → doc add/edit → loa_docs_save.php (AJAX)
```

---

## Part 5: Employee Selection & Auto-Population Flow

```
User types in search box
  → keyup (debounced 300ms)
  → AJAX GET loa_employee_search.php?q={term}
     → SELECT employeeID, employeeName, associateOID, jobTitle,
              phoneNumber, workEmail, locationCode, status_adp
       FROM employee_adp
       WHERE (employeeName LIKE ? OR employeeID LIKE ?)
         AND status_adp = 'Active'
       LIMIT 20
  → Returns JSON array
  → Dropdown list renders below input

User selects employee
  → AJAX GET loa_employee_search.php?oid={associateOID}&detail=1
     → Full employee + locationInfo join
  → JS populates hidden inputs + display spans:
     full_name, employee_id, adp_associate_oid,
     job_title, phone, email,
     location_id, location_fk, location_name, manager_name
     (region, area_director, regional_director NOT stored — come from view)
  → "Continue to Leave Details" button enables
```

---

## Part 6: Form Sections Detail

### Section 1 — Employee Info (auto-populated, read-only on edit)
Full name, Employee ID, ADP OID, Job Title, Location, Manager, Phone, Email
_(Region, AD, RD not shown on edit form — sourced from locationInfo via view for filtering only)_

### Section 2 — Leave Details
| Field | Type | Notes |
|---|---|---|
| `request_date` | date | defaults today |
| `last_day_worked` | date | required |
| `start_date` | date | required, ≥ last_day_worked |
| `expected_end_date` | date | required |
| `expected_return_date` | date | auto = expected_end_date + 1 |
| `leave_category` | select enum | FMLA/Medical/Personal/Family/Military/Other |
| `leave_classification` | radio | Intermittent / Continuous |
| `leave_type` | text | free-form descriptor |
| `details` | textarea | reason, max 1024 |

### Section 3 — FMLA Tracking
| Field | Type |
|---|---|
| `eligible_for_leave` | checkbox (FMLA eligible?) |
| `determination_date` | date |
| `certification_received` | select: yes/no/pending |
| `fmla_hours_used` | decimal |
| `fmla_hours_remaining` | decimal (auto: 480 − used) |

### Section 4 — Benefits & Pay
| Field | Type |
|---|---|
| `salary_hourly` | select: salary/hourly |
| `pto_balance` | decimal |
| `pay_status` | select enum |
| `benefits_status` | select enum |
| `cobra_notice_sent_date` | date |
| `benefit_options` | text |

### Section 5 — Documentation Sub-Table
Inline table of `loa_documentation` rows:
- Columns: Type, Status, Date Sent, Date Received, Sent By, Received By, Expiration, Notes
- "+ Add Document" row at bottom
- Each row saves via `loa_docs_save.php`

### Section 6 — Status & Admin
| Field | Type |
|---|---|
| `leave_status` | select enum |
| `actual_return_date` | date |
| `return_status_code` | text |
| `loa_duration_days` | computed (start → end) or manual |
| `adp_leave_id` | text |
| `adp_sync_status` | display only |

---

## Part 7: Validations

### Client-side (JS)
- `start_date` ≥ `last_day_worked`
- `expected_end_date` ≥ `start_date`
- `leave_category` required before save
- FMLA: if `leave_category = 'FMLA'`, `eligible_for_leave` must be set
- `fmla_hours_used` ≤ 480

### Server-side (PHP in loa_save.php)
- Employee exists in `employee_adp`
- All required fields present
- Date order validation repeated
- Prepared statements — all user input parameterized
- No INSERT if `adp_associate_oid` already has an active LOA (`leave_status` IN pending/approved/active) — warn user

---

## Part 8: Dashboard (loa.php) Features

- **Filter bar** (matching Onboarding pattern — `filterbar1` + `<details>` dropdowns):
  - leave_status, leave_category, location, region, AD, RD, date range
- **Table columns**: Employee, Location, Leave Type, Start Date, Expected Return, Status, FMLA Eligible, Docs (received/pending), Days Out, Created
- **Sortable** headers (same JS pattern as Onboarding)
- **Color-coded status badges**:
  - pending = grey
  - approved = blue
  - active = green
  - returned = teal
  - denied = red
- **Search bar**: text filter across name/employee_id
- Source: `v_loa_dashboard` view

---

## Part 9: Build Sequence

| Step | File | Task |
|---|---|---|
| 1 | `loa_employee_search.php` | AJAX employee typeahead (JSON endpoint) |
| 2 | `loa_new.php` | New case form (employee select → auto-pop → leave details) |
| 3 | `loa_save.php` | INSERT handler → redirect to edit |
| 4 | `loa_edit.php` | Full edit page, all 6 sections |
| 5 | `loa_save.php` | Add UPDATE branch to save handler |
| 6 | `loa_docs_save.php` | Documentation sub-table AJAX endpoint |
| 7 | `loa.php` | Dashboard list view from v_loa_dashboard |
| 8 | `loa_filter.php` | Filter logic for dashboard |
| 9 | — | Polish: validation, field highlight feedback, status badges |
| 10 | — | Test: create → edit → doc tracking → return workflow |

---

## Part 10: Notes & Flags

- **`onboarding_filter.php`** uses unparameterized `mysqli_real_escape_string` — LOA filter should use prepared statements or parameterized `IN()` clauses instead.
- **`v_loa_dashboard`** join is on `adp_associate_oid` with a `COLLATE` cast — ensure LOA new/edit stores `adp_associate_oid` exactly as it comes from `employee_adp.associateOID`.
- **`loa_form` has `address/city/state/zip`** fields — these appear to be employee home address. Not present in `employee_adp`; determine if needed or remove from scope.
- **`adp_sync_status`** and `adp_leave_id`** — stubs for future ADP API integration; display on edit page but don't block workflow.
- **Permission form name** to register in `V_employee_permissions`: suggest `'LOA'`.
