# PRD: Lead Fields Module (Tabs) — Bug Fixes

**Date:** 2026-04-29
**Module:** Lead Field Tabs (`manages.lead-field-tabs`)
**Failing Test Cases:** TC_LF_003, TC_LF_006, TC_LF_007, TC_LF_016, TC_LF_017, TC_LF_032

---

## Overview

Six QA test cases fail in the Lead Field Tabs module. They map to five logical fix groups covering a commented-out Open API link, a missing back button, wrong permission gate on the Permissions action button, missing client-side max-length enforcement and duplicate-name validation for tab names, and the permissions modal not opening.

All changes are in two files only.

---

## Fix 1 — Open API Documentation Link (TC_LF_003)

### Failing Test
| ID | Priority | Scenario |
|----|----------|----------|
| TC_LF_003 | Medium | Locate Open API link → click "View Open API Documentation" → page/link opens without error |

### Root Cause
In `index.blade.php` (lines 19-21), the entire "Pro Tip" + "Open API" block is wrapped in an HTML comment:

```html
<!-- <span><b>Pro Tip:</b> When dragging, press the Home, End, Page Up, or Page Down keys...</span>
<span><b>Open API:</b> View Open API Documentation</span> -->
```

The Open API span is never rendered; the link does not exist on the page.

### Affected File
- `resources/views/admin/manages/lead-field-tabs/index.blade.php`

### Fix
Remove the HTML comment and render the Open API line as a clickable anchor. Leave the "Pro Tip" line commented since it is unrelated to this fix.

```blade
<div class="normal-text">
    <span>
        <b>Instructions:</b> Use this page to manage your data fields. Lead tabs and lead fields
        are the basis of organizing and tracking information about your prospects and customers.<br>
        To reorder the items below, click-and-drag the item to the desired location.
    </span>
    {{-- Pro Tip line intentionally omitted --}}
    <span>
        <b>Open API:</b>
        <a href="{{ route('api.documentation') }}" target="_blank" rel="noopener">
            View Open API Documentation
        </a>
    </span>
</div>
```

> **Note:** Replace `route('api.documentation')` with the correct named route or URL for the system's OpenAPI/Swagger documentation page. If no such route exists yet, use a `config()` value or a plain URL string until one is created.

---

## Fix 2 — Action Buttons Displayed per Tab (TC_LF_006)

### Failing Test
| ID | Priority | Scenario |
|----|----------|----------|
| TC_LF_006 | High | Each tab row should display: Delete, Edit Tab, Permissions, Add/Remove Fields |

### Root Cause
The Permissions button is nested inside `@if($permission['edit'])`:

```blade
@if($permission['edit'])
    <a ... title="Edit">...</a>
    <a ... title="Manage Permissions">...</a>   {{-- ← wrong gate ─}}
@endif
```

`$permission['edit']` and `$permission['manageRole']` are separate capabilities. When the test user has `manageRole=true` but `edit=false` (a common QA role setup), the Permissions button is invisible, leaving only Delete and Add/Remove Fields visible — 2 of 4 expected buttons. The `manageRole` key is already fetched in `index()` and merged into `$permission` but is never used in the view.

### Affected File
- `resources/views/admin/manages/lead-field-tabs/index.blade.php`

### Fix
Extract the Permissions anchor from the `@if($permission['edit'])` block and gate it independently on `$permission['manageRole']`. Add `data-url` attribute to both Edit and Permissions buttons (consistent with other modules):

