Show upgrade UI for assessment analytics instead of bare 403.
Deploy Ladill Care / deploy (push) Successful in 41s

Non-Enterprise orgs hitting /reports/assessments get the in-app plans upsell; CSV export redirects to View plans with an upsell flash.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-20 16:26:43 +00:00
co-authored by Cursor
parent d4b645d85b
commit cebc9c8e4c
4 changed files with 50 additions and 10 deletions
@@ -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,
]);
}
+29 -6
View File
@@ -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.
}
}
@@ -8,8 +8,12 @@
<x-settings.card title="Upgrade your plan">
<p class="text-sm text-slate-600">
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
</p>
<div class="mt-4 flex flex-wrap items-center gap-3">
<a href="{{ route('care.pro.index') }}" class="btn-primary">View plans</a>
+4 -1
View File
@@ -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