# Gap Analysis Report: SOW_03 My Merchants v2

**Date:** 2026-03-25
**SOW File:** `/var/www/html/Claude Docs/SOW_03_My_Merchants_v2.docx`
**Codebase:** `/var/www/html/hubwallet-crm`
**Branch:** `develop`
**Analyst:** Claude Code (automated analysis)

---

## Executive Summary

**Overall SOW Completion: ~28%**

The My Merchants module has a working foundation (portfolio listing, merchant detail with 8 tabs), but 8 of the 11 sub-pages are complete stubs containing only a subtitle. The merchant detail view is missing 3 required tabs (Authorizations, Memos, 1099K) and the critical KPI cards carousel on the Batches tab.

---

## Summary Metrics

| Metric | Count |
|--------|-------|
| **Total Gap Items** | 106 |
| Critical | 38 |
| High | 42 |
| Medium | 20 |
| Low | 6 |
| **Missing** | 97 |
| **Partial** | 9 |

---

## Progress Tracker

| SOW Section | Description | Total Gaps | Done | Remaining | % Done |
|-------------|-------------|-----------|------|-----------|--------|
| §3 | Portfolio Overview (Main Page) | 21 | 0 | 21 | 0% |
| §4 | Merchant Detail View | 40 | 5 | 35 | 13% |
| §5 | Portfolio Activity | 12 | 0 | 12 | 0% |
| §6 | Merchant Tracker | 10 | 0 | 10 | 0% |
| §7 | The Scoop | 10 | 0 | 10 | 0% |
| §8 | Deposit Exceptions | 9 | 0 | 9 | 0% |
| §9 | Dispute Reporting | 9 | 0 | 9 | 0% |
| §10 | Risk Reporting | 6 | 0 | 6 | 0% |
| §11 | Risk Alerts | 7 | 0 | 7 | 0% |
| §12 | Merchant Management | 10 | 0 | 10 | 0% |
| §13 | Merchant Importer | 8 | 0 | 8 | 0% |
| §14 | Business Rules | 9 | 0 | 9 | 0% |
| **TOTAL** | | **151** | **5** | **146** | **~3%** |

> **Mark items complete** by changing `- [ ]` to `- [x]` as each gap is resolved.

---

## Tech Stack Reference

- **Framework:** Laravel (PHP 8.x), Blade templates
- **Frontend:** Bootstrap 5, ApexCharts, Select2, AJAX (jQuery)
- **DB:** MySQL with Eloquent ORM, soft deletes
- **Teal color:** `#34c38f`
- **Key base paths:**
  - Views: `resources/views/admin/`
  - My Merchants views: `resources/views/admin/my-merchants/`
  - Merchant sub-page views: `resources/views/admin/merchants/`
  - Controllers: `app/Http/Controllers/Merchants/`
  - Models: `app/Models/Merchants/`
  - Public JS: `public/assets/js/`

---

## New Files Required

| File | Purpose |
|------|---------|
| `database/migrations/XXXX_create_merchant_memos_table.php` | Memos tab data store |
| `database/migrations/XXXX_create_merchant_statements_table.php` | Statements tab data store |
| `database/migrations/XXXX_create_merchant_risk_alert_configs_table.php` | Risk alert threshold config |
| `app/Models/Merchants/MerchantMemo.php` | Memo model |
| `app/Models/Merchants/MerchantStatement.php` | Statement model |
| `app/Models/Merchants/MerchantRiskAlertConfig.php` | Risk alert config model |
| `resources/views/admin/my-merchants/partials/authorizations.blade.php` | Authorizations tab |
| `resources/views/admin/my-merchants/partials/memos.blade.php` | Memos tab |
| `resources/views/admin/my-merchants/partials/1099k.blade.php` | 1099K tab |
| `app/Services/MerchantImportService.php` | CSV import service |
| `public/assets/js/merchant-importer.js` | Importer wizard JS |
| `public/assets/js/portfolio-activity.js` | Portfolio Activity charts/filters JS |
| `public/assets/js/the-scoop.js` | The Scoop charts JS |

---

## Recommended Implementation Order

| Phase | Focus | Gap IDs |
|-------|-------|---------|
| **Phase 1** | Critical missing tabs + KPI cards | GAP-4.5, 4.6, 4.7, 4.9 |
| **Phase 2** | Portfolio Overview fixes | GAP-3.1–3.21 |
| **Phase 3** | Stub pages (high business value) | GAP-5.x, 6.x, 9.x, 10.x |
| **Phase 4** | Stub pages (continued) | GAP-7.x, 8.x, 11.x |
| **Phase 5** | Merchant Importer wizard | GAP-13.x |
| **Phase 6** | Business rules, Statements, Merchant Mgmt | GAP-12.x, 14.x, 4.37 |

---
---

# §3 — Portfolio Overview (My Merchants Main Page)

---

### [GAP-3.1] Date range shown in teal in page title

- [ ] **[GAP-3.1]** Add active date range (e.g., "3/1/2026 – 3/31/2026") displayed in teal text beside the "My Merchants" page title
  - **Status:** Missing
  - **Priority:** High
  - **Effort:** S
  - **Files:** `resources/views/admin/my-merchants/index.blade.php`, `app/Http/Controllers/Merchants/MyMerchantController.php`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Read `resources/views/admin/my-merchants/index.blade.php` to find the page title element.
> 2. Read `app/Http/Controllers/Merchants/MyMerchantController.php` index method to see how filters are passed.
> 3. In `MyMerchantController.php` index method, compute `$dateRangeLabel` from the current date filter (default: first and last day of current month, formatted as `M/D/YYYY – M/D/YYYY`). Pass it to the view.
> 4. In `index.blade.php`, after the page title 'My Merchants', add a `<span>` with class `text-teal ms-2 fs-6` showing `{{ $dateRangeLabel }}`. Teal color is `#34c38f` (use Bootstrap class `text-success` or add inline style `color:#34c38f`).
> 5. When the user applies a custom date range filter, this label must update to reflect the selected range. If date range is dynamic (AJAX-filtered), also update it in the AJAX response JSON and update it client-side via JS."

---

### [GAP-3.2] Toggle switches (not checkboxes) on card type rows

- [ ] **[GAP-3.2]** Replace checkboxes in Totals by Card Type table with Bootstrap toggle switches
  - **Status:** Partial (checkboxes exist, need switch style)
  - **Priority:** Medium
  - **Effort:** S
  - **Files:** `resources/views/admin/my-merchants/index.blade.php`, `public/assets/js/my-merchants-portfolio.js`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Read `resources/views/admin/my-merchants/index.blade.php` and find the Totals by Card Type table's checkbox inputs.
> 2. Change each checkbox from `<input type='checkbox'>` to a Bootstrap 5 form-switch: wrap each in `<div class='form-check form-switch'>` and add class `form-check-input` to the input. Remove any custom checkbox styling.
> 3. The toggle must still trigger the same `card_types` filter AJAX call as the existing checkboxes. Read `public/assets/js/my-merchants-portfolio.js` and ensure the change event listener targets `.form-check-input` within the card type table.
> 4. Each toggle should be checked by default (all card types enabled). The Total row at the bottom should NOT have a toggle — it shows aggregate totals only."

---

### [GAP-3.3] Card brand logos in card type table

- [ ] **[GAP-3.3]** Add brand logo images for Visa, Mastercard, American Express, Discover in the Totals by Card Type table
  - **Status:** Missing
  - **Priority:** Medium
  - **Effort:** S
  - **Files:** `resources/views/admin/my-merchants/index.blade.php`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Check `public/assets/images/` for any existing card brand logos (visa.png, mastercard.png, amex.png, discover.png or similar).
> 2. If logos exist, in `resources/views/admin/my-merchants/index.blade.php`, in the Totals by Card Type table, add an `<img>` tag with the brand logo (height: 20px) in the Card Type column for Visa, Mastercard, American Express, and Discover rows. EBT, Pin-Debit, Fleet Cards, and Other rows show no logo (text only).
> 3. If logos do not exist, use publicly available SVG card logos from a CDN like `https://cdn.jsdelivr.net/gh/aaronfagan/svg-credit-card-payment-icons/` or use Font Awesome / Bootstrap Icons card icons as fallback.
> 4. Logos should be placed in a new `<td>` or inside the card type name `<td>` as an inline element before the text."

---

### [GAP-3.4] EBT, Pin-Debit, Fleet Cards, Other card type rows

- [ ] **[GAP-3.4]** Add EBT, Pin-Debit, Fleet Cards, and Other rows to the Totals by Card Type table
  - **Status:** Missing
  - **Priority:** High
  - **Effort:** M
  - **Files:** `resources/views/admin/my-merchants/index.blade.php`, `app/Models/Merchants/MerchantTransaction.php`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Read `app/Models/Merchants/MerchantTransaction.php` and find `getCardTypesSummeryForMerchants()` method.
> 2. The current method returns card type data. Extend it to also group and aggregate for card_type values: 'EBT', 'Pin-Debit' (or 'PIN_DEBIT'), 'Fleet' (or 'FLEET'), and 'Other' (anything not in Visa/MC/Amex/Discover/EBT/Pin-Debit/Fleet).
> 3. In `app/Http/Controllers/Merchants/MyMerchantController.php` index method, ensure `$cardTypesSummery` includes these new types.
> 4. In `resources/views/admin/my-merchants/index.blade.php`, in the card type table, add rows for EBT, Pin-Debit, Fleet Cards, and Other — each with a toggle switch, card type name (no logo for these), Total $, and Transactions count. These should appear after the four branded card rows and before the Total aggregate row.
> 5. EBT, Pin-Debit, Fleet Cards, Other rows should be included in the `card_types` filter array so they can be toggled on/off."

---

### [GAP-3.5] Info tooltip on Total row of card type table

- [ ] **[GAP-3.5]** Add an info (?) tooltip icon on the Total aggregate row of the card type table
  - **Status:** Missing
  - **Priority:** Low
  - **Effort:** XS
  - **Files:** `resources/views/admin/my-merchants/index.blade.php`