```blade
<td class="d-flex gap-2 justify-content-end">
    @if($permission['destroy'] && !$tab->is_system_defined)
        @if(\App\Models\Masters\MasterLeadFieldTab::getDeleteIconPermission($tab->id))
            <a href="javascript:void(0);" class="action-btn"
               data-toggle="tooltip" title="Delete"
               data-confirm="Are You Sure?|This action can not be undone. Do you want to continue?"
               data-confirm-yes="event.preventDefault();
               document.getElementById('delete-form-{{ $tab->id }}').submit();">
                <i class="{{ \Config::get('settings.icon_delete') }}"></i>
            </a>
            {!! html()->form('DELETE', route($routePrefix . '.destroy', $tab->id))
                ->attributes(['style' => 'display:none', 'id' => 'delete-form-' . $tab->id])
                ->open() !!}
            {!! html()->form()->close() !!}
        @endif
    @endif

    @if($permission['edit'])
        <a href="{{ route($routePrefix . '.edit', $tab->id) }}"
           class="action-btn show-modal"
           data-url="{{ route($routePrefix . '.edit', $tab->id) }}"
           data-toggle="tooltip" title="Edit Tab">
            <i class="{{ \Config::get('settings.icon_edit') }}"></i>
        </a>
    @endif

    @if($permission['manageRole'])
        <a href="{{ route($routePrefix . '.permissions', $tab->id) }}"
           class="action-btn show-modal"
           data-url="{{ route($routePrefix . '.permissions', $tab->id) }}"
           data-toggle="tooltip" title="Manage Permissions">
            <i class="{{ \Config::get('settings.icon_settings') }}"></i>
        </a>
    @endif

    @if($permission['create'])
        <a href="{{ route('manages.lead-fields.index', ['lead_field_tab_id' => $tab->id]) }}"
           type="button" class="btn add-btn">
            Add/Remove Fields
        </a>
    @endif
</td>
```

---

## Fix 3 — Back to Administration Button (TC_LF_007)

### Failing Test
| ID | Priority | Scenario |
|----|----------|----------|
| TC_LF_007 | Medium | Click "Back to Administration" → user is redirected to administration/manage area |

### Root Cause
The `index.blade.php` for lead-field-tabs opens directly at `<div class="project-box">` (line 14) with no back button. The standard `back-section` div present in other modules (email templates, SMS templates) is absent here.

### Affected File
- `resources/views/admin/manages/lead-field-tabs/index.blade.php`

### Fix
Add the back button immediately before the opening `<div class="project-box">`:

```blade
<div class="col-12">
    <div class="back-section mb-2">
        <button type="button" class="btn back-btn waves-effect" onclick="window.history.back()">
            <i class="{{ \Config::get('settings.icon_back') }}"></i> Back to Administration
        </button>
    </div>
    <div class="project-box">
```

---

## Fix 4 — Duplicate Tab Name Validation (TC_LF_016)

### Failing Test
| ID | Priority | Scenario |
|----|----------|----------|
| TC_LF_016 | High | Enter an existing tab name → system rejects the duplicate with a validation message |

### Root Cause
`__formPost()` in `LeadFieldTabController` validates `'title' => 'required|max:255'` only. The model's `store()` method also performs no uniqueness check. Two tabs with identical names can be created for the same company.

### Affected File
- `app/Http/Controllers/Manages/LeadFieldTabController.php`

### Fix
In `__formPost()`, after `$this->validate(...)` passes, add a case-insensitive duplicate check that excludes the current record during updates. Move the duplicate check before the model store call:

