diff --git a/app/Http/Controllers/Care/Concerns/RequiresPlanFeature.php b/app/Http/Controllers/Care/Concerns/RequiresPlanFeature.php index 1ecf48e..aa27aac 100644 --- a/app/Http/Controllers/Care/Concerns/RequiresPlanFeature.php +++ b/app/Http/Controllers/Care/Concerns/RequiresPlanFeature.php @@ -11,12 +11,16 @@ trait RequiresPlanFeature { /** * For page views: return an upgrade screen when the org lacks the feature. + * + * @param 'pro'|'enterprise' $planHint Which tier to highlight in pricing copy */ protected function proFeatureUpgradeView( Request $request, string $feature, string $title, string $description, + string $planHint = 'pro', + ?string $upgradeBody = null, ): ?View { $organization = $this->organization($request); $plans = app(PlanService::class); @@ -25,11 +29,17 @@ trait RequiresPlanFeature return null; } + $isEnterprise = $planHint === 'enterprise'; + return view('care.settings.pro-feature', [ 'organization' => $organization, 'title' => $title, 'description' => $description, - 'proPriceMinor' => $plans->proPricePerBranchMinor(), + 'planLabel' => $isEnterprise ? 'Enterprise' : 'Pro', + 'proPriceMinor' => $isEnterprise + ? $plans->enterprisePricePerBranchMinor() + : $plans->proPricePerBranchMinor(), + 'upgradeBody' => $upgradeBody, ]); } diff --git a/app/Http/Controllers/Care/ReportController.php b/app/Http/Controllers/Care/ReportController.php index b145b7e..4d1b925 100644 --- a/app/Http/Controllers/Care/ReportController.php +++ b/app/Http/Controllers/Care/ReportController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Care; use App\Http\Controllers\Controller; +use App\Http\Controllers\Care\Concerns\RequiresPlanFeature; use App\Http\Controllers\Care\Concerns\ScopesToAccount; use App\Models\Branch; use App\Services\Care\AdminOverviewService; @@ -10,6 +11,7 @@ use App\Services\Care\CareFeatures; use App\Services\Care\OrganizationResolver; use App\Services\Care\PlanService; use App\Services\Care\ReportService; +use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Carbon; use Illuminate\View\View; @@ -17,8 +19,11 @@ use Symfony\Component\HttpFoundation\StreamedResponse; class ReportController extends Controller { + use RequiresPlanFeature; use ScopesToAccount; + private const ASSESSMENT_ANALYTICS_UPSELL = 'Assessment analytics are part of Care Enterprise. Upgrade to unlock multi-patient clinical form and outcome reports.'; + public function __construct( protected ReportService $reports, protected AdminOverviewService $adminOverview, @@ -54,6 +59,21 @@ class ReportController extends Controller public function show(Request $request, string $type): View { $this->authorizeReport($request, $type); + + if ($type === 'assessments') { + if ($upgrade = $this->proFeatureUpgradeView( + $request, + 'assessment_analytics', + 'Assessment analytics', + 'Org-level clinical form and outcome analytics across patients.', + 'enterprise', + 'GHS '.number_format(app(PlanService::class)->enterprisePricePerBranchMinor() / 100, 0) + .' / branch / month for Enterprise — assessment analytics, priority support, and advanced workflows on top of Pro.', + )) { + return $upgrade; + } + } + $organization = $this->organization($request); $branchScope = app(OrganizationResolver::class)->branchScope($this->member($request)); $branchId = $request->input('branch_id') ? (int) $request->input('branch_id') : $branchScope; @@ -80,9 +100,16 @@ class ReportController extends Controller ]); } - public function export(Request $request, string $type): StreamedResponse + public function export(Request $request, string $type): StreamedResponse|RedirectResponse { $this->authorizeReport($request, $type); + + if ($type === 'assessments') { + if ($deny = $this->denyWithoutFeature($request, 'assessment_analytics', self::ASSESSMENT_ANALYTICS_UPSELL)) { + return $deny; + } + } + abort_unless( app(\App\Services\Care\CarePermissions::class)->can($this->member($request), 'reports.finance.export'), 403, @@ -155,11 +182,7 @@ class ReportController extends Controller app(CareFeatures::class)->enabled($organization, CareFeatures::ASSESSMENTS_ENGINE), 404, ); - abort_unless( - app(PlanService::class)->hasFeature($organization, 'assessment_analytics'), - 403, - 'Assessment analytics require an Enterprise plan.', - ); + // Plan gate: show() renders upgrade UI; export() redirects via denyWithoutFeature. } } diff --git a/resources/views/care/settings/pro-feature.blade.php b/resources/views/care/settings/pro-feature.blade.php index 37c5f13..6b85c8c 100644 --- a/resources/views/care/settings/pro-feature.blade.php +++ b/resources/views/care/settings/pro-feature.blade.php @@ -8,8 +8,12 @@

- GHS {{ number_format($proPriceMinor / 100, 0) }} / branch / month for Pro — - multi-branch locations, laboratory, pharmacy, encounter billing, and in-app service queues. + @if (! empty($upgradeBody)) + {{ $upgradeBody }} + @else + GHS {{ number_format($proPriceMinor / 100, 0) }} / branch / month for {{ $planLabel ?? 'Pro' }} — + multi-branch locations, laboratory, pharmacy, encounter billing, and in-app service queues. + @endif

View plans diff --git a/tests/Feature/CareAssessmentExtrasTest.php b/tests/Feature/CareAssessmentExtrasTest.php index c7bfd2c..c3d1ea3 100644 --- a/tests/Feature/CareAssessmentExtrasTest.php +++ b/tests/Feature/CareAssessmentExtrasTest.php @@ -111,7 +111,10 @@ class CareAssessmentExtrasTest extends TestCase $this->actingAs($this->user) ->get(route('care.reports.show', ['type' => 'assessments'])) - ->assertForbidden(); + ->assertOk() + ->assertSee('Upgrade your plan') + ->assertSee('View plans') + ->assertSee('Enterprise'); } public function test_fhir_export_web_and_api(): void