> **Claude Prompt:**
> "In `resources/views/admin/my-merchants/index.blade.php`, find the Total aggregate row at the bottom of the Totals by Card Type table. Add a Bootstrap tooltip icon: `<i class='mdi mdi-information-outline ms-1 text-muted' data-bs-toggle='tooltip' title='Total represents the sum of all enabled card types above.'></i>` next to the 'Total' label. Ensure Bootstrap tooltip is initialized (check if `$('[data-bs-toggle=\"tooltip\"]').tooltip()` is called somewhere in the page or common JS, add it if not)."

---

### [GAP-3.6] "Not Processing" segment in Portfolio Breakdown pie chart

- [ ] **[GAP-3.6]** Add "Not Processing" as the second segment in the Portfolio Breakdown donut/pie chart
  - **Status:** Missing (commented out)
  - **Priority:** Critical
  - **Effort:** M
  - **Files:** `resources/views/admin/my-merchants/index.blade.php`, `public/assets/js/my-merchants-portfolio.js`, `app/Models/Merchants/MerchantTransaction.php`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Read `app/Models/Merchants/MerchantTransaction.php` and find `getPortfolioBreakdown()`. Currently it returns 'Processing' (batch within last 14 days) and 'Closed' (status=Closed). Add a third category: 'Not Processing' = merchants that are NOT closed but have NO batch in the last 14 days.
>    - Query: `Merchant::where('status', '!=', 2)->whereDoesntHave('transactions', fn($q) => $q->where('settlement_date', '>=', now()->subDays(14)))->count()`
>    - Return array with keys: processing_count, not_processing_count, closed_count.
> 2. In `app/Http/Controllers/Merchants/MyMerchantController.php`, pass `$portfolioBreakdown` to view including the not_processing_count.
> 3. In `resources/views/admin/my-merchants/index.blade.php`:
>    - Add a toggle switch for 'Not Processing (N)' in the legend list, with orange color swatch.
>    - Pass `not_processing_count` to the JS chart data (via hidden input or inline JS variable).
> 4. In `public/assets/js/my-merchants-portfolio.js`, add 'Not Processing' as the second data point in the ApexCharts series array with color `#f1b44c` (orange). Label: 'Not Processing'.
> 5. The toggle switch for 'Not Processing' must filter the pie chart same as Processing and Closed toggles. When unchecked, remove that segment from the displayed data."

---

### [GAP-3.7] Toggle switches on pie chart segments

- [ ] **[GAP-3.7]** Add Bootstrap toggle switches next to each Portfolio Breakdown legend item (Processing, Not Processing, Closed)
  - **Status:** Partial
  - **Priority:** Medium
  - **Effort:** S
  - **Files:** `resources/views/admin/my-merchants/index.blade.php`, `public/assets/js/my-merchants-portfolio.js`

> **Claude Prompt:**
> "In `resources/views/admin/my-merchants/index.blade.php`, in the Portfolio Breakdown section legend, wrap each legend item's checkbox in a Bootstrap form-switch (`<div class='form-check form-switch'>`). Each switch label should show the segment name and count: 'Processing ({{ $portfolioBreakdown->processing_count }})'. When a toggle is turned off, remove that segment from the ApexCharts donut chart. In `public/assets/js/my-merchants-portfolio.js`, listen for `.form-check-input` change events on portfolio segment toggles and call `chart.updateSeries()` with the filtered data array."

---

### [GAP-3.8] Percentage labels on pie chart segments

- [ ] **[GAP-3.8]** Show percentage labels directly on each pie chart segment
  - **Status:** Missing
  - **Priority:** Low
  - **Effort:** XS
  - **Files:** `public/assets/js/my-merchants-portfolio.js`

> **Claude Prompt:**
> "In `public/assets/js/my-merchants-portfolio.js`, in the ApexCharts pie/donut chart options, set `dataLabels: { enabled: true }` and `plotOptions: { pie: { dataLabels: { offset: -10 } } }`. Format labels as percentages using `formatter: function(val) { return val.toFixed(1) + '%'; }` in the `dataLabels.formatter` option."

---

### [GAP-3.9] Info tooltip icons on pie chart legend

- [ ] **[GAP-3.9]** Add info (?) tooltip icons next to each legend item in Portfolio Breakdown
  - **Status:** Missing
  - **Priority:** Low
  - **Effort:** XS
  - **Files:** `resources/views/admin/my-merchants/index.blade.php`

> **Claude Prompt:**
> "In `resources/views/admin/my-merchants/index.blade.php`, next to each Portfolio Breakdown legend item (Processing, Not Processing, Closed), add `<i class='mdi mdi-information-outline ms-1 text-muted' data-bs-toggle='tooltip'>` with the following tooltips:
> - Processing: 'Merchants with at least one settled batch in the last 14 days'
> - Not Processing: 'Merchants that are active but have not processed in the last 14 days'
> - Closed: 'Merchants reported as closed by the processor or manually closed'
> Initialize Bootstrap tooltips."

---

### [GAP-3.10] "Showing N Merchants" counter above table

- [ ] **[GAP-3.10]** Add "Showing N Merchants" counter text above the merchant list table
  - **Status:** Missing
  - **Priority:** Low
  - **Effort:** XS
  - **Files:** `resources/views/admin/my-merchants/index.blade.php`

> **Claude Prompt:**
> "In `resources/views/admin/my-merchants/index.blade.php`, above the merchants DataTable, add a `<span id='merchants-count-label' class='text-muted small'>Showing {{ $merchants->total() }} Merchants</span>`. When the AJAX filter refreshes the table, also update this label with the new total count from the paginator response. In `public/assets/js/my-merchants-portfolio.js` (or the AJAX success handler), update `$('#merchants-count-label').text('Showing ' + data.total + ' Merchants')`."

---

### [GAP-3.11] Column Visibility gear icon with Column Search input

- [ ] **[GAP-3.11]** Add Column Visibility gear icon (⚙) with a searchable Column Search input in the dropdown
  - **Status:** Partial (dynamic headers exist, but no search input in dropdown)
  - **Priority:** High
  - **Effort:** M
  - **Files:** `resources/views/admin/my-merchants/index.blade.php`, common table component

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Read `resources/views/admin/my-merchants/index.blade.php` to find how the Column Visibility dropdown is currently rendered (look for `MasterDynamicHeader` column toggle UI).
> 2. Read other modules (e.g., `resources/views/admin/leads/index.blade.php`) to find the standard column visibility dropdown pattern used in this codebase.
> 3. Add a gear icon button (⚙) in the merchant list toolbar. Clicking it opens a Bootstrap dropdown with:
>    - A text input at the top labeled 'Column Search' (filters the column list as the user types)
>    - A list of all available columns as toggle checkboxes (checked = visible)
> 4. Implement client-side column search: as the user types in the Column Search input, filter the visible column checkboxes using JS (hide non-matching labels).
> 5. Column visibility changes must trigger a POST to the dynamic headers endpoint to persist the user's column preference."

---

### [GAP-3.12] VIM column (default visible) in merchant list

- [ ] **[GAP-3.12]** Add VIM (Yes/No flag) as a default-visible column in the My Merchants list
  - **Status:** Missing
  - **Priority:** High
  - **Effort:** S
  - **Files:** `database/seeders/MyMerchantDynamicHeadersSeeder.php`, `app/Http/Controllers/Merchants/MyMerchantController.php`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Read `database/seeders/MyMerchantDynamicHeadersSeeder.php` to understand the dynamic header seeder format.
> 2. Add a 'VIM' column entry to the seeder with `is_default: true` (visible by default). Column key: `vim_status`, display name: 'VIM', sortable: true.
> 3. In `app/Http/Controllers/Merchants/MyMerchantController.php` index method, ensure `vim_status` is selected in the merchant query. The display should show 'Yes' (with a star icon: `<i class='mdi mdi-star text-warning'></i>`) for VIM=1, and 'No' for VIM=0.
> 4. Run the seeder update: `php artisan db:seed --class=MyMerchantDynamicHeadersSeeder` (or use an upsert to avoid duplicates). If the seeder inserts new rows, wrap in `updateOrCreate` or check for existing record first."

---

### [GAP-3.13] Last Lead Note column with "Show more" link

- [ ] **[GAP-3.13]** Add "Last Lead Note" as a default-visible column in the merchant list with "Show more" truncation
  - **Status:** Missing
  - **Priority:** High
  - **Effort:** M
  - **Files:** `database/seeders/MyMerchantDynamicHeadersSeeder.php`, `app/Http/Controllers/Merchants/MyMerchantController.php`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Read `app/Models/Merchants/Merchant.php` to find if there is a `lead` relationship (hasOne or belongsTo Lead).
> 2. In `app/Http/Controllers/Merchants/MyMerchantController.php` index method, eager-load the `lead` relationship and its most recent note: `->with(['lead' => fn($q) => $q->select('id','merchant_id','last_note')->latest()])`. Add the last note text to each merchant row.
> 3. Add 'Last Lead Note' column to `MyMerchantDynamicHeadersSeeder.php` with `is_default: true`.
> 4. In the table cell template, render the note text truncated to 80 characters. If longer, show truncated text + `<a href='#' class='show-more-note' data-full='{{ $fullNote }}'>Show more</a>`. Add a JS click handler that expands the text inline when 'Show more' is clicked (toggle between truncated and full text, changing link text to 'Show less')."

---

### [GAP-3.14] Active column (green checkmark icon)

- [ ] **[GAP-3.14]** Add "Active" column (green checkmark icon for active merchants) as default-visible
  - **Status:** Missing
  - **Priority:** High
  - **Effort:** S
  - **Files:** `database/seeders/MyMerchantDynamicHeadersSeeder.php`

> **Claude Prompt:**
> "In `database/seeders/MyMerchantDynamicHeadersSeeder.php`, add an 'Active' column with `is_default: true`, sortable by `system_status`. In the table cell template (or via a model accessor), render: if `system_status == 1` (Active), show `<i class='mdi mdi-check-circle text-success'></i>`; otherwise show an empty cell or `<i class='mdi mdi-close-circle text-danger'></i>`."

---

### [GAP-3.15] Pagination default 25 items per page

- [ ] **[GAP-3.15]** Change merchant list default pagination from 10 to 25 items per page
  - **Status:** Partial (currently 10)
  - **Priority:** Low
  - **Effort:** XS
  - **Files:** `app/Http/Controllers/Merchants/MyMerchantController.php`

