# CapEx v2 Henry Feedback Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Add four improvements to the CapEx v2 Planning tab: Updated Estimated Cost field, friendly export headers, Last Updated date range filter, and Blendco Order dropdown.

**Architecture:** All changes are isolated to the Planning tab. DB migration adds 2 columns to `capex_planning`. PHP/HTML changes update the CRUD cycle (table display, editor, create form, save handler) and exports. No new files — all changes are edits to existing files.

**Tech Stack:** PHP 8, MySQL, PhpOffice\PhpSpreadsheet, plain JS (no framework)

---

## File Map

| File | Change |
|---|---|
| DB migration | `ALTER TABLE capex_planning` — add 2 columns |
| `api/capex_planning_table.php` | Add new columns to SELECT, update variance formula, render 2 new `<td>` columns, update Grand Total row |
| `support/capex_planning_editor.php` | Add 2 columns to SELECT, lock `estimated_cost` readonly, add `updated_estimated_cost` input, add `blendco_order` select |
| `support/capex_planning_create.php` | Add `updated_estimated_cost` input and `blendco_order` select |
| `support/capex_update_planning.php` | Add 2 new columns to INSERT/UPDATE, update bind_param string |
| `capex_planning_fragment.php` | Add Last Updated date range inputs to filter bar, add 2 new `<th>` headers, add scalar params to JS `refreshPlanningTable()` |
| `api/capex_planning_filters.php` | Add `plan_updated_start`/`plan_updated_end` vars and WHERE clause |
| `export_planning.php` | Replace `fetch_fields()` with column map, add new fields to SELECT, update variance formula |
| `export.php` | Replace `fetch_fields()` with column map |

---

## Task 1: Database Migration

**Files:**
- Modify: DB (run SQL manually or via migration tool)

- [ ] **Step 1: Run the migration**

Connect to the `whitewater` MySQL database and run:

```sql
ALTER TABLE capex_planning
    ADD COLUMN updated_estimated_cost DECIMAL(10,2) NULL DEFAULT NULL
    AFTER estimated_cost;

ALTER TABLE capex_planning
    ADD COLUMN blendco_order ENUM('Yes','No') NULL DEFAULT NULL
    AFTER quote_confirmed;
```

- [ ] **Step 2: Verify**

```sql
DESCRIBE capex_planning;
```

Expected: `updated_estimated_cost` appears after `estimated_cost`; `blendco_order` appears after `quote_confirmed`. Both nullable.

---

## Task 2: Table API — New Columns, Variance, Grand Total

**Files:**
- Modify: `public/finance/capex_v2/api/capex_planning_table.php`

- [ ] **Step 1: Add `updated_estimated_cost` to SELECT**

Find line 17 (the SELECT line beginning with `po_list.estimated_cost`):

Old:
```php
SELECT po_list.ID,po_list.created,po_list.capex_requestor, po_list.market, po_list.store_number, po_list.item, po_list.year_built, po_list.timeline, po_list.deployment_month, po_list.estimated_cost, po_list.committed_uncommitted,
```

New:
```php
SELECT po_list.ID,po_list.created,po_list.capex_requestor, po_list.market, po_list.store_number, po_list.item, po_list.year_built, po_list.timeline, po_list.deployment_month, po_list.estimated_cost, po_list.updated_estimated_cost, po_list.committed_uncommitted,
```

- [ ] **Step 2: Add `blendco_order` to SELECT**

Find line 21 (ends with `po_list.quote_confirmed`):

Old:
```php
, po_list.order_date, po_list.po_number, po_list.notes_maintenance_history, po_list.updated,spend_details.posted_date, po_list.priority, po_list.request_year, po_list.quote_confirmed
```

New:
```php
, po_list.order_date, po_list.po_number, po_list.notes_maintenance_history, po_list.updated,spend_details.posted_date, po_list.priority, po_list.request_year, po_list.quote_confirmed, po_list.blendco_order
```

- [ ] **Step 3: Update variance formula**

Find line 19:

Old:
```php
CASE WHEN (IFNULL(spend_details.spend,0) - IFNULL(spend_details.creds,0)) = 0 THEN 0 ELSE IFNULL(po_list.estimated_cost,0) - (IFNULL(spend_details.spend,0) - IFNULL(spend_details.creds,0)) END AS variance,
```

