feat: granular permissions, export backup, user/role management
Some checks failed
Tests / PHP 8.3 (push) Has been cancelled
Tests / PHP 8.4 (push) Has been cancelled
Tests / PHP 8.5 (push) Has been cancelled

- PermissionSeeder: 29 permissions with role mapping (admin/manager/staff)
- Policies updated to use hasPermissionTo() instead of hasRole()
- ExportBackup command: JSON per-table backup with chunk processing
- UserResource: CRUD users with role assignment (admin only)
- RoleResource: CRUD roles with permission assignment (admin only)
- UserPolicy + RolePolicy: admin-only access control
- ImportCskh: detailed skip logging with color in dry-run mode
- Widgets: permission-based visibility checks
- Tests: 8/8 pass (21 assertions)
This commit is contained in:
2026-05-02 04:47:10 +00:00
parent 7c5055075b
commit 3a8db5cae6
40 changed files with 1804 additions and 440 deletions

View File

@@ -7,12 +7,27 @@ use App\Models\FeedbackAttachment;
use App\Models\FeedbackChannel;
use App\Models\User;
use App\Services\ClosingService;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
beforeEach(function () {
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
// Create permissions
Permission::firstOrCreate(['name' => 'close-ticket']);
Permission::firstOrCreate(['name' => 'view-feedback']);
Permission::firstOrCreate(['name' => 'create-feedback']);
Permission::firstOrCreate(['name' => 'update-feedback']);
Permission::firstOrCreate(['name' => 'add-interaction']);
Permission::firstOrCreate(['name' => 'transfer-department']);
});
// ─── Scenario 1: Staff cannot close ticket ───────────────────────────
test('staff cannot close ticket - returns 403 forbidden', function () {
\Spatie\Permission\Models\Role::create(['name' => 'staff']);
$staffRole = Role::create(['name' => 'staff']);
$staffRole->givePermissionTo(['view-feedback', 'create-feedback', 'update-feedback', 'add-interaction']);
$staff = User::factory()->create();
$staff->assignRole('staff');
@@ -45,7 +60,8 @@ test('staff cannot close ticket - returns 403 forbidden', function () {
// ─── Scenario 2: Manager cannot close without proof_of_resolution ────
test('manager cannot close ticket without proof_of_resolution evidence', function () {
\Spatie\Permission\Models\Role::create(['name' => 'manager']);
$managerRole = Role::create(['name' => 'manager']);
$managerRole->givePermissionTo(['close-ticket', 'view-feedback', 'create-feedback', 'update-feedback', 'transfer-department']);
$manager = User::factory()->create();
$manager->assignRole('manager');
@@ -81,7 +97,9 @@ test('manager cannot close ticket without proof_of_resolution evidence', functio
// ─── Scenario 2b: Manager CAN close with proof_of_resolution ─────────
test('manager can close ticket with proof_of_resolution evidence', function () {
\Illuminate\Support\Facades\Notification::fake();
\Spatie\Permission\Models\Role::create(['name' => 'manager']);
$managerRole = Role::create(['name' => 'manager']);
$managerRole->givePermissionTo(['close-ticket', 'view-feedback', 'create-feedback', 'update-feedback', 'transfer-department']);
$manager = User::factory()->create();
$manager->assignRole('manager');
@@ -122,7 +140,8 @@ test('manager can close ticket with proof_of_resolution evidence', function () {
// ─── Scenario 2c: Manager cannot close ticket from other department ──
test('manager cannot close ticket from department they do not manage', function () {
\Spatie\Permission\Models\Role::create(['name' => 'manager']);
$managerRole = Role::create(['name' => 'manager']);
$managerRole->givePermissionTo(['close-ticket', 'view-feedback', 'create-feedback', 'update-feedback', 'transfer-department']);
$manager = User::factory()->create();
$manager->assignRole('manager');

View File

@@ -7,12 +7,24 @@ use App\Models\FeedbackChannel;
use App\Models\TicketTransferLog;
use App\Models\User;
use App\Services\TransferService;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
beforeEach(function () {
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
Permission::firstOrCreate(['name' => 'transfer-department']);
Permission::firstOrCreate(['name' => 'view-feedback']);
Permission::firstOrCreate(['name' => 'create-feedback']);
Permission::firstOrCreate(['name' => 'update-feedback']);
});
// ─── Scenario 3: Transfer Flow ──────────────────────────────────────
test('transfer creates log, updates department, and notifies manager', function () {
\Spatie\Permission\Models\Role::create(['name' => 'manager']);
$managerRole = Role::create(['name' => 'manager']);
$managerRole->givePermissionTo(['transfer-department', 'view-feedback', 'create-feedback', 'update-feedback']);
\Illuminate\Support\Facades\Notification::fake();
@@ -73,7 +85,8 @@ test('transfer creates log, updates department, and notifies manager', function
// ─── Transfer without reason should fail ────────────────────────────
test('transfer without reason throws validation error', function () {
\Spatie\Permission\Models\Role::create(['name' => 'manager']);
$managerRole = Role::create(['name' => 'manager']);
$managerRole->givePermissionTo(['transfer-department', 'view-feedback', 'create-feedback', 'update-feedback']);
$sender = User::factory()->create();
$sender->assignRole('manager');