> **Claude Prompt:**
> "In `app/Http/Controllers/Merchants/MyMerchantController.php` index method, find the `paginate(10)` call and change it to `paginate(25)`. Also ensure the per-page selector in the view defaults to 25."

---

### [GAP-3.16] Hidden columns: Processor, Last Batch Date, per-card-type volume columns

- [ ] **[GAP-3.16]** Add 18 additional columns available via Column Visibility (hidden by default): Processor, Last Batch Date, and per-card-type Volume & Transactions for all 8 card types
  - **Status:** Missing
  - **Priority:** High
  - **Effort:** L
  - **Files:** `database/seeders/MyMerchantDynamicHeadersSeeder.php`, `app/Http/Controllers/Merchants/MyMerchantController.php`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. In `database/seeders/MyMerchantDynamicHeadersSeeder.php`, add the following columns with `is_default: false` (hidden by default):
>    - Processor (key: `processor`)
>    - Last Batch Date (key: `last_batch_date`)
>    - Mastercard Volume (key: `mc_volume`), Mastercard Transactions (key: `mc_transactions`)
>    - Visa Volume (key: `visa_volume`), Visa Transactions (key: `visa_transactions`)
>    - American Express Volume (key: `amex_volume`), American Express Transactions (key: `amex_transactions`)
>    - Discover Volume (key: `discover_volume`), Discover Transactions (key: `discover_transactions`)
>    - EBT Volume (key: `ebt_volume`), EBT Transactions (key: `ebt_transactions`)
>    - Pin-Debit Volume (key: `pin_debit_volume`), Pin-Debit Transactions (key: `pin_debit_transactions`)
>    - Fleet Cards Volume (key: `fleet_volume`), Fleet Cards Transactions (key: `fleet_transactions`)
>    - Other Volume (key: `other_volume`), Other Transactions (key: `other_transactions`)
> 2. In `MyMerchantController.php` index listing query, JOIN or subquery against `merchant_transactions` to compute per-card-type SUM(amount) and COUNT(*) grouped by `card_type` for the selected date range. Attach these values to each merchant row.
> 3. Format volume columns as currency (e.g., `$1,234.56`), transaction columns as integers."

---

### [GAP-3.17] Filter panel: Date Range field

- [ ] **[GAP-3.17]** Add "Date Range" dropdown field to the Merchant Filters panel (default: This Month)
  - **Status:** Missing
  - **Priority:** High
  - **Effort:** M
  - **Files:** `resources/views/admin/my-merchants/index.blade.php`

> **Claude Prompt:**
> "In `resources/views/admin/my-merchants/index.blade.php`, in the right-side 'Filter Merchants' offcanvas/panel, add a 'Date Range' `<select>` dropdown with options: This Month, Last Month, Last 3 Months, Last 6 Months, Last 12 Months, Custom Range. Default selected: 'This Month'. When 'Custom Range' is selected, show two date inputs: 'From' and 'To' (use Bootstrap Datepicker or HTML date inputs). Include the selected date range as a query parameter `date_range` (and `date_from`/`date_to` for custom) in the filter AJAX call. In `MyMerchantController.php` index method, parse this parameter and apply the date filter to both the merchant listing query and the card type summary query."

---

### [GAP-3.18] Filter panel: Sales Rep Number tag-style multi-select

- [ ] **[GAP-3.18]** Add "Sales Rep Number" tag-style multi-select filter to the Merchant Filters panel
  - **Status:** Missing
  - **Priority:** Medium
  - **Effort:** M
  - **Files:** `resources/views/admin/my-merchants/index.blade.php`, `app/Http/Controllers/Merchants/MyMerchantController.php`

> **Claude Prompt:**
> "In `resources/views/admin/my-merchants/index.blade.php` filter panel, add a 'Sales Rep Number' field using a Select2 multi-select dropdown that allows adding/removing tags. Populate options from `MerchantUserMap` or `User` table (sales rep numbers). Pass selected values as `sales_rep_numbers[]` array in the filter request. In `MyMerchantController.php`, filter merchants WHERE assigned user's sales_rep_number is in the array."

---

### [GAP-3.19] Filter panel: Pricing tag-style multi-select

- [ ] **[GAP-3.19]** Add "Pricing" tag-style multi-select filter to the Merchant Filters panel
  - **Status:** Missing
  - **Priority:** Medium
  - **Effort:** S
  - **Files:** `resources/views/admin/my-merchants/index.blade.php`, `app/Http/Controllers/Merchants/MyMerchantController.php`

> **Claude Prompt:**
> "In `resources/views/admin/my-merchants/index.blade.php` filter panel, add a 'Pricing' field using a Select2 multi-select. Options should be pricing model types from the merchant/lead data (e.g., Interchange Plus, Tiered, Flat Rate). Pass as `pricing[]`. In `MyMerchantController.php`, filter by pricing model if provided."

---

### [GAP-3.20] Filter panel: Lead Type dropdown

- [ ] **[GAP-3.20]** Add "Lead Type" dropdown filter to the Merchant Filters panel
  - **Status:** Missing
  - **Priority:** Medium
  - **Effort:** S
  - **Files:** `resources/views/admin/my-merchants/index.blade.php`, `app/Http/Controllers/Merchants/MyMerchantController.php`

> **Claude Prompt:**
> "In `resources/views/admin/my-merchants/index.blade.php` filter panel, add a 'Lead Type' `<select>` dropdown. Default: 'All Lead Type'. Populate from the Lead types in the system (check `Lead` model for type enum or relationship). Pass as `lead_type` in filter request. Filter merchants via their linked lead's type."

---

### [GAP-3.21] Filter panel: Clear (X) and Apply (✓) icon buttons

- [ ] **[GAP-3.21]** Add Clear (X) and Apply (✓) icon buttons at the top-right of the Merchant Filters panel
  - **Status:** Missing
  - **Priority:** Medium
  - **Effort:** S
  - **Files:** `resources/views/admin/my-merchants/index.blade.php`

> **Claude Prompt:**
> "In `resources/views/admin/my-merchants/index.blade.php`, in the 'Filter Merchants' offcanvas panel header, add two icon buttons in the top-right:
> 1. Clear button: `<button class='btn btn-sm btn-outline-secondary' id='clear-filters-btn'><i class='mdi mdi-close'></i></button>` — clicking resets all filter form fields to defaults.
> 2. Apply button: `<button class='btn btn-sm btn-success' id='apply-filters-btn'><i class='mdi mdi-check'></i></button>` — clicking applies the filters (same as existing 'Apply Filters' button at bottom). Add JS: `$('#clear-filters-btn').on('click', function() { $('#filter-form')[0].reset(); });` and `$('#apply-filters-btn').on('click', function() { $('#apply-filters-bottom-btn').trigger('click'); });`."

---
---

# §4 — Merchant Detail View

---

### [GAP-4.1] Breadcrumb navigation

- [ ] **[GAP-4.1]** Add breadcrumb "My Merchants > [MERCHANT DBA NAME]" to the detail page header
  - **Status:** Missing
  - **Priority:** Medium
  - **Effort:** XS
  - **Files:** `resources/views/admin/my-merchants/show.blade.php`

> **Claude Prompt:**
> "In `resources/views/admin/my-merchants/show.blade.php`, add a Bootstrap breadcrumb above the page title:
> ```html
> <nav aria-label='breadcrumb'>
>   <ol class='breadcrumb'>
>     <li class='breadcrumb-item'><a href='{{ route('my-merchants.index') }}'>My Merchants</a></li>
>     <li class='breadcrumb-item active'>{{ $data->dba ?? $data->legal_name }}</li>
>   </ol>
> </nav>
> ```"

---

### [GAP-4.2] MID in teal, clickable, next to merchant name

- [ ] **[GAP-4.2]** Display MID number in teal next to the merchant DBA name in the page header; make it clickable
  - **Status:** Missing
  - **Priority:** Medium
  - **Effort:** XS
  - **Files:** `resources/views/admin/my-merchants/show.blade.php`

> **Claude Prompt:**
> "In `resources/views/admin/my-merchants/show.blade.php`, in the page header next to the merchant DBA name, add: `<span class='text-muted ms-2 fs-6'><a href='#' style='color:#34c38f;' class='mid-link'>{{ $data->mid }}</a></span>`. Clicking the MID should either copy it to clipboard or navigate to the merchant management record."

---

### [GAP-4.3] Copy MID icon

- [ ] **[GAP-4.3]** Add clipboard copy icon next to the MID in the merchant detail header
  - **Status:** Missing
  - **Priority:** Medium
  - **Effort:** XS
  - **Files:** `resources/views/admin/my-merchants/show.blade.php`

> **Claude Prompt:**
> "In `resources/views/admin/my-merchants/show.blade.php`, after the MID span (see GAP-4.2), add: `<i class='mdi mdi-content-copy ms-1 cursor-pointer' id='copy-mid-btn' title='Copy MID' data-mid='{{ $data->mid }}'></i>`. Add JS: `$('#copy-mid-btn').on('click', function() { navigator.clipboard.writeText($(this).data('mid')); $(this).attr('title', 'Copied!'); setTimeout(() => $(this).attr('title', 'Copy MID'), 1500); });`"

---

### [GAP-4.4] Bookmark/favorite icon

- [ ] **[GAP-4.4]** Add bookmark/favorite icon to the merchant detail header
  - **Status:** Missing
  - **Priority:** Low
  - **Effort:** S
  - **Files:** `resources/views/admin/my-merchants/show.blade.php`, new AJAX endpoint

> **Claude Prompt:**
> "In `resources/views/admin/my-merchants/show.blade.php`, add a bookmark icon in the header: `<i class='mdi mdi-bookmark-outline ms-2 cursor-pointer' id='bookmark-merchant-btn' data-merchant-id='{{ $data->id }}'></i>`. On click, POST to `route('my-merchants.bookmark', $data->id)` via AJAX. Toggle the icon class between `mdi-bookmark-outline` (not saved) and `mdi-bookmark` (saved). Add the route and controller method that saves/removes the merchant ID from a `user_favorite_merchants` table or JSON field on the user."

---

### [GAP-4.5] Authorizations tab — completely missing