New:
```php
CASE WHEN (IFNULL(spend_details.spend,0) - IFNULL(spend_details.creds,0)) = 0 THEN 0 ELSE COALESCE(po_list.updated_estimated_cost, po_list.estimated_cost) - (IFNULL(spend_details.spend,0) - IFNULL(spend_details.creds,0)) END AS variance,
```

- [ ] **Step 4: Render Updated Est. Cost column**

Find the `estimated_cost` echo (line 62):

```php
    echo '<td align="center">$' . number_format((float)$row['estimated_cost'], 2, '.', ',') . '</td>';
```

Add immediately after it:

```php
    echo '<td align="center">' . (isset($row['updated_estimated_cost']) && $row['updated_estimated_cost'] !== null ? '$' . number_format((float)$row['updated_estimated_cost'], 2, '.', ',') : '') . '</td>';
```

- [ ] **Step 5: Render Blendco Order column**

Find the `quote_confirmed` echo (line 73):

```php
    echo '<td>' . ($row['quote_confirmed'] ? 'Yes' : 'No') . '</td>';
```

Add immediately after it:

```php
    echo '<td>' . htmlspecialchars((string)($row['blendco_order'] ?? '')) . '</td>';
```

- [ ] **Step 6: Update Grand Total budget accumulation**

Find line 80:

```php
    $grandtotal_budget = $grandtotal_budget + $row['estimated_cost'];
```

Replace with:

```php
    $grandtotal_budget += (isset($row['updated_estimated_cost']) && $row['updated_estimated_cost'] !== null)
        ? (float)$row['updated_estimated_cost']
        : (float)$row['estimated_cost'];
```

- [ ] **Step 7: Update Grand Total row — add 2 empty cells**

Find the Grand Total row block (starts at line 85). Replace the entire block:

Old:
```php
// Grand Total row — 20 columns total: colspan(6) + 3 totals + 11 empty
echo '<tr bgcolor="lightgrey" class="no-click">';
echo '<td colspan="6"><b>Grand Total</b></td>';
echo '<td align="center"><b>$' . number_format((float)$grandtotal_budget, 2, '.', ',') . '</b></td>';
echo '<td align="center"><b>$' . number_format((float)$grandtotal_spent, 2, '.', ',') . '</b></td>';
echo '<td align="center"><b>$' . number_format((float)$grandtotal_variance, 2, '.', ',') . '</b></td>';
echo '<td></td>'; // Capex Type
echo '<td></td>'; // Category
echo '<td></td>'; // Equipment
echo '<td></td>'; // Type
echo '<td></td>'; // Status
echo '<td></td>'; // PO
echo '<td></td>'; // Priority
echo '<td></td>'; // Year
echo '<td></td>'; // Quote Confirmed
echo '<td></td>'; // Last Updated
echo '<td></td>'; // View button
echo '</tr>';
```

New:
```php
// Grand Total row — 22 columns total: colspan(6) + 3 totals + 12 empty
echo '<tr bgcolor="lightgrey" class="no-click">';
echo '<td colspan="6"><b>Grand Total</b></td>';
echo '<td align="center"><b>$' . number_format((float)$grandtotal_budget, 2, '.', ',') . '</b></td>';
echo '<td></td>'; // Updated Est. Cost
echo '<td align="center"><b>$' . number_format((float)$grandtotal_spent, 2, '.', ',') . '</b></td>';
echo '<td align="center"><b>$' . number_format((float)$grandtotal_variance, 2, '.', ',') . '</b></td>';
echo '<td></td>'; // Capex Type
echo '<td></td>'; // Category
echo '<td></td>'; // Equipment
echo '<td></td>'; // Type
echo '<td></td>'; // Status
echo '<td></td>'; // PO
echo '<td></td>'; // Priority
echo '<td></td>'; // Year
echo '<td></td>'; // Quote Confirmed
echo '<td></td>'; // Blendco Order
echo '<td></td>'; // Last Updated
echo '<td></td>'; // View button
echo '</tr>';
```

- [ ] **Step 8: Verify**