```php
protected function __formPost(Request $request, $id = 0)
{
    try {
        $this->validate($request, [
            'type'       => 'required|integer',
            'title'      => 'required|max:255',
            'position'   => 'nullable|max:50',
            'visibility' => 'nullable|max:50',
            'role_ids'   => 'nullable|array',
        ]);

        $duplicate = \App\Models\Masters\MasterLeadFieldTab::whereRaw('LOWER(title) = LOWER(?)', [$request->title])
            ->where('company_id', \Auth::user()->company_id)
            ->whereNull('deleted_at')
            ->when($id, fn($q) => $q->where('id', '!=', $id))
            ->exists();

        if ($duplicate) {
            return redirect()->back()
                ->withInput()
                ->withErrors(['title' => 'A tab with this name already exists.']);
        }

        $input    = $request->all();
        $response = $this->_model->store($input, $id, $request);

        if (in_array($response['status'], [200, 201])) {
            if (isset($input['role_ids']) && !empty($input['role_ids'])) {
                $tabId    = $id ?: $response['data']->id;
                $tabModel = $this->_model->findOrFail($tabId);
                $syncData = [];
                foreach ($input['role_ids'] as $roleId) {
                    $syncData[$roleId] = ['type' => 1];
                }
                $tabModel->deleteExcludedUserClasses()->sync($syncData);
            }
            return redirect()->back()->with('success', $response['message']);
        }
        return redirect()->back()->with('error', $response['message']);

    } catch (\Exception $e) {
        \App\Models\ErrorLog::Log($e);
        return Helper::rj($e->getMessage(), 500);
    }
}
```

**Key details:**
- `LOWER()` comparison makes the check case-insensitive (`Sales Reps` = `sales reps`)
- `whereNull('deleted_at')` ensures soft-deleted tabs do not block re-creation of the same name
- `when($id, ...)` skips self-comparison during edits
- `$this->validate()` is called first so `ValidationException` propagates before any DB queries

---

## Fix 5 — Max Length Client-side Enforcement (TC_LF_017)

### Failing Test
| ID | Priority | Scenario |
|----|----------|----------|
| TC_LF_017 | High | Enter a tab name > 255 chars → system restricts or shows validation without UI distortion |

### Root Cause
The `title` field in `__formUiGeneration()` has no `maxlength` HTML attribute. Server-side `max:255` catches this on submission, but the user has no client-side feedback while typing — they can enter an unlimited string that visually distorts the form before hitting server validation.

### Affected File
- `app/Http/Controllers/Manages/LeadFieldTabController.php`

### Fix
Add `'maxlength' => 255` to the `title` field attributes:

```php
'title' => [
    'type'  => 'text',
    'label' => 'Tab Name',
    'value' => $data->title ?? '',
    'attributes' => [
        'required'     => true,
        'autocomplete' => 'off',
        'maxlength'    => 255,
    ],
],
```

---

## Fix 6 — Permissions Modal Does Not Open (TC_LF_032)

### Failing Test
| ID | Priority | Scenario |
|----|----------|----------|
| TC_LF_032 | High | Click Permissions button for a tab → permissions modal opens with correct context |

### Root Cause
This failure is a downstream consequence of Fix 2. The Permissions button is gated by `$permission['edit']`. A test user with only `manageRole` permission cannot see the button and cannot click it, so the modal never opens. The modal infrastructure itself (route `manages.lead-field-tabs.permissions`, controller method `permissionsForm()`, and model relationship `permissionUserClasses()`) is all correctly implemented and will work once the button is made visible.

Fix 2 (moving the button to `@if($permission['manageRole'])` and adding `data-url`) directly resolves TC_LF_032 with no additional code changes needed.

> **If the modal opens but shows an error:** Check that `initUIGeneration($id)` in the parent `Controller` class does not gate on `edit` permission. If it does, replace the `initUIGeneration` call in `permissionsForm()` with a direct `$this->_model->findOrFail($id)` call instead.

---

## Summary

| Fix | Test Cases | Files Changed |
|-----|------------|---------------|
| Fix 1: Uncomment + link Open API | TC_LF_003 | `lead-field-tabs/index.blade.php` |
| Fix 2: Extract Permissions button to manageRole gate + data-url | TC_LF_006, TC_LF_032 | `lead-field-tabs/index.blade.php` |
| Fix 3: Back to Administration button | TC_LF_007 | `lead-field-tabs/index.blade.php` |
| Fix 4: Duplicate tab name check | TC_LF_016 | `LeadFieldTabController.php` |
| Fix 5: maxlength=255 on title field | TC_LF_017 | `LeadFieldTabController.php` |

**Total files:** 2