- [ ] **[GAP-4.5]** Create the Authorizations tab in merchant detail view (NEW TAB)
  - **Status:** Missing
  - **Priority:** Critical
  - **Effort:** L
  - **Files:** NEW `resources/views/admin/my-merchants/partials/authorizations.blade.php`, `resources/views/admin/my-merchants/show.blade.php`, `app/Http/Controllers/Merchants/MyMerchantController.php`, `app/Models/Merchants/MerchantTransaction.php`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Read `resources/views/admin/my-merchants/show.blade.php` to see how tabs are declared and how partials are included.
> 2. Read `resources/views/admin/my-merchants/partials/batches.blade.php` as a pattern reference.
> 3. Read `app/Http/Controllers/Merchants/MyMerchantController.php` show method to see how tab data is loaded.
> 4. In `show.blade.php`, add 'Authorizations' as a new tab between 'Batches' and 'Account Status'.
> 5. Create `resources/views/admin/my-merchants/partials/authorizations.blade.php` with:
>    - A date range filter dropdown (Today, This Week, This Month, Last 30 Days, Custom)
>    - An approval ratio metric showing: total authorizations, approved count, declined count, approval % (styled as KPI cards)
>    - A table with columns: Date/Time, Card Type, Amount, Auth Code, Status (Approved/Declined with color badge)
>    - Status badge: Approved = green `badge bg-success`, Declined = red `badge bg-danger`
> 6. In `MerchantTransaction.php`, add an `authorizations()` scope: `->where('transaction_type', 'auth')->orWhere('auth_code', '!=', null)` ordered by `transaction_date DESC`.
> 7. In `MyMerchantController.php` show method, add `authorizations` case to the tab switch: query last 100 authorizations, compute approval ratio, pass to view.
> 8. Add 'authorizations' to the `$allowedTabs` array."

---

### [GAP-4.6] Memos tab — completely missing

- [ ] **[GAP-4.6]** Create the Memos tab with memo list, add memo form, and type filters (NEW TAB + MODEL)
  - **Status:** Missing
  - **Priority:** Critical
  - **Effort:** XL
  - **Files:** NEW migration, NEW `app/Models/Merchants/MerchantMemo.php`, NEW `resources/views/admin/my-merchants/partials/memos.blade.php`, `show.blade.php`, `MyMerchantController.php`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Create a migration: `php artisan make:migration create_merchant_memos_table`. Table structure:
>    - `id`, `merchant_id` (FK merchants.id), `user_id` (FK users.id), `company_id`
>    - `memo_type` ENUM('note','call','email','status_change') default 'note'
>    - `memo_text` TEXT, `timestamps`, `softDeletes`
> 2. Create `app/Models/Merchants/MerchantMemo.php` with fillable, belongsTo(Merchant), belongsTo(User), SoftDeletes, and a `getMemoTypeTextAttribute()` accessor.
> 3. In `Merchant.php` model, add `memos()` hasMany relationship.
> 4. Create `resources/views/admin/my-merchants/partials/memos.blade.php`:
>    - Filter bar with 4 tab-buttons: All | Note | Call | Email | Status Change (active tab highlighted teal)
>    - 'Add Memo' form: textarea for memo text, dropdown for memo type, 'Save' button (AJAX POST)
>    - Memo list: each memo shows created_at timestamp, user avatar/name, type badge, memo text. Newest first.
>    - Empty state: 'No memos yet. Add the first memo above.'
> 5. In `show.blade.php`, add 'Memos' tab between 'Authorizations' and 'Account Status'.
> 6. In `MyMerchantController.php`, add 'memos' to `$allowedTabs` and load memos data (with user relationship, ordered by created_at DESC).
> 7. Add POST route for `my-merchants.memos.store` → controller method that creates a MerchantMemo record and returns JSON."

---

### [GAP-4.7] 1099K tab — completely missing

- [ ] **[GAP-4.7]** Create the 1099K tab showing annual gross payment amounts and IRS filing status (NEW TAB)
  - **Status:** Missing
  - **Priority:** Critical
  - **Effort:** L
  - **Files:** NEW `resources/views/admin/my-merchants/partials/1099k.blade.php`, `show.blade.php`, `MyMerchantController.php`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Read `resources/views/admin/my-merchants/partials/statements.blade.php` as a pattern reference.
> 2. Create `resources/views/admin/my-merchants/partials/1099k.blade.php` with:
>    - A table with columns: Tax Year, Gross Payment Amount, Transaction Count, IRS Filing Status (Pending/Filed/Not Required), Actions
>    - IRS Filing Status badge: Pending = yellow, Filed = green, Not Required = gray
>    - Each row links or downloads the 1099-K PDF if available
>    - Empty state message if no 1099K data available
> 3. Data source: Query `MerchantTransaction` grouped by YEAR(settlement_date) to compute annual gross volume and transaction count. Filing status is a manual field (store in a `merchant_1099k` table or as a JSON field on the merchant).
> 4. In `show.blade.php`, add '1099K' tab between 'Statements' and 'Profile'.
> 5. In `MyMerchantController.php`, add '1099k' to `$allowedTabs` and build annual summary data."

---

### [GAP-4.8] Batches tab: Date Range selector dropdown

- [ ] **[GAP-4.8]** Enable/implement the Date Range selector dropdown on the Batches tab (currently commented out)
  - **Status:** Partial (structure commented out)
  - **Priority:** High
  - **Effort:** M
  - **Files:** `resources/views/admin/my-merchants/partials/batches.blade.php`, `app/Http/Controllers/Merchants/MyMerchantController.php`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Read `resources/views/admin/my-merchants/partials/batches.blade.php` and uncomment/implement the date range selector.
> 2. Add a Bootstrap dropdown `<select id='batches-date-range'>` with options: Today, This Week, This Month (default), Last 30 Days, Last 60 Days, Last 90 Days, Custom Range.
> 3. When the date range is changed, make an AJAX request to a new endpoint `route('my-merchants.batches', $merchant->id)` with `?date_range=this_month` parameter.
> 4. In `MyMerchantController.php`, add a `batches()` AJAX method that accepts `date_range` and re-queries: volume/transaction chart data, card type summary, and batch table — returning JSON.
> 5. The JS in `public/assets/js/my-merchant-batches.js` should handle the AJAX response by re-rendering the chart and updating the table HTML."

---

### [GAP-4.9] Batches tab: 11 KPI Metric Cards carousel

- [ ] **[GAP-4.9]** Implement the 11 KPI Metric Cards carousel on the Batches tab (horizontally scrollable with left/right arrows)
  - **Status:** Missing
  - **Priority:** Critical
  - **Effort:** XL
  - **Files:** `resources/views/admin/my-merchants/partials/batches.blade.php`, `app/Http/Controllers/Merchants/MyMerchantController.php`, `public/assets/js/my-merchant-batches.js`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Read `resources/views/admin/my-merchants/partials/batches.blade.php` to find where to insert the KPI carousel.
> 2. Read `app/Http/Controllers/Merchants/MyMerchantController.php` show method (batches section) for context.
>
> **In `MyMerchantController.php`**, in the batches data section, compute and pass the following KPI values:
> - `$volumeRank`: This merchant's rank among all active merchants by volume (e.g., '1,403 out of 1,410 Active') — query all merchants ordered by total volume DESC, find position of current merchant.
> - `$avgBatchAmount`: AVG(batch_amount) for selected period from merchant_transactions.
> - `$bestProcessingDay`: Day of week with highest total volume (SUM grouped by DAYNAME(settlement_date)).
> - `$lastProcessingDate`: MAX(settlement_date) for this merchant.
> - `$totalTransactionVolume`: SUM(amount) for selected period.
> - `$highestVolumeDay`: The single date with highest volume (MAX grouped by settlement_date).
> - `$accountAge`: `Carbon::parse($merchant->approval_date)->diffInYears(now())` years.
> - `$chargebackRatio`: COUNT(chargebacks) / COUNT(all transactions) * 100 for the period.
> - `$avgTransaction`: AVG(amount) for the period.
> - `$nextEvent`: Pull from merchant's linked lead events/appointments (next upcoming event date, if any).
> - `$profit`: Total residual/commission for the period (from split percentages if available, else 0).
>
> **In `partials/batches.blade.php`**, add a horizontally scrollable carousel section between the date range selector and the Volume chart. Use a `<div class='kpi-carousel d-flex overflow-auto gap-3 pb-2'>` with left/right arrow buttons for navigation. Each KPI card is a Bootstrap card with:
> - Title (e.g., 'Avg Batch Amount')
> - Value in large bold text
> - Sub-text (date range or context)
> - A 'View [Section]' link (as specified in the SOW):
>   - Merchant Volume Ranking → `?tab=batches` + 'View All Merchants' link to my-merchants.index
>   - Avg Batch Amount → `?tab=batches` 'View Batches'
>   - Best Processing Day → 'View Batches'
>   - Last Processing Date → `?tab=account-status` 'View Account Status'
>   - Total Transaction Volume → `?tab=financials` 'View Financials'
>   - Highest Volume Day → 'View Daily Batches'
>   - Account Age → `?tab=account-status` 'View Account Status'
>   - Chargeback Ratio → `?tab=chargebacks` 'View Chargebacks'
>   - Avg Transaction → `?tab=authorizations` 'View Transactions'
>   - Next Event → 'View Transactions'
>   - Profit → (no link)
>
> Add CSS for the carousel scrolling with hidden scrollbar. Add JS arrow button click handlers to scroll left/right by 300px."

---

### [GAP-4.21] Batches tab: Month navigator (Prev/Next)

- [ ] **[GAP-4.21]** Add month navigator (Prev/Next buttons + current month/year label) to the Daily Deposits batch table
  - **Status:** Missing
  - **Priority:** High
  - **Effort:** M
  - **Files:** `resources/views/admin/my-merchants/partials/batches.blade.php`, `app/Http/Controllers/Merchants/MyMerchantController.php`

> **Claude Prompt:**
> "In `resources/views/admin/my-merchants/partials/batches.blade.php`, above the batch table, add a month navigator:
> ```html
> <div class='d-flex align-items-center gap-2'>
>   <button id='prev-month-btn' class='btn btn-sm btn-outline-secondary'><i class='mdi mdi-chevron-left'></i></button>
>   <span id='batch-month-label'>{{ date('F Y') }}</span>
>   <button id='next-month-btn' class='btn btn-sm btn-outline-secondary'><i class='mdi mdi-chevron-right'></i></button>
> </div>
> ```
> Add JS that tracks current month/year, and on Prev/Next click, makes an AJAX call to `route('my-merchants.batches', $merchant->id)?month=YYYY-MM` to reload the batch table for that month. The controller should accept a `month` parameter and filter `settlement_date` to that month."

