# Purchase Plugin Permissions Fix

## Problem

After installing the Purchase plugin, permissions for the **Request for Quotation** (RFQ) and **Orders** sections in the role editor would not save correctly. Specifically:

- Unchecking Purchase RFQ or Orders permissions in Settings > Roles > Edit Role, then saving, appeared to succeed with no errors
- Refreshing the page showed the permissions as re-ticked (still granted)
- This affected only the Purchase plugin sections; all other plugin permissions saved correctly

## Root Cause

The `PermissionManager` in `plugins/webkul/support/src/PermissionManager.php` generates permission names from Filament resource class names. The Purchase plugin contains two resources that share the same class names as their Sale plugin counterparts:

| Resource Class (Purchase) | Resource Class (Sale) | Generated Permission (both) |
|---|---|---|
| `Webkul\Purchase\Filament\Admin\Clusters\Orders\Resources\QuotationResource` | `Webkul\Sale\Filament\Clusters\Orders\Resources\QuotationResource` | `view_any_quotation` |
| `Webkul\Purchase\Filament\Admin\Clusters\Orders\Resources\OrderResource` | `Webkul\Sale\Filament\Clusters\Orders\Resources\OrderResource` | `view_any_order` |

Because both plugins share the same permission name (e.g. `view_any_quotation`), when a user unchecked the Purchase RFQ section in the role editor, the Sale plugin's `QuotationResource` still had that permission checked — so the permission was never actually removed from the role. On page reload, both Purchase and Sale sections appeared ticked because the shared permission still existed.

## Fix

Three files were modified:

---

### 1. `plugins/webkul/support/src/PermissionManager.php`

**What:** Added the two conflicting Purchase resources to the `getConflictingResources()` array.

**Why:** When a resource is in this list, the `PermissionManager` prefixes its generated permission names with the plugin name, making them unique. Purchase's `QuotationResource` now generates `view_any_purchase_quotation` instead of the shared `view_any_quotation`.

**Change:** At the bottom of the `getConflictingResources()` return array, add:

```php
// Purchase resources that share names with Sale resources - needs plugin prefix
'Webkul\Purchase\Filament\Admin\Clusters\Orders\Resources\QuotationResource',
'Webkul\Purchase\Filament\Admin\Clusters\Orders\Resources\OrderResource',
```

**Before (end of array):**
```php
'Webkul\Invoice\Filament\Clusters\Configuration\Resources\ProductCategoryResource',
'Webkul\Invoice\Filament\Clusters\Configuration\Resources\BankAccountResource',
'Webkul\Invoice\Filament\Clusters\Vendors\Resources\ProductResource',
```

**After:**
```php
'Webkul\Invoice\Filament\Clusters\Configuration\Resources\ProductCategoryResource',
'Webkul\Invoice\Filament\Clusters\Configuration\Resources\BankAccountResource',
'Webkul\Invoice\Filament\Clusters\Vendors\Resources\ProductResource',
// Purchase resources that share names with Sale resources - needs plugin prefix
'Webkul\Purchase\Filament\Admin\Clusters\Orders\Resources\QuotationResource',
'Webkul\Purchase\Filament\Admin\Clusters\Orders\Resources\OrderResource',
```

**Resulting permission name changes:**

| Old Name | New Name |
|---|---|
| `view_any_quotation` | `view_any_purchase_quotation` |
| `view_quotation` | `view_purchase_quotation` |
| `create_quotation` | `create_purchase_quotation` |
| `update_quotation` | `update_purchase_quotation` |
| `delete_quotation` | `delete_purchase_quotation` |
| `delete_any_quotation` | `delete_any_purchase_quotation` |
| `view_any_order` | `view_any_purchase_order` |
| `view_order` | `view_purchase_order` |
| `create_order` | `create_purchase_order` |
| `update_order` | `update_purchase_order` |
| `delete_order` | `delete_purchase_order` |
| `delete_any_order` | `delete_any_purchase_order` |

Note: `view_any_quotation` and `view_any_order` still exist — they are now exclusively for Sale plugin's resources.

---

### 2. `plugins/webkul/purchases/src/Policies/QuotationPolicy.php`

**What:** Updated all six permission string checks to use the new `purchase_`-prefixed names.

