fix(secretary): allow insurance pages; add grantable subscription resource

Insurance pages redirected to the dashboard: the insurance-pricing/claims
routes never listed `secretary`, so RoleRoute bounced a secretary who had
insurances.view and saw the menu item. Added secretary + permission
['insurances','view'] to both routes; also gated my-financial with
['payments','view'] for consistency.

«خرید اشتراک» was owner-only with no permission toggle, so it could not be
granted. Added a `subscription` secretary resource (view/create) end-to-end:
- entity DEFAULT_PERMISSIONS + SecretaryPermissions type + both secretary forms.
- backend: SubscriptionController::my (view) and trial (create),
  PaymentController::initiateSubscription (create). resolveEntity in
  SubscriptionController was already secretary-aware.
- frontend: subscription + subscription/success routes accept secretary +
  permission; settings navs gate «خرید اشتراک» by ['subscription','view'].

Tests: subscription denied-by-default / allowed-when-granted. docs/api
secretary.md updated (resource list, enforcement map, JSON example).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-07-23 18:21:53 +03:30
co-authored by Claude Opus 4.8
parent 2fbb7233ef
commit 43db753942
11 changed files with 56 additions and 9 deletions
@@ -226,6 +226,25 @@ class SecretaryResourceEnforcementTest extends ApiTestCase
$this->assertSame(200, $this->responseCode());
}
public function testSubscriptionDeniedByDefault(): void
{
[$secretary] = $this->makeClinicSecretary();
$this->em->flush();
$this->authJson('GET', '/api/v1/subscription/my', $secretary);
$this->assertSame(403, $this->responseCode());
}
public function testSubscriptionAllowedWhenGranted(): void
{
[$secretary, $rel] = $this->makeClinicSecretary();
$rel->mergePermissions(['resources' => ['subscription' => ['view' => true]]]);
$this->em->flush();
$this->authJson('GET', '/api/v1/subscription/my', $secretary);
$this->assertSame(200, $this->responseCode());
}
public function testPatientDeleteSeparateFromUpdate(): void
{
// منشی با patients.update ولی بدون patients.delete نباید بتواند حذف کند.