Load the Planning tab in browser. Confirm:
- Table has "Updated Est. Cost" column after "Estimated Cost"
- Table has "Blendco Order" column after "Quote Confirmed"
- Grand Total row has correct alignment (budget total still lines up under "Estimated Cost")
- Variance values change for rows that have `updated_estimated_cost` set (test by manually setting a value in DB)

---

## Task 3: Planning Editor — Readonly Estimated Cost, New Fields

**Files:**
- Modify: `public/finance/capex_v2/support/capex_planning_editor.php`

- [ ] **Step 1: Add `updated_estimated_cost` and `blendco_order` to the SELECT**

Find the SELECT clause starting at line 44. The current SELECT ends with `po_list.quote_confirmed`:

Old:
```php
    po_list.estimated_cost, po_list.committed_uncommitted,
```

New:
```php
    po_list.estimated_cost, po_list.updated_estimated_cost, po_list.committed_uncommitted,
```

Also find the last SELECT column `po_list.quote_confirmed` (line 54):

Old:
```php
    po_list.priority, po_list.updated, po_list.whom_updated, po_list.quote_confirmed
```

New:
```php
    po_list.priority, po_list.updated, po_list.whom_updated, po_list.quote_confirmed, po_list.blendco_order
```

- [ ] **Step 2: Keep `estimated_cost` always readonly — remove the `.readOnly=false` line**

In the Edit/Admin JS block (around line 133–158), find:

```js
            document.getElementById('edit_estimated_cost').readOnly=false;
```

Remove that line entirely. `estimated_cost` already has `readonly` on its HTML element and should stay that way for all roles.

- [ ] **Step 3: Add `updated_estimated_cost` to the Edit/Admin JS unlock block**

In the same Edit/Admin block, after the line:

```js
            document.getElementById('edit_year_built').readOnly=false;
```

Add:

```js
            document.getElementById('edit_updated_estimated_cost').readOnly=false;
```

- [ ] **Step 4: Add `blendco_order` to the Edit/Admin JS unlock block**

In the same Edit/Admin block, after the line:

```js
            document.getElementById('edit_quote_confirmed').removeAttribute('readonly');
```

Add:

```js
            document.getElementById('edit_blendco_order').removeAttribute('readonly');
```

- [ ] **Step 5: Add Updated Est. Cost input to the form HTML**

Find the `estimated_cost` input `<td>` block (around line 277–279):

```php
                        </td><td>
                            <label for="edit_estimated_cost">Estimated Cost</label><br>
                            <input type="text" id="edit_estimated_cost" name="estimated_cost" value="<?php echo number_format((float)($edit_entry['estimated_cost'] ?? 0),2,'.',','); ?>" class="inputs" readonly>
                        </td><td>
```

Insert a new `<td>` block immediately after the `estimated_cost` `</td>`:

```php
                        </td><td>
                            <label for="edit_estimated_cost">Estimated Cost</label><br>
                            <input type="text" id="edit_estimated_cost" name="estimated_cost" value="<?php echo number_format((float)($edit_entry['estimated_cost'] ?? 0),2,'.',','); ?>" class="inputs" readonly>
                        </td><td>
                            <label for="edit_updated_estimated_cost">Updated Est. Cost</label><br>
                            <input type="text" id="edit_updated_estimated_cost" name="updated_estimated_cost" value="<?php echo $edit_entry['updated_estimated_cost'] !== null ? number_format((float)$edit_entry['updated_estimated_cost'],2,'.',',') : ''; ?>" class="inputs" readonly>
                        </td><td>
```

- [ ] **Step 6: Add Blendco Order select to the form HTML**

Find the `quote_confirmed` `<td>` block (lines 358–365):

```php
                        <td>
                            <label for="edit_quote_confirmed">Quote Confirmed</label><br>
                            <select id="edit_quote_confirmed" name="quote_confirmed" class="inputs" readonly>
                                <option value="<?php echo (int)($edit_entry['quote_confirmed'] ?? 0);?>"><?php echo $edit_entry['quote_confirmed'] ? 'Yes' : 'No';?></option>
                                <option value="1">Yes</option>
                                <option value="0">No</option>
                            </select>
                        </td>
                    </tr>
```

Replace with:

```php
                        <td>
                            <label for="edit_quote_confirmed">Quote Confirmed</label><br>
                            <select id="edit_quote_confirmed" name="quote_confirmed" class="inputs" readonly>
                                <option value="<?php echo (int)($edit_entry['quote_confirmed'] ?? 0);?>"><?php echo $edit_entry['quote_confirmed'] ? 'Yes' : 'No';?></option>
                                <option value="1">Yes</option>
                                <option value="0">No</option>
                            </select>
                        </td>
                        <td>
                            <label for="edit_blendco_order">Blendco Order</label><br>
                            <select id="edit_blendco_order" name="blendco_order" class="inputs" readonly>
                                <option value="">— Select —</option>
                                <option value="Yes" <?= ($edit_entry['blendco_order'] ?? '') === 'Yes' ? 'selected' : '' ?>>Yes</option>
                                <option value="No"  <?= ($edit_entry['blendco_order'] ?? '') === 'No'  ? 'selected' : '' ?>>No</option>
                            </select>
                        </td>
                    </tr>
```

- [ ] **Step 7: Verify**

Open the editor for an existing record. Confirm:
- "Estimated Cost" field is read-only (grayed out) for Edit/Admin users
- "Updated Est. Cost" field is editable for Edit/Admin, empty if NULL in DB
- "Blendco Order" dropdown appears after Quote Confirmed, shows current value

---

## Task 4: Create Form — New Fields

**Files:**
- Modify: `public/finance/capex_v2/support/capex_planning_create.php`

- [ ] **Step 1: Add Updated Est. Cost input**

Find the `estimated_cost` input `<td>` block (around line 173–176):

```php
                    </td><td>
                        <label for="edit_estimated_cost">Estimated Cost</label><br>
                        <input type="text" id="edit_estimated_cost" name="estimated_cost"  class="inputs" required>
                    </td><td>
```

Insert a new `<td>` immediately after:

```php
                    </td><td>
                        <label for="edit_estimated_cost">Estimated Cost</label><br>
                        <input type="text" id="edit_estimated_cost" name="estimated_cost"  class="inputs" required>
                    </td><td>
                        <label for="edit_updated_estimated_cost">Updated Est. Cost</label><br>
                        <input type="number" id="edit_updated_estimated_cost" name="updated_estimated_cost" step="0.01" class="inputs">
                    </td><td>
```

- [ ] **Step 2: Add Blendco Order select**

Find the row containing `edit_priority` and `whom_updated` (around lines 239–246). The `</tr>` closing that row is immediately before the Notes `<tr>`. Insert a new row between them:

Find:
```php
                    </td>
                </tr><tr>
                    <td colspan="3">
                        <label for="edit_notes_maintenance_history">Notes/Maintenance History</label><br>
```

Replace with:
```php
                    </td>
                </tr><tr>
                    <td>
                        <label for="edit_blendco_order">Blendco Order</label><br>
                        <select id="edit_blendco_order" name="blendco_order" class="inputs">
                            <option value="">— Select —</option>
                            <option value="Yes">Yes</option>
                            <option value="No">No</option>
                        </select>
                    </td>
                </tr><tr>
                    <td colspan="3">
                        <label for="edit_notes_maintenance_history">Notes/Maintenance History</label><br>
```

- [ ] **Step 3: Verify**

Open the Create New Planning Request form. Confirm:
- "Updated Est. Cost" number input appears after "Estimated Cost" (optional, no `required`)
- "Blendco Order" dropdown appears before the Notes field

---

## Task 5: Save Handler — New Columns

**Files:**
- Modify: `public/finance/capex_v2/support/capex_update_planning.php`

- [ ] **Step 1: Add variable declarations**

After line 63 (`$quote_confirmed = ...`), add:

```php
    $updated_estimated_cost = !empty($_POST['updated_estimated_cost']) ? str_replace(',', '', $_POST['updated_estimated_cost']) : NULL;
    $blendco_order = !empty($_POST['blendco_order']) ? $_POST['blendco_order'] : NULL;
```

- [ ] **Step 2: Add columns to the INSERT statement**

Find the INSERT column list (lines 66–69):

