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:
@@ -242,10 +242,10 @@ export default function App() {
|
|||||||
<Route path="my-patients/:recordUuid/session/:sessionUuid/pay" element={<RoleRoute roles={['doctor', 'secretary', 'clinic']} blockClinicScope><SessionPaymentPage /></RoleRoute>} />
|
<Route path="my-patients/:recordUuid/session/:sessionUuid/pay" element={<RoleRoute roles={['doctor', 'secretary', 'clinic']} blockClinicScope><SessionPaymentPage /></RoleRoute>} />
|
||||||
<Route path="patients/:recordUuid/session/:sessionUuid/edit" element={<RoleRoute roles={['doctor', 'secretary', 'clinic']} blockClinicScope><EditSessionPage /></RoleRoute>} />
|
<Route path="patients/:recordUuid/session/:sessionUuid/edit" element={<RoleRoute roles={['doctor', 'secretary', 'clinic']} blockClinicScope><EditSessionPage /></RoleRoute>} />
|
||||||
<Route path="my-patients/:recordUuid/session/:sessionUuid/edit" element={<RoleRoute roles={['doctor', 'secretary', 'clinic']} blockClinicScope><EditSessionPage /></RoleRoute>} />
|
<Route path="my-patients/:recordUuid/session/:sessionUuid/edit" element={<RoleRoute roles={['doctor', 'secretary', 'clinic']} blockClinicScope><EditSessionPage /></RoleRoute>} />
|
||||||
<Route path="insurance-pricing" element={<RoleRoute roles={['doctor', 'clinic']} blockClinicScope><InsurancePricingPage /></RoleRoute>} />
|
<Route path="insurance-pricing" element={<RoleRoute roles={['doctor', 'clinic', 'secretary']} blockClinicScope permission={['insurances', 'view']}><InsurancePricingPage /></RoleRoute>} />
|
||||||
<Route path="claims" element={<RoleRoute roles={['doctor', 'clinic']} blockClinicScope><ClaimsPage /></RoleRoute>} />
|
<Route path="claims" element={<RoleRoute roles={['doctor', 'clinic', 'secretary']} blockClinicScope permission={['insurances', 'view']}><ClaimsPage /></RoleRoute>} />
|
||||||
<Route path="claims/:patientUuid" element={<RoleRoute roles={['doctor', 'clinic']} blockClinicScope><ClaimPatientDetailPage /></RoleRoute>} />
|
<Route path="claims/:patientUuid" element={<RoleRoute roles={['doctor', 'clinic']} blockClinicScope><ClaimPatientDetailPage /></RoleRoute>} />
|
||||||
<Route path="my-financial" element={<RoleRoute roles={['doctor', 'secretary', 'clinic']}><MyFinancialPage /></RoleRoute>} />
|
<Route path="my-financial" element={<RoleRoute roles={['doctor', 'secretary', 'clinic']} permission={['payments', 'view']}><MyFinancialPage /></RoleRoute>} />
|
||||||
|
|
||||||
{/* فاز ۲ — دکتر / کلینیک */}
|
{/* فاز ۲ — دکتر / کلینیک */}
|
||||||
<Route path="staff" element={<RoleRoute roles={['doctor', 'clinic', 'secretary']} blockClinicScope permission={['staff', 'view']}><StaffPage /></RoleRoute>} />
|
<Route path="staff" element={<RoleRoute roles={['doctor', 'clinic', 'secretary']} blockClinicScope permission={['staff', 'view']}><StaffPage /></RoleRoute>} />
|
||||||
@@ -253,9 +253,9 @@ export default function App() {
|
|||||||
<Route path="account-settings" element={<RoleRoute roles={['doctor', 'clinic', 'secretary']}><AccountSettingsPage /></RoleRoute>} />
|
<Route path="account-settings" element={<RoleRoute roles={['doctor', 'clinic', 'secretary']}><AccountSettingsPage /></RoleRoute>} />
|
||||||
<Route path="tags-settings" element={<RoleRoute roles={['doctor', 'clinic', 'secretary']} blockClinicScope permission={['tags', 'view']}><TagsSettingsPage /></RoleRoute>} />
|
<Route path="tags-settings" element={<RoleRoute roles={['doctor', 'clinic', 'secretary']} blockClinicScope permission={['tags', 'view']}><TagsSettingsPage /></RoleRoute>} />
|
||||||
<Route path="appointment-settings" element={<RoleRoute roles={['doctor', 'secretary']} blockClinicScope permission={['appointment_settings', 'view']}><AppointmentSettingsPage /></RoleRoute>} />
|
<Route path="appointment-settings" element={<RoleRoute roles={['doctor', 'secretary']} blockClinicScope permission={['appointment_settings', 'view']}><AppointmentSettingsPage /></RoleRoute>} />
|
||||||
<Route path="subscription" element={<RoleRoute roles={['doctor', 'clinic']} blockClinicScope><SubscriptionPage /></RoleRoute>} />
|
<Route path="subscription" element={<RoleRoute roles={['doctor', 'clinic', 'secretary']} blockClinicScope permission={['subscription', 'view']}><SubscriptionPage /></RoleRoute>} />
|
||||||
<Route path="discounts" element={<RoleRoute roles={['doctor', 'clinic', 'secretary']} blockClinicScope permission={['discounts', 'view']}><DiscountsPage /></RoleRoute>} />
|
<Route path="discounts" element={<RoleRoute roles={['doctor', 'clinic', 'secretary']} blockClinicScope permission={['discounts', 'view']}><DiscountsPage /></RoleRoute>} />
|
||||||
<Route path="subscription/success" element={<RoleRoute roles={['doctor', 'clinic']} blockClinicScope><PaymentSuccessPage /></RoleRoute>} />
|
<Route path="subscription/success" element={<RoleRoute roles={['doctor', 'clinic', 'secretary']} blockClinicScope permission={['subscription', 'view']}><PaymentSuccessPage /></RoleRoute>} />
|
||||||
<Route path="clinic-services" element={<RoleRoute roles={['doctor', 'clinic', 'secretary']} blockClinicScope permission={['services', 'view']}><ClinicServicesPage /></RoleRoute>} />
|
<Route path="clinic-services" element={<RoleRoute roles={['doctor', 'clinic', 'secretary']} blockClinicScope permission={['services', 'view']}><ClinicServicesPage /></RoleRoute>} />
|
||||||
<Route path="clinic-services/:uuid" element={<RoleRoute roles={['doctor', 'clinic', 'secretary']} blockClinicScope permission={['services', 'view']}><ServiceDetailPage /></RoleRoute>} />
|
<Route path="clinic-services/:uuid" element={<RoleRoute roles={['doctor', 'clinic', 'secretary']} blockClinicScope permission={['services', 'view']}><ServiceDetailPage /></RoleRoute>} />
|
||||||
<Route path="inventory" element={<RoleRoute roles={['doctor', 'clinic', 'secretary']} blockClinicScope permission={['inventory', 'view']}><InventoryPage /></RoleRoute>} />
|
<Route path="inventory" element={<RoleRoute roles={['doctor', 'clinic', 'secretary']} blockClinicScope permission={['inventory', 'view']}><InventoryPage /></RoleRoute>} />
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ type NavItem = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const NAV_ITEMS: NavItem[] = [
|
const NAV_ITEMS: NavItem[] = [
|
||||||
{ key: 'subscription', label: 'خرید اشتراک', to: '/admin/subscription' },
|
{ key: 'subscription', label: 'خرید اشتراک', to: '/admin/subscription', perm: ['subscription', 'view'] },
|
||||||
{ key: 'payment', label: 'مدیریت پرداخت', to: '/admin/my-financial', perm: ['payments', 'view'] },
|
{ key: 'payment', label: 'مدیریت پرداخت', to: '/admin/my-financial', perm: ['payments', 'view'] },
|
||||||
{ key: 'appointment', label: 'مدیریت نوبت دهی', to: '/admin/appointment-settings', roles: ['doctor'], perm: ['appointment_settings', 'view'] },
|
{ key: 'appointment', label: 'مدیریت نوبت دهی', to: '/admin/appointment-settings', roles: ['doctor'], perm: ['appointment_settings', 'view'] },
|
||||||
{ key: 'appointment', label: 'مدیریت نوبت دهی', to: '/admin/settings/appointment-settings', roles: ['clinic'], perm: ['appointment_settings', 'view'] },
|
{ key: 'appointment', label: 'مدیریت نوبت دهی', to: '/admin/settings/appointment-settings', roles: ['clinic'], perm: ['appointment_settings', 'view'] },
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export type SettingsMenuItem = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const SETTINGS_MENU: SettingsMenuItem[] = [
|
export const SETTINGS_MENU: SettingsMenuItem[] = [
|
||||||
{ key: 'subscription', label: 'خرید اشتراک', icon: CreditCardIcon, to: '/admin/subscription' },
|
{ key: 'subscription', label: 'خرید اشتراک', icon: CreditCardIcon, to: '/admin/subscription', perm: ['subscription', 'view'] },
|
||||||
{ key: 'doctor', label: 'مدیریت پزشک', icon: UserIcon, to: '/admin/profile', roles: ['doctor'] },
|
{ key: 'doctor', label: 'مدیریت پزشک', icon: UserIcon, to: '/admin/profile', roles: ['doctor'] },
|
||||||
{ key: 'appointment', label: 'مدیریت نوبت دهی', icon: CalendarDaysIcon, to: '/admin/appointment-settings', roles: ['doctor'], perm: ['appointment_settings', 'view'] },
|
{ key: 'appointment', label: 'مدیریت نوبت دهی', icon: CalendarDaysIcon, to: '/admin/appointment-settings', roles: ['doctor'], perm: ['appointment_settings', 'view'] },
|
||||||
{ key: 'appointment', label: 'مدیریت نوبت دهی', icon: CalendarDaysIcon, to: '/admin/settings/appointment-settings', roles: ['clinic'], perm: ['appointment_settings', 'view'] },
|
{ key: 'appointment', label: 'مدیریت نوبت دهی', icon: CalendarDaysIcon, to: '/admin/settings/appointment-settings', roles: ['clinic'], perm: ['appointment_settings', 'view'] },
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ const EMPTY_PERMISSIONS: SecretaryPermissions = {
|
|||||||
sms: { view: false, create: false, update: false, delete: false },
|
sms: { view: false, create: false, update: false, delete: false },
|
||||||
appointment_settings: { view: false, update: false },
|
appointment_settings: { view: false, update: false },
|
||||||
clinic_doctors: { view: false, create: false, update: false, delete: false },
|
clinic_doctors: { view: false, create: false, update: false, delete: false },
|
||||||
|
subscription: { view: false, create: false },
|
||||||
};
|
};
|
||||||
|
|
||||||
type PermSection = keyof SecretaryPermissions;
|
type PermSection = keyof SecretaryPermissions;
|
||||||
@@ -229,6 +230,14 @@ const PERMISSION_SECTIONS: {
|
|||||||
{ key: "delete", label: "حذف پزشک" },
|
{ key: "delete", label: "حذف پزشک" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "subscription",
|
||||||
|
title: "خرید اشتراک",
|
||||||
|
items: [
|
||||||
|
{ key: "view", label: "مشاهده اشتراک" },
|
||||||
|
{ key: "create", label: "خرید/فعالسازی اشتراک" },
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function PermissionAccordions({
|
function PermissionAccordions({
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const DEFAULT_PERMISSIONS: SecretaryPermissions = {
|
|||||||
sms: { view: false, create: false, update: false, delete: false },
|
sms: { view: false, create: false, update: false, delete: false },
|
||||||
appointment_settings: { view: false, update: false },
|
appointment_settings: { view: false, update: false },
|
||||||
clinic_doctors: { view: false, create: false, update: false, delete: false },
|
clinic_doctors: { view: false, create: false, update: false, delete: false },
|
||||||
|
subscription: { view: false, create: false },
|
||||||
};
|
};
|
||||||
|
|
||||||
type PermSection = keyof SecretaryPermissions;
|
type PermSection = keyof SecretaryPermissions;
|
||||||
@@ -154,6 +155,13 @@ const PERMISSION_LABELS: Record<PermSection, { label: string; actions: { key: st
|
|||||||
{ key: 'delete', label: 'حذف' },
|
{ key: 'delete', label: 'حذف' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
subscription: {
|
||||||
|
label: 'خرید اشتراک',
|
||||||
|
actions: [
|
||||||
|
{ key: 'view', label: 'مشاهده' },
|
||||||
|
{ key: 'create', label: 'خرید' },
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function PermissionsMatrix({
|
function PermissionsMatrix({
|
||||||
|
|||||||
@@ -536,6 +536,10 @@ export interface SecretaryPermissions {
|
|||||||
update: boolean;
|
update: boolean;
|
||||||
delete: boolean;
|
delete: boolean;
|
||||||
};
|
};
|
||||||
|
subscription: {
|
||||||
|
view: boolean;
|
||||||
|
create: boolean;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Specialty {
|
export interface Specialty {
|
||||||
|
|||||||
@@ -92,7 +92,8 @@ Create a secretary for a doctor.
|
|||||||
"discounts": { "view": false, "create": false, "update": false, "delete": false },
|
"discounts": { "view": false, "create": false, "update": false, "delete": false },
|
||||||
"sms": { "view": false, "create": false, "update": false, "delete": false },
|
"sms": { "view": false, "create": false, "update": false, "delete": false },
|
||||||
"appointment_settings": { "view": false, "update": false },
|
"appointment_settings": { "view": false, "update": false },
|
||||||
"clinic_doctors": { "view": false, "create": false, "update": false, "delete": false }
|
"clinic_doctors": { "view": false, "create": false, "update": false, "delete": false },
|
||||||
|
"subscription": { "view": false, "create": false }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,7 +131,7 @@ Create a secretary for a doctor.
|
|||||||
|
|
||||||
**Permissions Structure:**
|
**Permissions Structure:**
|
||||||
|
|
||||||
مجموعهٔ منابع (resources) بر اساس صفحات و ماژولهای در دسترسِ منشی است: `appointments`, `patients`, `payments`, `insurances`, `addresses`, `clinic_info`, `inventory`, `tags`, `services`, `staff`, `discounts`, `sms`, `appointment_settings`, `clinic_doctors`. `mergePermissions` هر منبع/اکشن ارسالشده را deep-merge میکند. منابعِ `inventory`, `tags`, `services`, `staff`, `discounts`, `sms`, `appointment_settings`, `clinic_doctors` بهصورت پیشفرض همه `false`اند (default-deny)؛ بقیه طبق `DEFAULT_PERMISSIONS`. منبعِ `appointment_settings` فقط `view/update` دارد. منبعِ `clinic_doctors` **فقط در حالت کلینیک** معنا دارد (پزشک مستقل نه toggle نه منو).
|
مجموعهٔ منابع (resources) بر اساس صفحات و ماژولهای در دسترسِ منشی است: `appointments`, `patients`, `payments`, `insurances`, `addresses`, `clinic_info`, `inventory`, `tags`, `services`, `staff`, `discounts`, `sms`, `appointment_settings`, `clinic_doctors`, `subscription`. منبعِ `subscription` فقط `view/create` دارد. `mergePermissions` هر منبع/اکشن ارسالشده را deep-merge میکند. منابعِ `inventory`, `tags`, `services`, `staff`, `discounts`, `sms`, `appointment_settings`, `clinic_doctors` بهصورت پیشفرض همه `false`اند (default-deny)؛ بقیه طبق `DEFAULT_PERMISSIONS`. منبعِ `appointment_settings` فقط `view/update` دارد. منبعِ `clinic_doctors` **فقط در حالت کلینیک** معنا دارد (پزشک مستقل نه toggle نه منو).
|
||||||
|
|
||||||
**اعمال (enforcement):** همهٔ منابع در بکاند enforce میشوند، نه فقط `appointments`. منبعِ حقیقت، ستون JSON `permission` روی ردیفِ فعالِ `DoctorSecretary` در محیطِ فعالِ کاربر (`UserActiveContext.db_uuid`) است؛ نقطهٔ مرکزی `App\Secretary\Security\SecretaryAccessChecker` (`can` / `canOrNonSecretary` / `denyUnlessGranted`). نبودِ مجوز → `403 ERR_FORBIDDEN_001`. نقشه:
|
**اعمال (enforcement):** همهٔ منابع در بکاند enforce میشوند، نه فقط `appointments`. منبعِ حقیقت، ستون JSON `permission` روی ردیفِ فعالِ `DoctorSecretary` در محیطِ فعالِ کاربر (`UserActiveContext.db_uuid`) است؛ نقطهٔ مرکزی `App\Secretary\Security\SecretaryAccessChecker` (`can` / `canOrNonSecretary` / `denyUnlessGranted`). نبودِ مجوز → `403 ERR_FORBIDDEN_001`. نقشه:
|
||||||
|
|
||||||
@@ -148,6 +149,7 @@ Create a secretary for a doctor.
|
|||||||
| `sms` | `SmsWalletController` (balance/charge/logs/settings). endpointهای admin (قالب/ارسال) همچنان `ROLE_ADMIN` | view/create/update |
|
| `sms` | `SmsWalletController` (balance/charge/logs/settings). endpointهای admin (قالب/ارسال) همچنان `ROLE_ADMIN` | view/create/update |
|
||||||
| `appointment_settings` | `AppointmentSettingsController::denyDoctorAccess` → `SecretaryAccessChecker::canForDoctor` (اسکوپِ پزشکِ تخصیصیافته + توگل). `clinic_uuid` برای محیطِ کلینیک لازم است | view/update |
|
| `appointment_settings` | `AppointmentSettingsController::denyDoctorAccess` → `SecretaryAccessChecker::canForDoctor` (اسکوپِ پزشکِ تخصیصیافته + توگل). `clinic_uuid` برای محیطِ کلینیک لازم است | view/update |
|
||||||
| `clinic_doctors` (فقط کلینیک) | `ClinicController::detachDoctor` (delete)، `ClinicDoctorPermissionController` (view/update)، `ClinicInvitationController` (create/view/update/delete) via `SecretaryAccessChecker::canForClinic` | view/create/update/delete |
|
| `clinic_doctors` (فقط کلینیک) | `ClinicController::detachDoctor` (delete)، `ClinicDoctorPermissionController` (view/update)، `ClinicInvitationController` (create/view/update/delete) via `SecretaryAccessChecker::canForClinic` | view/create/update/delete |
|
||||||
|
| `subscription` | `SubscriptionController::my` (view) و `trial` (create)، `PaymentController::initiateSubscription` (create). resolveEntity از قبل منشیآگاه است | view/create |
|
||||||
|
|
||||||
نقشهای غیرمنشی (`ROLE_CLINIC`/`ROLE_DOCTOR`/`ROLE_ADMIN`) از این چک عبور میکنند (`canOrNonSecretary` برایشان `true`). منشیِ بدون رابطهٔ فعال/context هیچ مجوزی ندارد → همهچیز `403`.
|
نقشهای غیرمنشی (`ROLE_CLINIC`/`ROLE_DOCTOR`/`ROLE_ADMIN`) از این چک عبور میکنند (`canOrNonSecretary` برایشان `true`). منشیِ بدون رابطهٔ فعال/context هیچ مجوزی ندارد → همهچیز `403`.
|
||||||
|
|
||||||
|
|||||||
@@ -386,6 +386,7 @@ class PaymentController extends BaseController
|
|||||||
#[Route('/api/v1/subscription-payment', methods: ['POST'])]
|
#[Route('/api/v1/subscription-payment', methods: ['POST'])]
|
||||||
public function initiateSubscription(Request $request, #[CurrentUser] User $user): JsonResponse
|
public function initiateSubscription(Request $request, #[CurrentUser] User $user): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->secretaryAccess->denyUnlessGranted($user, 'subscription', 'create');
|
||||||
$data = json_decode($request->getContent(), true) ?? [];
|
$data = json_decode($request->getContent(), true) ?? [];
|
||||||
$gatewayName = trim($data['gateway'] ?? 'mellat');
|
$gatewayName = trim($data['gateway'] ?? 'mellat');
|
||||||
$frontendAddress = trim($data['frontend_address'] ?? '');
|
$frontendAddress = trim($data['frontend_address'] ?? '');
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class DoctorSecretary
|
|||||||
'sms' => ['view' => false, 'create' => false, 'update' => false, 'delete' => false],
|
'sms' => ['view' => false, 'create' => false, 'update' => false, 'delete' => false],
|
||||||
'appointment_settings' => ['view' => false, 'update' => false],
|
'appointment_settings' => ['view' => false, 'update' => false],
|
||||||
'clinic_doctors' => ['view' => false, 'create' => false, 'update' => false, 'delete' => false],
|
'clinic_doctors' => ['view' => false, 'create' => false, 'update' => false, 'delete' => false],
|
||||||
|
'subscription' => ['view' => false, 'create' => false],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class SubscriptionController extends BaseController
|
|||||||
private readonly ClinicRepository $clinicRepo,
|
private readonly ClinicRepository $clinicRepo,
|
||||||
private readonly UserActiveContextRepository $contextRepo,
|
private readonly UserActiveContextRepository $contextRepo,
|
||||||
private readonly EntityManagerInterface $em,
|
private readonly EntityManagerInterface $em,
|
||||||
|
private readonly \App\Secretary\Security\SecretaryAccessChecker $secretaryAccess,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
// ── Public ──────────────────────────────────────────────────────────────
|
// ── Public ──────────────────────────────────────────────────────────────
|
||||||
@@ -56,6 +57,7 @@ class SubscriptionController extends BaseController
|
|||||||
#[IsGranted('IS_AUTHENTICATED_FULLY')]
|
#[IsGranted('IS_AUTHENTICATED_FULLY')]
|
||||||
public function my(#[CurrentUser] User $user): JsonResponse
|
public function my(#[CurrentUser] User $user): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->secretaryAccess->denyUnlessGranted($user, 'subscription', 'view');
|
||||||
[$entityType, $entityId] = $this->resolveEntity($user);
|
[$entityType, $entityId] = $this->resolveEntity($user);
|
||||||
if ($entityId === null) {
|
if ($entityId === null) {
|
||||||
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
|
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
|
||||||
@@ -76,6 +78,7 @@ class SubscriptionController extends BaseController
|
|||||||
#[IsGranted('IS_AUTHENTICATED_FULLY')]
|
#[IsGranted('IS_AUTHENTICATED_FULLY')]
|
||||||
public function trial(#[CurrentUser] User $user): JsonResponse
|
public function trial(#[CurrentUser] User $user): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->secretaryAccess->denyUnlessGranted($user, 'subscription', 'create');
|
||||||
[$entityType, $entityId] = $this->resolveEntity($user);
|
[$entityType, $entityId] = $this->resolveEntity($user);
|
||||||
if ($entityId === null) {
|
if ($entityId === null) {
|
||||||
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
|
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
|
||||||
|
|||||||
@@ -226,6 +226,25 @@ class SecretaryResourceEnforcementTest extends ApiTestCase
|
|||||||
$this->assertSame(200, $this->responseCode());
|
$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
|
public function testPatientDeleteSeparateFromUpdate(): void
|
||||||
{
|
{
|
||||||
// منشی با patients.update ولی بدون patients.delete نباید بتواند حذف کند.
|
// منشی با patients.update ولی بدون patients.delete نباید بتواند حذف کند.
|
||||||
|
|||||||
Reference in New Issue
Block a user