---

### [GAP-4.22] Batches tab: "Find in Merchant" button

- [ ] **[GAP-4.22]** Add "Find in Merchant" button to the batch table toolbar
  - **Status:** Missing
  - **Priority:** Low
  - **Effort:** XS
  - **Files:** `resources/views/admin/my-merchants/partials/batches.blade.php`

> **Claude Prompt:**
> "In `resources/views/admin/my-merchants/partials/batches.blade.php`, in the batch table toolbar (alongside Search and Export), add: `<button class='btn btn-sm btn-outline-primary' id='find-in-merchant-btn'><i class='mdi mdi-magnify'></i> Find in Merchant</button>`. This button opens an inline text search that highlights matching rows in the batch table. Add JS: `$('#find-in-merchant-btn').on('click', function() { /* show/focus an inline search input that filters table rows client-side */ });`"

---

### [GAP-4.26] Account Status tab: Unhide accordion sections

- [ ] **[GAP-4.26]** Unhide the hidden accordion sections in Account Status tab (Processing Info, Card Type Info, Settlement Info, Account Activity Info, Income/Expense Info, Discount Rates)
  - **Status:** Partial (sections have `d-none` class)
  - **Priority:** High
  - **Effort:** M
  - **Files:** `resources/views/admin/my-merchants/partials/account-status.blade.php`

> **Claude Prompt:**
> "In `resources/views/admin/my-merchants/partials/account-status.blade.php`, find all accordion sections with `d-none` class (Processing Information, Card Type Information, Settlement Information, Account Activity Information, Income/Expense Information, Discount Rates). Remove the `d-none` class from each to make them visible. Verify the fields inside each section are properly bound to the `$data` object (the Merchant model). For any field that shows a blank value because the merchant model doesn't have that field, either add the field to the merchant model/migration or hide that specific field with a null check: `@if($data->field_name) ... @endif`."

---

### [GAP-4.28 & 4.29] Memos tab: Covered in GAP-4.6

> *See GAP-4.6 above for full Memos tab implementation.*

---

### [GAP-4.30] Financials tab: Volume trend charts

- [ ] **[GAP-4.30]** Add volume trend charts (monthly/quarterly/annual view) to the Financials tab
  - **Status:** Missing (currently only a table)
  - **Priority:** High
  - **Effort:** L
  - **Files:** `resources/views/admin/my-merchants/partials/financials.blade.php`, `app/Http/Controllers/Merchants/MyMerchantController.php`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Read `resources/views/admin/my-merchants/partials/batches.blade.php` and `public/assets/js/my-merchant-batches.js` to understand the ApexCharts pattern used.
> 2. In `resources/views/admin/my-merchants/partials/financials.blade.php`, above the existing daily table, add:
>    - A view selector: tab buttons 'Monthly' | 'Quarterly' | 'Annual'
>    - An ApexCharts bar+line chart showing Gross Volume (bars) and Net Volume (line) over time
>    - The chart data changes based on the selected view (monthly = last 12 months, quarterly = last 8 quarters, annual = last 5 years)
> 3. In `MyMerchantController.php`, compute monthly, quarterly, and annual aggregations from `MerchantTransaction`. Pass them as JSON-encoded hidden inputs or inline JS variables.
> 4. Add JS to initialize the chart and handle view selector tab switching (re-render chart with different dataset)."

---

### [GAP-4.31] Retrievals tab: Reason Code column

- [ ] **[GAP-4.31]** Add Reason Code column to the Retrievals tab table
  - **Status:** Missing
  - **Priority:** Medium
  - **Effort:** S
  - **Files:** `resources/views/admin/my-merchants/partials/retrievals.blade.php`

> **Claude Prompt:**
> "In `resources/views/admin/my-merchants/partials/retrievals.blade.php`, add a 'Reason Code' column to the table header and each row. Map to `$retrieval->reason_code` (already exists in `merchant_transactions` table). Place it between Currency and Status columns."

---

### [GAP-4.32] Retrievals tab: Response Due Date column

- [ ] **[GAP-4.32]** Add Response Due Date column to the Retrievals tab table
  - **Status:** Missing
  - **Priority:** Medium
  - **Effort:** S
  - **Files:** `resources/views/admin/my-merchants/partials/retrievals.blade.php`, migration if needed

> **Claude Prompt:**
> "Check if `merchant_transactions` table has a `response_due_date` column (look in migrations). If not, create a migration to add it: `$table->date('response_due_date')->nullable()`. In `resources/views/admin/my-merchants/partials/retrievals.blade.php`, add 'Response Due Date' column after Reason Code. Display as formatted date: `{{ $retrieval->response_due_date ? Carbon::parse($retrieval->response_due_date)->format('m/d/Y') : '—' }}`."

---

### [GAP-4.33] Retrievals tab: Open/Responded/Expired status

- [ ] **[GAP-4.33]** Update Retrievals tab Status column to show: Open, Responded, Expired
  - **Status:** Partial (different status values currently)
  - **Priority:** Medium
  - **Effort:** S
  - **Files:** `app/Models/Merchants/MerchantTransaction.php`

> **Claude Prompt:**
> "In `app/Models/Merchants/MerchantTransaction.php`, find or add a `getRetrievalStatusTextAttribute()` accessor. Logic:
> - If `response_due_date` is past and no response recorded → 'Expired' (badge: red)
> - If response has been submitted (add a `retrieval_responded_at` field or flag) → 'Responded' (badge: green)
> - Otherwise → 'Open' (badge: yellow)
> Update `partials/retrievals.blade.php` to use this accessor instead of the generic `status_text`."

---

### [GAP-4.34] Chargebacks tab: Representment Deadline column

- [ ] **[GAP-4.34]** Add Representment Deadline column to the Chargebacks tab table
  - **Status:** Missing
  - **Priority:** Medium
  - **Effort:** S
  - **Files:** `resources/views/admin/my-merchants/partials/chargebacks.blade.php`, migration if needed

> **Claude Prompt:**
> "Check if `merchant_transactions` table has a `representment_deadline` column. If not, add a migration: `$table->date('representment_deadline')->nullable()`. In `resources/views/admin/my-merchants/partials/chargebacks.blade.php`, add 'Representment Deadline' column after Reason Code. Format as date. If the deadline is within 3 days, highlight the cell in orange/yellow as a warning."

---

### [GAP-4.35] Chargebacks tab: Open/Won/Lost/Represented status

- [ ] **[GAP-4.35]** Update Chargebacks tab Status to show: Open, Won, Lost, Represented
  - **Status:** Partial
  - **Priority:** Medium
  - **Effort:** S
  - **Files:** `app/Models/Merchants/MerchantTransaction.php`

> **Claude Prompt:**
> "In `app/Models/Merchants/MerchantTransaction.php`, update or add `getChargebackStatusTextAttribute()` accessor mapping chargeback status values to: Open (yellow badge), Won (green badge), Lost (red badge), Represented (blue badge). Update `partials/chargebacks.blade.php` to use this new accessor."

---

### [GAP-4.37] Statements tab: Backend data source

- [ ] **[GAP-4.37]** Implement MerchantStatement model, migration, and controller data loading for the Statements tab
  - **Status:** Missing (UI exists, data is empty collection)
  - **Priority:** Critical
  - **Effort:** XL
  - **Files:** NEW migration `create_merchant_statements_table`, NEW `app/Models/Merchants/MerchantStatement.php`, `app/Http/Controllers/Merchants/MyMerchantController.php`, `resources/views/admin/my-merchants/partials/statements.blade.php`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Create migration `create_merchant_statements_table` with fields: `id`, `merchant_id` (FK), `company_id`, `statement_date` (date), `period_start` (date), `period_end` (date), `total_volume` (decimal 12,2), `total_fees` (decimal 10,2), `net_volume` (decimal 12,2), `file_path` (string, nullable), `timestamps`.
> 2. Create `app/Models/Merchants/MerchantStatement.php` model with fillable, belongsTo(Merchant), and a `getDownloadUrlAttribute()` accessor (returns storage URL if file_path exists).
> 3. In `Merchant.php`, add `statements()` hasMany(MerchantStatement) relationship.
> 4. In `MyMerchantController.php` show method, replace `$this->_data['statements'] = collect()` with `$this->_data['statements'] = MerchantStatement::where('merchant_id', $id)->orderBy('statement_date', 'desc')->get()`.
> 5. In `resources/views/admin/my-merchants/partials/statements.blade.php`, verify the table columns map correctly: Statement Date, Period, Total Volume, Total Fees, Net Volume, and Download button (if `$statement->file_path` exists, show a PDF download button)."

---

### [GAP-4.38] Statements tab: PDF download

- [ ] **[GAP-4.38]** Implement PDF download functionality for statements
  - **Status:** Missing
  - **Priority:** High
  - **Effort:** M
  - **Files:** `resources/views/admin/my-merchants/partials/statements.blade.php`, route, controller

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Add a route: `Route::get('my-merchants/{merchantId}/statements/{statementId}/download', [MyMerchantController::class, 'downloadStatement'])->name('my-merchants.statements.download')`.
> 2. In `MyMerchantController.php`, add `downloadStatement($merchantId, $statementId)` method that: retrieves the `MerchantStatement` record, verifies company_id matches authenticated user's company, and returns `Storage::download($statement->file_path)`.
> 3. In `partials/statements.blade.php`, in the Download column, render: `@if($statement->file_path) <a href='{{ route('my-merchants.statements.download', [$merchant->id, $statement->id]) }}' class='btn btn-sm btn-outline-primary'><i class='mdi mdi-file-pdf'></i> PDF</a> @else <span class='text-muted'>—</span> @endif`."

---

### [GAP-4.39 & 4.40] 1099K tab: Covered in GAP-4.7

> *See GAP-4.7 above for full 1099K tab implementation.*

---
---

# §5 — Portfolio Activity

---

### [GAP-5.1] Full Portfolio Activity page implementation