Old:
```php
    INSERT INTO whitewater.capex_planning
    (id, capex_requestor, market, store_number, item, year_built, timeline, deployment_month, estimated_cost,
     committed_uncommitted, actual_cost, variance, carryover, capex_type, capex_category, equipment_type, `type`,
     status, order_date, po_number, notes_maintenance_history, priority, whom_updated, quote_confirmed)
```

New:
```php
    INSERT INTO whitewater.capex_planning
    (id, capex_requestor, market, store_number, item, year_built, timeline, deployment_month, estimated_cost,
     updated_estimated_cost, committed_uncommitted, actual_cost, variance, carryover, capex_type, capex_category,
     equipment_type, `type`, status, order_date, po_number, notes_maintenance_history, priority, whom_updated,
     quote_confirmed, blendco_order)
```

- [ ] **Step 3: Add VALUES placeholders**

Find the VALUES line (line 71):

Old:
```php
    (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
```

New (26 placeholders):
```php
    (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
```

- [ ] **Step 4: Add ON DUPLICATE KEY UPDATE clauses**

After `quote_confirmed = VALUES(quote_confirmed)` (line 95), add:

```php
        updated_estimated_cost = VALUES(updated_estimated_cost),
        blendco_order = VALUES(blendco_order)
```

Full updated ON DUPLICATE KEY UPDATE block:
```php
    ON DUPLICATE KEY UPDATE
        capex_requestor = VALUES(capex_requestor),
        market = VALUES(market),
        store_number = VALUES(store_number),
        item = VALUES(item),
        year_built = VALUES(year_built),
        timeline = VALUES(timeline),
        deployment_month = VALUES(deployment_month),
        estimated_cost = VALUES(estimated_cost),
        updated_estimated_cost = VALUES(updated_estimated_cost),
        committed_uncommitted = VALUES(committed_uncommitted),
        actual_cost = VALUES(actual_cost),
        variance = VALUES(variance),
        carryover = VALUES(carryover),
        capex_type = VALUES(capex_type),
        capex_category = VALUES(capex_category),
        equipment_type = VALUES(equipment_type),
        `type` = VALUES(`type`),
        status = VALUES(status),
        order_date = VALUES(order_date),
        po_number = VALUES(po_number),
        notes_maintenance_history = VALUES(notes_maintenance_history),
        priority = VALUES(priority),
        whom_updated = VALUES(whom_updated),
        quote_confirmed = VALUES(quote_confirmed),
        blendco_order = VALUES(blendco_order)
```

- [ ] **Step 5: Update bind_param**

Current (line 101):

Old:
```php
    $stmt->bind_param(
        "issssssssssssssssssssssi",
        $id, $capex_requestor, $market, $store_number, $item, $year_built, $timeline, $deployment_month,
        $estimated_cost, $committed_uncommitted, $actual_cost, $variance, $carryover,
        $capex_type, $capex_category, $equipment_type, $type, $status,
        $order_date, $po_number, $notes_maintenance_history, $priority, $whom_updated, $quote_confirmed
    );
```

New (26 params: i + 23s + i + s = `isssssssssssssssssssssssis`):
```php
    $stmt->bind_param(
        "isssssssssssssssssssssssis",
        $id, $capex_requestor, $market, $store_number, $item, $year_built, $timeline, $deployment_month,
        $estimated_cost, $updated_estimated_cost, $committed_uncommitted, $actual_cost, $variance, $carryover,
        $capex_type, $capex_category, $equipment_type, $type, $status,
        $order_date, $po_number, $notes_maintenance_history, $priority, $whom_updated, $quote_confirmed,
        $blendco_order
    );
```

- [ ] **Step 6: Verify**

Open an existing record in the editor. Set `updated_estimated_cost` to a value and `blendco_order` to "Yes". Click Save. Confirm:
- Success message appears
- Re-open editor: values persisted
- Create a new record: both fields save correctly (Updated Est. Cost optional, Blendco Order can be blank)

---

## Task 6: Fragment — New Headers, Last Updated Filter, JS params

**Files:**
- Modify: `public/finance/capex_v2/capex_planning_fragment.php`

- [ ] **Step 1: Add Updated Est. Cost table header**

Find in `<thead>` (line 346):

```html
        <th>Estimated Cost</th>
        <th>Actual Cost</th>
```

Replace with:

```html
        <th>Estimated Cost</th>
        <th>Updated Est. Cost</th>
        <th>Actual Cost</th>
```

- [ ] **Step 2: Add Blendco Order table header**

Find (line 358):

```html
        <th>Quote Confirmed</th>
        <th>Last Updated</th>
```

Replace with:

```html
        <th>Quote Confirmed</th>
        <th>Blendco Order</th>
        <th>Last Updated</th>
```

- [ ] **Step 3: Add Last Updated date range filter inputs**

Find the Quote Confirmed filter `</div>` closing tag at the end of the second filter row (line 317–318):

```php
                </div>
            </div>
            <div class="capex-filter-row capex-filter-row--actions-bottom">
```

Insert a new filter cell before the `</div>` that closes the second filter row:

```php
                </div>
                <div class="capex-filter-cell">
                    <!-- Last Updated Filter -->
                    <b>Last Updated</b><br>
                    <input type="date" name="plan_updated_start" id="plan_updated_start" class="inputs_filter" value="<?php echo htmlspecialchars($plan_updated_start ?? ''); ?>"> -
                    <input type="date" name="plan_updated_end" id="plan_updated_end" class="inputs_filter" value="<?php echo htmlspecialchars($plan_updated_end ?? ''); ?>">
                </div>
            </div>
            <div class="capex-filter-row capex-filter-row--actions-bottom">
```

- [ ] **Step 4: Add Last Updated params to `refreshPlanningTable()` JS**

Find in `refreshPlanningTable()` (around line 401):

```js
        const scalarKeys = ['plan_po', 'plan_startdate_post', 'plan_enddate_post'];
```

Replace with:

```js
        const scalarKeys = ['plan_po', 'plan_startdate_post', 'plan_enddate_post', 'plan_updated_start', 'plan_updated_end'];
```

- [ ] **Step 5: Verify**

Load the Planning tab. Confirm:
- Table has "Updated Est. Cost" header after "Estimated Cost"
- Table has "Blendco Order" header after "Quote Confirmed"
- "Last Updated" date range inputs appear in the filter bar
- Selecting dates and clicking Filter updates the URL with `plan_updated_start` and `plan_updated_end` params

---

## Task 7: Filter Backend — Last Updated WHERE Clause

**Files:**
- Modify: `public/finance/capex_v2/api/capex_planning_filters.php`

- [ ] **Step 1: Add variable declarations**

After line 15 (`$plan_enddate_post = ...`), add:

```php
$plan_updated_start = $_GET['plan_updated_start'] ?? '';
$plan_updated_end   = $_GET['plan_updated_end'] ?? '';
```

- [ ] **Step 2: Add WHERE clause**

After the Posted Date Range filter block (lines 105–109):

```php
// Date Range Filter (Post Date)  ------------------------------------------------
if (!empty($plan_startdate_post) && !empty($plan_enddate_post)) {
    $plan_query_search .= ' AND spend_details.posted_date BETWEEN "' . $plan_startdate_post . '" AND "' . $plan_enddate_post . '" ';
    $plan_filtered .= " Posted Date Between (".$plan_startdate_post." AND ".$plan_enddate_post.")";
}
```

Add immediately after:

```php
// Last Updated Filter ------------------------------------------------
if (!empty($plan_updated_start) && !empty($plan_updated_end)) {
    $plan_query_search .= ' AND po_list.updated >= "' . $plan_updated_start . '" AND po_list.updated < DATE_ADD("' . $plan_updated_end . '", INTERVAL 1 DAY) ';
    $plan_filtered .= " Last Updated Between (".$plan_updated_start." AND ".$plan_updated_end.")";
}
```

- [ ] **Step 3: Verify**

On the Planning tab, enter a Last Updated date range and click Filter. Confirm:
- Only rows with `updated` timestamps within the range appear
- "Applied Filtering" label shows the Last Updated range
- Clearing the filter restores all rows
- End date is inclusive (a row updated exactly on the end date appears)

---

## Task 8: Planning Export — Column Map, New Fields, Variance

**Files:**
- Modify: `public/finance/capex_v2/export_planning.php`

- [ ] **Step 1: Add `updated_estimated_cost` and `blendco_order` to SELECT**

Find the SELECT in `$sql` (lines 31–41):

