feat: Implement permission gate for appointment and billing controllers

- Added PermissionGateTrait to manage access control for AppointmentPlanController and BillingController.
- Introduced denyUnlessGrantedForPlanning method in AppointmentPlanController to handle specific permission checks for planning appointments.
- Updated existing methods in both controllers to utilize the new permission checks.
- Refactored ResourcePermissionTrait to use PermissionGateTrait for cleaner permission management.
- Added tests to ensure proper permission enforcement across different scenarios, including cross-tenant access restrictions for staff.
This commit is contained in:
hamed
2026-08-08 10:27:13 +03:30
parent c452150a83
commit 934405c42d
14 changed files with 830 additions and 67 deletions
@@ -52,7 +52,15 @@ class SecretaryAppointmentScopeTest extends ApiTestCase
$this->assertSame(1, $body['meta']['totalRecords']);
}
public function testSecretaryWithNoAssignmentSeesNothing(): void
/**
* منشیِ بدون رابطهٔ فعال دیگر لیست خالی نمی‌گیرد، ۴۰۳ می‌گیرد.
*
* تا ۲۰۲۶-۰۸-۰۷ این مسیر گِیت مجوز نداشت و پاسخ خالی از فیلترِ خودِ کوئری
* می‌آمد. با گِیتِ `appointments.view` (یافتهٔ ۸ آدیت)، `SecretaryAccessChecker`
* برای منشیِ بی‌رابطه `can()=false` می‌دهد — همان رفتار fail-closed مستندِ
* `SecretaryPermissionChecker`. «خالیِ خاموش» با «اجازه نداری» یکی نیست.
*/
public function testSecretaryWithNoAssignmentIsDenied(): void
{
$owner = $this->createUser(['ROLE_CLINIC']);
$clinic = new Clinic($owner);
@@ -73,7 +81,7 @@ class SecretaryAppointmentScopeTest extends ApiTestCase
$body = $this->authJson('GET', '/api/v1/my/appointments', $secretaryUser);
// بدون رابطه‌ی فعال → resolveSecretaryFilter=null → لیست خالی
$this->assertSame(0, $body['meta']['totalRecords']);
$this->assertSame(403, $this->responseCode());
$this->assertSame('ERR_FORBIDDEN_001', $body['errors'][0]['code']);
}
}