- [ ] **[GAP-5.1]** Implement the full Portfolio Activity page (currently a stub showing only subtitle)
  - **Status:** Missing
  - **Priority:** Critical
  - **Effort:** XL
  - **Files:** `resources/views/admin/merchants/portfolio-activity.blade.php`, `app/Http/Controllers/Merchants/MerchantController.php`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Read `resources/views/admin/merchants/portfolio-activity.blade.php` (currently a stub).
> 2. Read `resources/views/admin/my-merchants/index.blade.php` for layout/filter panel patterns to reuse.
> 3. Read `app/Http/Controllers/Merchants/MerchantController.php` to find the `portfolioActivity()` method and understand what data is currently available.
>
> Implement the full page layout:
>
> **Filter bar (top):** Multi-select tag pills for: All Processors, All Datasources, All Groups, All Users. Plus a 'Filter: Closed' toggle tag. Plus sub-filters row: All Sales Rep Number, All Processor, All Pricing. Plus 'Viewing' date range selector (Date Range default) with From/To date pickers.
>
> **Monthly Booked Volume section:** Horizontal bar chart using ApexCharts with:
> - Title: 'Monthly Booked Volume'
> - Two rows per month: first bar = combined MC/Visa/Discover/Amex volume (labeled with dollar amount), second bar = Transactions count
> - Y-axis: month labels; X-axis: percentage 0% to 100%
> - Data from: SUM(batch_amount) and COUNT(*) from merchant_transactions grouped by MONTH(settlement_date) for selected merchants
>
> **Transactions table:** Standard paginated data table with columns (see GAP-5.7 through 5.12). Export button and 'Show N entries' selector (default 25).
>
> **In `MerchantController.php`**, `portfolioActivity()` method should:
> - Scope to company_id of authenticated user
> - Apply processor/group/user/date filters
> - Return monthly volume data as JSON for chart
> - Return paginated merchant list with monthly_volume, avg_ticket, processor, datasource, group, processing_history computed fields"

---

### [GAP-5.9] Portfolio Activity: Merchant column with TurboApp/Lead sub-links

- [ ] **[GAP-5.9]** Add "Go to TurboApp" and "Go to Lead" sub-links under each merchant name in Portfolio Activity table
  - **Status:** Missing
  - **Priority:** High
  - **Effort:** S
  - **Files:** `resources/views/admin/merchants/portfolio-activity.blade.php`

> **Claude Prompt:**
> "In `resources/views/admin/merchants/portfolio-activity.blade.php`, in the Merchant column of the transactions table, render:
> ```html
> <a href='{{ route('my-merchants.show', $row->id) }}'>{{ $row->dba }}</a>
> <br>
> <small>
>   @if($row->turboapp_url) <a href='{{ $row->turboapp_url }}' target='_blank' class='text-muted'>Go to TurboApp</a> | @endif
>   @if($row->lead_id) <a href='{{ route('leads.show', $row->lead_id) }}' class='text-muted'>Go to Lead</a> @endif
> </small>
> ```"

---

### [GAP-5.11] Portfolio Activity: Processing History column with colored dot

- [ ] **[GAP-5.11]** Add Processing History column with colored dot indicator to Portfolio Activity table
  - **Status:** Missing
  - **Priority:** High
  - **Effort:** M
  - **Files:** `resources/views/admin/merchants/portfolio-activity.blade.php`, `app/Http/Controllers/Merchants/MerchantController.php`

> **Claude Prompt:**
> "In `app/Http/Controllers/Merchants/MerchantController.php` portfolioActivity method, for each merchant, compute `processing_history` text: e.g., 'Approved: 01/15/2024 | First Batch: 02/01/2024 | Status: Active'. Determine dot color: green dot if last_batch_date within 30 days AND status=Active; red dot otherwise. In the view, render: `<span class='dot' style='width:10px;height:10px;border-radius:50%;background:{{ $row->processing_dot_color }};display:inline-block;'></span> {{ $row->processing_history }}`."

---
---

# §6 — Merchant Tracker

---

### [GAP-6.1] Full Merchant Tracker page implementation

- [ ] **[GAP-6.1]** Implement the full Merchant Tracker page (currently a stub)
  - **Status:** Missing
  - **Priority:** Critical
  - **Effort:** XL
  - **Files:** `resources/views/admin/merchants/merchant-tracker.blade.php`, `app/Http/Controllers/Merchants/MerchantController.php`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Read `resources/views/admin/merchants/merchant-tracker.blade.php` (currently a stub).
> 2. Read `app/Http/Controllers/Merchants/MerchantController.php` to find `merchantTracker()` method.
>
> **Implement the full page:**
>
> **Filters (top row):**
> - 'All Merchants' dropdown (default: All; options: My Merchants, Other)
> - 'All Processors' dropdown
> - 'All Groups' dropdown
> - 'All Users' dropdown
> - 'Search' button that applies filters
> - Below filters: text label 'Viewing merchants who have not processed any transactions since' + a configurable dropdown: 1 week | 2 weeks | 1 month | 3 months (default: 1 week)
>
> **Results table:**
> - Columns: # (row number), Enabled (toggle), Last Batch (e.g. '7 days ago'), Merchant (with sub-lines), Assigned Users, Action
> - See GAP-6.5 through 6.9 for column details
>
> **Pagination:** 'Page N of N' with First/Prev/Next/Last buttons
>
> **In `MerchantController.php`**, `merchantTracker()` method should:
> - Accept `period` param (1_week, 2_weeks, 1_month) to determine cutoff date
> - Query merchants where `last_batch_date < NOW() - period` AND merchant is not closed
> - Scope to company_id
> - Apply processor/group/user filters
> - Return paginated results"

---

### [GAP-6.5] Merchant Tracker: Enabled toggle with AJAX endpoint

- [ ] **[GAP-6.5]** Add per-merchant enabled/disabled toggle in Merchant Tracker with AJAX persistence
  - **Status:** Missing
  - **Priority:** Critical
  - **Effort:** M
  - **Files:** `resources/views/admin/merchants/merchant-tracker.blade.php`, migration/model, `MerchantController.php`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Add a `tracker_enabled` boolean column to the `merchants` table via migration (default: true).
> 2. In `resources/views/admin/merchants/merchant-tracker.blade.php`, in the Enabled column, render a green checkmark toggle: `<button class='btn btn-sm tracker-toggle {{ $m->tracker_enabled ? 'btn-success' : 'btn-outline-secondary' }}' data-merchant-id='{{ $m->id }}'><i class='mdi mdi-check'></i></button><br><small>(Click to toggle)</small>`.
> 3. Add route: `Route::post('merchants/{id}/tracker-toggle', [MerchantController::class, 'trackerToggle'])->name('merchants.tracker-toggle')`.
> 4. In `MerchantController.php`, add `trackerToggle($id)` method that flips `tracker_enabled` and returns JSON `{enabled: true/false}`.
> 5. Add JS click handler that calls the AJAX endpoint and updates the button class."

---

### [GAP-6.7] Merchant Tracker: Merchant column sub-lines

- [ ] **[GAP-6.7]** Add sub-lines (In system date, First batch date, Last batch date) under merchant name in Tracker
  - **Status:** Missing
  - **Priority:** High
  - **Effort:** S
  - **Files:** `resources/views/admin/merchants/merchant-tracker.blade.php`

> **Claude Prompt:**
> "In `resources/views/admin/merchants/merchant-tracker.blade.php`, in the Merchant column, render:
> ```html
> <a href='{{ route('my-merchants.show', $m->id) }}'>{{ $m->dba }}</a>
> <br><small class='text-muted'>In system: {{ $m->created_at->format('m/d/Y') }}</small>
> <br><small class='text-muted'>First batch: {{ $m->start_processing_date ? Carbon::parse($m->start_processing_date)->format('m/d/Y') : 'N/A' }}</small>
> <br><small class='text-muted'>Last batch: {{ $m->last_batch_date ? Carbon::parse($m->last_batch_date)->format('m/d/Y') : 'N/A' }}</small>
> ```"

---
---

# §7 — The Scoop

---

### [GAP-7.1] Full The Scoop page implementation

- [ ] **[GAP-7.1]** Implement the full "The Scoop" portfolio intelligence dashboard (currently a stub)
  - **Status:** Missing
  - **Priority:** Critical
  - **Effort:** XL
  - **Files:** `resources/views/admin/merchants/the-scoop.blade.php`, `app/Http/Controllers/Merchants/MerchantController.php`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Read `resources/views/admin/merchants/the-scoop.blade.php` (currently a stub).
> 2. Read `app/Http/Controllers/Merchants/MerchantController.php` `theScoop()` method.
> 3. Read `public/assets/js/my-merchant-batches.js` for ApexCharts pattern reference.
>
> **Implement the full page:**
>
> **Filter bar:**
> - Month `<select>` (Jan–Dec, default: current month)
> - Year `<select>` (last 5 years + current, default: current year)
> - All Groups `<select>` dropdown
> - Agent/user `<select>` dropdown (blank = all agents)
> - 'Refresh' button
> - 'Show Primary User Data Only' checkbox
>
> **Two tabs: Portfolio Overview | Agents**
>
> **Portfolio Overview tab — 4 chart quadrants (2x2 grid):**
>
> 1. **Total Volume Shown bar chart** (ApexCharts bar):
>    - Title: 'Total Volume Shown: $[total]' (computed in controller)
>    - X-axis: last 15 months (Mar Y-1 through current)
>    - Y-axis: dollar amounts (format as $0, $5M, $10M etc.)
>    - Bar labels: abbreviated amounts ('20M', '17M')
>    - Data: SUM(batch_amount) from merchant_transactions GROUP BY YEAR, MONTH
>
> 2. **Total Residual Contribution pie chart** (ApexCharts donut):
>    - Title: 'Total Residual Contribution ([Month Year])'
>    - Segments: per agent/group residual amounts
>    - If no residual data uploaded: show placeholder 'Reports Not Uploaded' centered text
>
> 3. **Total Approvals Shown bar chart** (ApexCharts bar):
>    - Title: 'Total Approvals Shown: [count]'
>    - Monthly approval counts from merchant `approval_date`
>
> 4. **Total Approved pie chart** (ApexCharts donut):
>    - Title: 'Total Approved ([Month Year])'
>    - Segments: per agent approval counts with % labels
>
> **Agents tab:** Same 4 charts but data scoped per individual agent (not aggregate).
>
> **In `MerchantController.php`** `theScoop()` method:
> - Build 15-month volume array
> - Build monthly approval counts
> - Build per-agent/group breakdown
> - Pass all data as JSON-encoded variables"