Old:
```php
SELECT po_list.ID, po_list.created, po_list.capex_requestor, po_list.market, po_list.store_number,
    po_list.item, po_list.year_built, po_list.timeline, po_list.deployment_month,
    po_list.estimated_cost, po_list.committed_uncommitted,
    (IFNULL(spend_details.spend,0) - IFNULL(spend_details.creds,0)) AS actual_cost,
    CASE WHEN (IFNULL(spend_details.spend,0) - IFNULL(spend_details.creds,0)) = 0 THEN 0
         ELSE IFNULL(po_list.estimated_cost,0) - (IFNULL(spend_details.spend,0) - IFNULL(spend_details.creds,0))
    END AS variance,
    po_list.carryover, po_list.capex_type, po_list.capex_category, po_list.equipment_type,
    po_list.type, po_list.status, po_list.order_date, po_list.po_number,
    po_list.request_year, po_list.priority, po_list.quote_confirmed,
    po_list.notes_maintenance_history, po_list.updated
```

New (adds `updated_estimated_cost`, updated variance, adds `blendco_order`):
```php
SELECT po_list.ID, po_list.created, po_list.capex_requestor, po_list.market, po_list.store_number,
    po_list.item, po_list.year_built, po_list.timeline, po_list.deployment_month,
    po_list.estimated_cost, po_list.updated_estimated_cost, po_list.committed_uncommitted,
    (IFNULL(spend_details.spend,0) - IFNULL(spend_details.creds,0)) AS actual_cost,
    CASE WHEN (IFNULL(spend_details.spend,0) - IFNULL(spend_details.creds,0)) = 0 THEN 0
         ELSE COALESCE(po_list.updated_estimated_cost, po_list.estimated_cost) - (IFNULL(spend_details.spend,0) - IFNULL(spend_details.creds,0))
    END AS variance,
    po_list.carryover, po_list.capex_type, po_list.capex_category, po_list.equipment_type,
    po_list.type, po_list.status, po_list.order_date, po_list.po_number,
    po_list.request_year, po_list.priority, po_list.quote_confirmed, po_list.blendco_order,
    po_list.notes_maintenance_history, po_list.updated
```

- [ ] **Step 2: Replace `fetch_fields()` block with column map**

Find lines 51–57:

```php
// Fetch column names and set as headers
$fields = $result->fetch_fields();
$col = 'A';
foreach ($fields as $field) {
    $sheet->setCellValue($col . '1', $field->name);
    $col++;
}
```

Replace with:

```php
$column_map = [
    'ID'                       => 'ID',
    'created'                  => 'Created',
    'capex_requestor'          => 'Requestor',
    'market'                   => 'Market',
    'store_number'             => 'Store #',
    'item'                     => 'Item',
    'year_built'               => 'Year Built',
    'timeline'                 => 'Timeline',
    'deployment_month'         => 'Month Deployed',
    'estimated_cost'           => 'Estimated Cost',
    'updated_estimated_cost'   => 'Updated Est. Cost',
    'committed_uncommitted'    => 'Commitment',
    'actual_cost'              => 'Actual Cost',
    'variance'                 => 'Variance',
    'carryover'                => 'Carryover',
    'capex_type'               => 'Capex Type',
    'capex_category'           => 'Category',
    'equipment_type'           => 'Equipment',
    'type'                     => 'Type',
    'status'                   => 'Status',
    'order_date'               => 'Order Date',
    'po_number'                => 'PO Number',
    'request_year'             => 'Year',
    'priority'                 => 'Priority',
    'quote_confirmed'          => 'Quote Confirmed',
    'blendco_order'            => 'Blendco Order',
    'notes_maintenance_history'=> 'Notes',
    'updated'                  => 'Last Updated',
];

// Write header row from column map
$col = 'A';
foreach ($column_map as $header) {
    $sheet->setCellValue($col . '1', $header);
    $col++;
}
```

- [ ] **Step 3: Update data row loop to use column map**

Find lines 60–67:

```php
// Fetch rows and write to Excel
$rowNum = 2;
while ($row = $result->fetch_assoc()) {
    $col = 'A';
    foreach ($row as $cell) {
        $sheet->setCellValue($col . $rowNum, $cell);
        $col++;
    }
    $rowNum++;
}
```