**Why:** The `QuotationPolicy` is Laravel's policy class for the `Webkul\Purchase\Models\Quotation` model. It must reference the same permission names that `PermissionManager` generates. Without this update, the policy would check for `view_any_quotation` (which no longer maps to the Purchase resource) and always return the wrong result.

**Change:** Replace all permission strings:

```php
// BEFORE
public function viewAny(User $user): bool
{
    return $user->can('view_any_quotation');
}

public function view(User $user, Quotation $quotation): bool
{
    return $user->can('view_quotation');
}

public function create(User $user): bool
{
    return $user->can('create_quotation');
}

public function update(User $user, Quotation $quotation): bool
{
    if (! $user->can('update_quotation')) {
        return false;
    }
    return $this->hasAccess($user, $quotation);
}

public function delete(User $user, Quotation $quotation): bool
{
    if (! $user->can('delete_quotation')) {
        return false;
    }
    return $this->hasAccess($user, $quotation);
}

public function deleteAny(User $user): bool
{
    return $user->can('delete_any_quotation');
}
```

```php
// AFTER
public function viewAny(User $user): bool
{
    return $user->can('view_any_purchase_quotation');
}

public function view(User $user, Quotation $quotation): bool
{
    return $user->can('view_purchase_quotation');
}

public function create(User $user): bool
{
    return $user->can('create_purchase_quotation');
}

public function update(User $user, Quotation $quotation): bool
{
    if (! $user->can('update_purchase_quotation')) {
        return false;
    }
    return $this->hasAccess($user, $quotation);
}

public function delete(User $user, Quotation $quotation): bool
{
    if (! $user->can('delete_purchase_quotation')) {
        return false;
    }
    return $this->hasAccess($user, $quotation);
}

public function deleteAny(User $user): bool
{
    return $user->can('delete_any_purchase_quotation');
}
```

Note: `Purchase\OrderResource` does **not** have a policy file — Shield handles its authorization directly via the generated permission name, so no policy update is needed for Orders.

---

### 3. Database — Regenerate Permissions and Sync Roles

After the code changes above, run the following commands to update the permissions table and reassign them to the Admin role:

```bash
# Regenerate all Shield permissions from the updated resource list
php artisan shield:generate --all --option=permissions --panel=admin

# Clear the permission cache
php artisan permission:cache-reset
php artisan cache:clear
```

Then run this PHP snippet (or use `php artisan tinker`) to sync all permissions to the Admin role:

```php
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;

$role = Role::where('name', config('filament-shield.panel_user.name', 'Admin'))->first();
$role->permissions()->sync(Permission::query()->pluck('id')->all());
```

This is also done automatically when reinstalling a plugin (the `InstallCommand` in `plugins/webkul/support/src/Console/Commands/InstallCommand.php` calls `regenerateAdminPanelPermissions()` at the end of `handle()`), so reinstalling the Purchase plugin after applying the code fixes will handle the DB step automatically.

---

## Notes on Other Purchase Policies

For reference, the other Purchase policies already use correct, unique permission names and do **not** need changes:

- **`PurchaseOrderPolicy.php`** — Uses `view_any_purchase::order` format. The `PurchaseOrderResource` class name itself generates a unique name (`purchase_order` → `purchase::order`) without needing a conflicting-resources entry.
- **`RequisitionPolicy.php`** — Uses `view_any_purchase::agreement`. Same pattern as above.

These were not broken because their resource class names (`PurchaseOrderResource`, `PurchaseAgreementResource`) already differ from any Sale resource names.

---

## How PermissionManager Generates Names

For reference, the naming algorithm in `PermissionManager::buildPermissionKeyUsing()`:

1. Takes the resource class FQCN, e.g. `Webkul\Purchase\Filament\Admin\Clusters\Orders\Resources\QuotationResource`
2. Extracts the identifier: everything after `Resources\` minus the `Resource` suffix → `Quotation`
3. Converts to snake_case → `quotation`
4. Replaces `_` with `::` → `quotation` (no change for single words)
5. For **conflicting** resources, prepends the plugin name from `Webkul\{PluginName}\...` → `purchase`
6. Final key: `{affix}_purchase_quotation` e.g. `view_any_purchase_quotation`

This is why multi-word resource names like `PurchaseOrder` produce `purchase::order` (the `_` becomes `::`), which is automatically unique.