---
---

# §8 — Deposit Exceptions

---

### [GAP-8.1] Full Deposit Exceptions page implementation

- [ ] **[GAP-8.1]** Implement the full Deposit Exceptions page (currently a stub)
  - **Status:** Missing
  - **Priority:** Critical
  - **Effort:** XL
  - **Files:** `resources/views/admin/merchants/deposit-exceptions.blade.php`, `app/Http/Controllers/Merchants/MerchantController.php`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Read `resources/views/admin/merchants/deposit-exceptions.blade.php` (currently a stub).
>
> **Implement the full page:**
>
> **Filters:** All Processors dropdown | All Groups dropdown | All Users dropdown | Refresh button. Second row: multi-select keyword filter ('All keywords selected') | Date range dropdown (Last 2 weeks default).
>
> **Exception table — grouped by category:**
> Render results grouped by the exception category found in the memo text of merchant_transactions. The 18 categories (from SOW) should appear as teal-colored header rows: `<tr class='table-section-header' style='background-color:#34c38f; color:white;'><td colspan='5'>ACH REJ REC (N)</td></tr>`.
>
> Under each header, render merchant rows with columns: MID | DBA (with inline `<input type='text' placeholder='Filter DBA...'>` that filters this group's rows) | Volume This Month | Memo Date | Memo (highlighted in red/orange if exception keyword present).
>
> The 18 exception categories to implement as grouped headers:
> ACH REJ REC, ACH SENT, ACCOUNT CLOSED ACH NACHA REJECTS, ACCOUNT CLOSED MERCHANT REQUEST, ACCOUNT CLOSED NO REASON STATED, ACCOUNT CLOSED PER NACHA REJECTS, AUTO-DVRTD, CHANGED FUNDING IND, CHANGED THE FUNDING INDICATOR, BATCHED CR, DIVERT, HELD, PLACED ON HOLD, RELEASED, ROR, SEC, SUSPENDED, TO COLL
>
> **In `MerchantController.php`** `depositExceptions()` method:
> - Query `merchant_transactions` where the `description` or memo matches exception keywords (use LIKE '%ACH REJ%', etc.)
> - Group results by matched exception category
> - For each merchant in each category, compute volume_this_month from SUM(amount) WHERE MONTH=current
> - Scope to company_id, apply filters"

---
---

# §9 — Dispute Reporting

---

### [GAP-9.1] Full Dispute Reporting page implementation

- [ ] **[GAP-9.1]** Implement the full Dispute Reporting page (currently a stub)
  - **Status:** Missing
  - **Priority:** Critical
  - **Effort:** XL
  - **Files:** `resources/views/admin/merchants/dispute-reporting.blade.php`, `app/Http/Controllers/Merchants/MerchantController.php`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Read `resources/views/admin/merchants/dispute-reporting.blade.php` (currently a stub).
>
> **Implement the full page:**
>
> **Filters (tag-style removable pills):**
> - All Types (multi-select: Chargeback, Retrieval)
> - All Processors (multi-select)
> - All Users (multi-select)
> - All Groups (multi-select)
> - Date period dropdown (Month to Date default)
> - From date / To date pickers
> - 'Show only initial records' checkbox
> - 'Search' button
>
> **Results table (14 columns):**
> DBA (with colored dot: red = active dispute) | MID | Approval Date | SIC Code | Type | Processor | Request Date | Dispute Date | Transaction Amount | Dispute Amount | Cardholder Account (masked) | Description | Disposition | Reason
>
> **Table features:**
> - Show N entries selector (default 50)
> - Export button (CSV/XLSX)
> - 'Showing N to N of N entries' pagination footer
> - Selected rows highlighted green (click to select)
>
> **In `MerchantController.php`** `disputeReporting()` method:
> - Query `merchant_transactions` where `status` IN (2=disputed, 3=chargeback) OR `transaction_type` IN ('chargeback', 'retrieval')
> - JOIN merchants table for DBA, MID, approval_date, sic_code, processor
> - Apply all filters
> - If 'show only initial records': filter to show first occurrence per case_number
> - Format `cardholder_account` as masked (show last 4 digits only: '****1234')
> - Paginate 50 per page
> - Export: return all matching rows as CSV/XLSX"

---
---

# §10 — Risk Reporting

---

### [GAP-10.1] Full Risk Reporting page implementation

- [ ] **[GAP-10.1]** Implement the full Risk Reporting page (currently a stub)
  - **Status:** Missing
  - **Priority:** Critical
  - **Effort:** XL
  - **Files:** `resources/views/admin/merchants/risk-reporting.blade.php`, `app/Http/Controllers/Merchants/MerchantController.php`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Read `resources/views/admin/merchants/risk-reporting.blade.php` (currently a stub).
>
> **Implement the full page:**
>
> **Filters (tag-style removable pills):**
> All Processors | All Datasources | All Groups | All Users | All SIC Codes | Month to Date dropdown
>
> **Results table (14 columns):**
> # | Merchant DBA | MID | Total Volume | Sales Volume | # of Sales | Average Ticket | Returns Volume | # of Returns | Return Volume Ratio | Return # Ratio | Chargeback Volume | # of Chargebacks | Chargeback Ratio
>
> All columns sortable. Format: Volume/Amount columns as currency, Ratio columns as percentage (e.g., '2.34%').
>
> **Row highlighting:**
> - Highlight row YELLOW if Return Volume Ratio > 5% OR Return # Ratio > 5%
> - Highlight row ORANGE if Chargeback Ratio > 1%
>
> **Table features:**
> - Show N entries selector (default 10)
> - Export button
> - 'Showing N to N of N entries (filtered from N total)' footer
>
> **In `MerchantController.php`** `riskReporting()` method:
> - Query `merchant_transactions` grouped by `merchant_id` for selected period
> - Compute per merchant:
>   - `total_volume` = SUM(amount) where all types
>   - `sales_volume` = SUM(amount) where transaction_type='sale' or status not dispute
>   - `num_sales` = COUNT where sale type
>   - `avg_ticket` = sales_volume / num_sales
>   - `returns_volume` = SUM(amount) where transaction_type='return'
>   - `num_returns` = COUNT where return
>   - `return_volume_ratio` = returns_volume / total_volume * 100
>   - `return_num_ratio` = num_returns / (num_sales + num_returns) * 100
>   - `chargeback_volume` = SUM(dispute_amount) where chargeback
>   - `num_chargebacks` = COUNT where chargeback
>   - `chargeback_ratio` = num_chargebacks / num_sales * 100
> - JOIN merchants for DBA, MID
> - Apply filters, paginate 10 per page"

---
---

# §11 — Risk Alerts

---

### [GAP-11.1] Full Risk Alerts page with two tabs

- [ ] **[GAP-11.1]** Implement the full Risk Alerts page with Triggered Alerts and Set Alerts tabs (currently a stub)
  - **Status:** Missing
  - **Priority:** Critical
  - **Effort:** XL
  - **Files:** `resources/views/admin/merchants/risk-alerts.blade.php`, NEW migration `create_merchant_risk_alert_configs_table`, NEW `app/Models/Merchants/MerchantRiskAlertConfig.php`, `app/Http/Controllers/Merchants/MerchantController.php`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Create migration `create_merchant_risk_alert_configs_table`: fields: `id`, `company_id`, `alert_type` (ENUM: chargeback_ratio, fraud_volume, dispute_count, volume_spike), `threshold_value` (decimal), `threshold_type` (ENUM: percentage, absolute), `is_active` (bool default true), `timestamps`.
> 2. Create `app/Models/Merchants/MerchantRiskAlertConfig.php` model.
>
> **Implement `risk-alerts.blade.php`:**
>
> **Two Bootstrap tabs:** 'Triggered Alerts' | 'Set Alerts'
>
> **Triggered Alerts tab:**
> - Section title: 'Triggered Alerts – [Month Year]'
> - Search box above table
> - Table (9 columns): # | DBA | MID | Total Fraud Volume | Total Fraud Volume Ratio | Total Chargeback Number | Total Dispute Number Ratio | All Chargeback Volume | All Chargeback Number
> - All columns sortable
> - Empty state: `<div class='text-center text-muted py-4'>There are no results in this table</div>`
>
> **Set Alerts tab:**
> - Configuration form with fields:
>   - Chargeback Ratio Threshold (%) — input, default 1%
>   - Fraud Volume Threshold ($) — input
>   - Dispute Count Threshold — input
>   - Volume Spike Threshold (% above average) — input, default 150%
>   - 'Active' toggle for each threshold
>   - 'Save Alert Configuration' button → AJAX POST to `route('merchants.risk-alerts.save')`
>
> **In `MerchantController.php`** `riskAlerts()` method:
> - Load `MerchantRiskAlertConfig` for company
> - Query merchants that EXCEED configured thresholds for current month (use same calculation as Risk Reporting)
> - Return triggered merchants list + config for view
>
> **Business rule (GAP-14.4 & 14.5):** A merchant is 'triggered' if:
> - Chargeback Ratio > config threshold (default 1%), OR
> - Monthly volume > 150% of their 6-month average volume"

---
---

# §12 — Merchant Management

---

### [GAP-12.1 through 12.10] Full Merchant Management fixes

- [ ] **[GAP-12.1]** Add "Search By" dropdown selector (DBA, MID, Legal Name) to merchant management filters
  - **Status:** Missing | **Priority:** Medium | **Effort:** S
  - **Files:** `resources/views/admin/merchants/index.blade.php` (merchant management view), `app/Http/Controllers/Merchants/MerchantController.php`

> **Claude Prompt:**
> "In `resources/views/admin/merchants/index.blade.php` (the merchant management list view, not my-merchants), add a 'Search By' dropdown before the search text input with options: DBA | MID | Legal Name. Pass as `search_by` in the filter request. In `MerchantController.php` index method, use `search_by` to determine which column to apply the `LIKE %search%` filter to."

---

- [ ] **[GAP-12.4]** Add bulk selection checkbox column to merchant management table
  - **Status:** Missing | **Priority:** High | **Effort:** M
  - **Files:** `resources/views/admin/merchants/index.blade.php`

> **Claude Prompt:**
> "In `resources/views/admin/merchants/index.blade.php`, add a checkbox column as the first column in the merchant management table. Header: `<th><input type='checkbox' id='select-all-merchants'></th>`. Each row: `<td><input type='checkbox' class='merchant-row-checkbox' value='{{ $m->id }}'></td>`. Add JS: `$('#select-all-merchants').on('change', function() { $('.merchant-row-checkbox').prop('checked', this.checked); });`. Track selected IDs for bulk action buttons."

---

- [ ] **[GAP-12.6]** Add edit icon (pencil) in Action column for each row
  - **Status:** Missing | **Priority:** High | **Effort:** S
  - **Files:** `resources/views/admin/merchants/index.blade.php`

> **Claude Prompt:**
> "In `resources/views/admin/merchants/index.blade.php`, add an Action column with `<a href='{{ route('merchants.edit', $m->id) }}' class='btn btn-sm btn-outline-primary'><i class='mdi mdi-pencil'></i></a>` for each row. Place the Action column after the MID column."

---

- [ ] **[GAP-12.9]** Add "Show Deactivated Merchants" checkbox to toggle soft-deleted records
  - **Status:** Missing | **Priority:** High | **Effort:** M
  - **Files:** `resources/views/admin/merchants/index.blade.php`, `app/Http/Controllers/Merchants/MerchantController.php`

> **Claude Prompt:**
> "In `resources/views/admin/merchants/index.blade.php` filter area, add: `<div class='form-check'><input class='form-check-input' type='checkbox' id='show-deactivated' name='show_deactivated' value='1'><label class='form-check-label'>Show Deactivated Merchants</label></div>`. In `MerchantController.php` index method, if `show_deactivated` is set in request, add `->withTrashed()` or `->onlyTrashed()` to the query (depending on whether to show all or only deactivated). Deactivated merchants should have a visual indicator (grayed-out row or 'Deactivated' badge)."

---
---

# §13 — Merchant Importer

---

### [GAP-13.1] Full 5-step Merchant Importer wizard

- [ ] **[GAP-13.1]** Implement the full 5-step Merchant Importer wizard (currently a stub)
  - **Status:** Missing
  - **Priority:** Critical
  - **Effort:** XL
  - **Files:** `resources/views/admin/merchants/merchant-importer.blade.php`, `app/Http/Controllers/Merchants/MerchantController.php`, NEW `app/Services/MerchantImportService.php`, NEW `public/assets/js/merchant-importer.js`

> **Claude Prompt:**
> "In the HubWallet CRM Laravel project at `/var/www/html/hubwallet-crm`:
>
> 1. Read `resources/views/admin/merchants/merchant-importer.blade.php` (currently a stub).
>
> **Implement the 5-step wizard:**
>
> **Step Navigation Bar (top):**
> Horizontal step indicator: Step 1: Upload a File | Step 2: Select Defaults | Step 3: Map Fields | Step 4: Preview | Step 5: Import Activity. Active step is teal/green, completed steps show a checkmark, future steps are gray.
>
> **Step 1 — Upload a File:**
> - Instructions text: 'Please drag & drop or select the CSV file to be imported. First row must contain the column names that will be used for mapping.'
> - Large dashed-border drag-and-drop zone: `<div id='drop-zone' class='border border-dashed rounded p-5 text-center'>Drop CSV file here to upload, or<br><button class='btn btn-primary mt-2' id='browse-btn'>Browse</button><input type='file' id='csv-file-input' accept='.csv' class='d-none'></div>`
> - JS: Handle drag-over, drag-leave, drop events. On file select or drop, validate it's a CSV, display filename, enable 'Next' button.
> - POST file to `route('merchants.importer.upload')` via AJAX FormData. Controller stores file in temp storage, returns column names in CSV header row.
>
> **Step 2 — Select Defaults:**
> - Form fields: Processor (select), Group (select), Sales Rep (select), Assigned User (select)
> - These defaults are applied to all imported records that don't have those fields in the CSV.
> - 'Next' button stores defaults in session and proceeds to Step 3.
>
> **Step 3 — Map Fields:**
> - Two-column layout: left = CSV column names (from uploaded file header), right = CRM field dropdown for each
> - CRM fields: MID, DBA, Legal Name, Phone, Processor, Group, Approval Date, Status, etc.
> - 'Next' button stores mapping in session and proceeds to Step 4.
>
> **Step 4 — Preview:**
> - Table showing first 5 rows as they will be imported after applying mapping
> - Show column headers as CRM field names, values from CSV
> - 'Back' button to adjust mapping; 'Import' button to start import.
>
> **Step 5 — Import Activity:**
> - Progress bar showing import progress (if large file)
> - Summary: Total Rows | Imported Successfully | Failed | Skipped (duplicates)
> - Error table: Row # | CSV Data | Error Reason
> - 'Download Error Report' button (CSV of failed rows)
>
> **In `MerchantController.php`:**
> - `importer()` — renders the wizard view
> - `importerUpload()` — stores CSV temp, returns headers
> - `importerProcess()` — delegates to `MerchantImportService`
>
> **Create `app/Services/MerchantImportService.php`:**
> - `process(array $mapping, array $defaults, string $filePath, int $companyId)` method
> - Reads CSV row by row
> - Maps fields per step 3 mapping
> - Applies defaults for missing fields
> - Validates each row: MID uniqueness (query merchants WHERE mid=value AND company_id=companyId), required fields
> - Creates `Merchant` records in bulk
> - Returns array: ['success' => N, 'failed' => N, 'errors' => []]"

---
---

# §14 — Business Rules

---

### [GAP-14.1] "Not Processing" segment in Portfolio Breakdown

> *Covered in GAP-3.6 above.*

---

### [GAP-14.2] Merchant status: Seasonal and Hold

- [ ] **[GAP-14.2]** Add "Seasonal" and "Hold" merchant status values to the system
  - **Status:** Missing | **Priority:** Medium | **Effort:** M
  - **Files:** `app/Models/Merchants/Merchant.php`, new migration

> **Claude Prompt:**
> "In `app/Models/Merchants/Merchant.php`, find the status enum constants (currently: 1=Open, 2=Closed). Add: 3=Seasonal, 4=Hold. Update the `getStatusTextAttribute()` and `getStatusBadgeAttribute()` accessors to include these new values (Seasonal = blue badge, Hold = yellow badge). Create a migration to update any ENUM column if needed (or if status is stored as tinyint, no migration needed — just update model constants). Update all status dropdown menus in merchant forms to include these new options."

---

### [GAP-14.3] Chargeback ratio KPI (chargebacks / transactions per month)

> *Covered in GAP-4.9 (KPI carousel — Chargeback Ratio card).*

---

### [GAP-14.4 & 14.5] Risk alert auto-trigger thresholds

> *Covered in GAP-11.1 (Risk Alerts implementation). The business rule: trigger when chargeback ratio > 1% OR volume > 150% of 6-month average.*

---

### [GAP-14.6] Merchant Tracker configurable threshold

> *Covered in GAP-6.1 (Merchant Tracker implementation). The configurable dropdown in the filter bar serves this requirement.*

---

### [GAP-14.7] MID uniqueness validation on import

> *Covered in GAP-13.1 (MerchantImportService.php — validate MID uniqueness per company).*

---

### [GAP-14.8] The Scoop: "Reports Not Uploaded" state for residuals

> *Covered in GAP-7.1 (The Scoop implementation). The residual pie chart must show a 'Reports Not Uploaded' placeholder when no residual data exists for the selected month/year.)*