Replace with:

```php
// Fetch rows and write to Excel using column map order
$rowNum = 2;
while ($row = $result->fetch_assoc()) {
    $col = 'A';
    foreach ($column_map as $db_col => $header) {
        $sheet->setCellValue($col . $rowNum, $row[$db_col] ?? '');
        $col++;
    }
    $rowNum++;
}
```

- [ ] **Step 4: Verify**

Click "Export View" on the Planning tab. Open the downloaded .xlsx. Confirm:
- Header row shows friendly names (Requestor, Market, Store #, etc.)
- "Updated Est. Cost" column appears after "Estimated Cost"
- "Blendco Order" column appears after "Quote Confirmed"
- Column order matches the map
- Variance values use updated_estimated_cost when available

---

## Task 9: Tracker Export — Column Map

**Files:**
- Modify: `public/finance/capex_v2/export.php`

- [ ] **Step 1: Replace `fetch_fields()` block with column map**

Find lines 38–43:

```php
// Fetch column names and set as headers
$fields = $result->fetch_fields();
$col = 'A';
foreach ($fields as $field) {
    $sheet->setCellValue($col . '1', $field->name);
    $col++;
}
```

Replace with:

```php
$column_map = [
    'ID'                 => 'ID',
    'cogID'              => 'Cog ID',
    'status'             => 'Status',
    'projectName'        => 'Project Name',
    'requestor'          => 'Requestor',
    'locationName'       => 'Location',
    'PO'                 => 'PO',
    'acct_status'        => 'Acct Status',
    'facilities_status'  => 'Facilities Status',
    'totalbudget'        => 'Total Budget',
    'spent'              => 'Spent',
    'variance'           => 'Variance',
    'reconcile'          => 'Reconcile',
    'maintainxID'        => 'MaintainX ID',
    'posted_date'        => 'Posted Date',
];

// Write header row from column map
$col = 'A';
foreach ($column_map as $header) {
    $sheet->setCellValue($col . '1', $header);
    $col++;
}
```

- [ ] **Step 2: Update data row loop to use column map**

Find lines 46–53:

```php
// Fetch rows and write to Excel
$row_num = 2;
while ($row = $result->fetch_assoc()) {
    $col = 'A';
    foreach ($row as $cell) {
        $sheet->setCellValue($col . $row_num, $cell);
        $col++;
    }
    $row_num++;
}
```

Replace with:

```php
// Fetch rows and write to Excel using column map order
$row_num = 2;
while ($row = $result->fetch_assoc()) {
    $col = 'A';
    foreach ($column_map as $db_col => $header) {
        $sheet->setCellValue($col . $row_num, $row[$db_col] ?? '');
        $col++;
    }
    $row_num++;
}
```

- [ ] **Step 3: Verify**

Click the Tracker tab Export button. Open the downloaded .xlsx. Confirm:
- Header row shows friendly names (Cog ID, Project Name, Requestor, etc.)
- Column order matches the map
- Data rows are complete and correctly aligned with headers

---

## Smoke Test Checklist

After all tasks are complete:

- [ ] DB: `DESCRIBE capex_planning` shows `updated_estimated_cost` and `blendco_order` columns
- [ ] Table: "Updated Est. Cost" column renders after "Estimated Cost"; empty for NULL rows
- [ ] Table: "Blendco Order" column renders after "Quote Confirmed"; empty for NULL rows
- [ ] Table: Variance formula uses `updated_estimated_cost` when set (verify with a test record)
- [ ] Grand Total: budget total uses `updated_estimated_cost` when available
- [ ] Editor: `estimated_cost` is read-only for all roles including Edit/Admin
- [ ] Editor: `updated_estimated_cost` is editable for Edit/Admin, saves and persists
- [ ] Editor: `blendco_order` dropdown saves "Yes"/"No"/blank and persists
- [ ] Create form: both new fields present, `updated_estimated_cost` optional, `blendco_order` defaults to blank
- [ ] Last Updated filter: filtering by date range narrows results correctly, end date inclusive
- [ ] Planning export: friendly headers, correct column order, new columns present
- [ ] Tracker export: friendly headers, correct column order