---

### [GAP-14.9] Deposit Exception categories match processor memo codes exactly

> *Covered in GAP-8.1 (Deposit Exceptions implementation). All 18 category strings must match exactly as defined in the SOW (case-sensitive memo code matching).*

---
---

# Implementation Checklist Summary

## Critical Gaps (38 items) — Must complete to meet core SOW
- [ ] GAP-3.6 Not Processing segment
- [ ] GAP-4.5 Authorizations tab
- [ ] GAP-4.6 Memos tab + model
- [ ] GAP-4.7 1099K tab
- [ ] GAP-4.9 KPI cards carousel (11 cards)
- [ ] GAP-4.37 Statements backend
- [ ] GAP-4.39 1099K data
- [ ] GAP-5.1 Portfolio Activity full page
- [ ] GAP-5.6 Monthly Booked Volume chart
- [ ] GAP-6.1 Merchant Tracker full page
- [ ] GAP-6.3 Configurable time window
- [ ] GAP-6.5 Enabled toggle with AJAX
- [ ] GAP-7.1 The Scoop full page
- [ ] GAP-7.4 Portfolio Overview + Agents tabs
- [ ] GAP-7.5 Total Volume chart
- [ ] GAP-7.6 Residual pie chart
- [ ] GAP-7.7 Approvals bar chart
- [ ] GAP-7.8 Approved pie chart
- [ ] GAP-7.10 The Scoop backend data
- [ ] GAP-8.1 Deposit Exceptions full page
- [ ] GAP-8.5 Exception grouping by category
- [ ] GAP-8.6 All 18 exception categories
- [ ] GAP-8.9 Exception backend query
- [ ] GAP-9.1 Dispute Reporting full page
- [ ] GAP-9.5 All 14 dispute columns
- [ ] GAP-9.9 Dispute backend query
- [ ] GAP-10.1 Risk Reporting full page
- [ ] GAP-10.3 All 14 risk columns
- [ ] GAP-10.6 Risk ratio calculations
- [ ] GAP-11.1 Risk Alerts full page + model
- [ ] GAP-11.2 Triggered + Set Alerts tabs
- [ ] GAP-11.4 All 9 triggered alert columns
- [ ] GAP-11.6 Set Alerts config UI
- [ ] GAP-11.7 Auto-trigger thresholds
- [ ] GAP-13.1 Merchant Importer full wizard
- [ ] GAP-13.3 CSV drag & drop upload
- [ ] GAP-13.5 Field mapping interface
- [ ] GAP-13.7 Import Activity results

---

*Report generated by Claude Code — 2026-03-25*
*SOW: SOW_03_My_Merchants_v2.docx | Codebase: /var/www/html/hubwallet-crm*
