Show upgrade UI for assessment analytics instead of bare 403.
Deploy Ladill Care / deploy (push) Successful in 41s
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:
@@ -11,12 +11,16 @@ trait RequiresPlanFeature
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* For page views: return an upgrade screen when the org lacks the feature.
|
* 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(
|
protected function proFeatureUpgradeView(
|
||||||
Request $request,
|
Request $request,
|
||||||
string $feature,
|
string $feature,
|
||||||
string $title,
|
string $title,
|
||||||
string $description,
|
string $description,
|
||||||
|
string $planHint = 'pro',
|
||||||
|
?string $upgradeBody = null,
|
||||||
): ?View {
|
): ?View {
|
||||||
$organization = $this->organization($request);
|
$organization = $this->organization($request);
|
||||||
$plans = app(PlanService::class);
|
$plans = app(PlanService::class);
|
||||||
@@ -25,11 +29,17 @@ trait RequiresPlanFeature
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$isEnterprise = $planHint === 'enterprise';
|
||||||
|
|
||||||
return view('care.settings.pro-feature', [
|
return view('care.settings.pro-feature', [
|
||||||
'organization' => $organization,
|
'organization' => $organization,
|
||||||
'title' => $title,
|
'title' => $title,
|
||||||
'description' => $description,
|
'description' => $description,
|
||||||
'proPriceMinor' => $plans->proPricePerBranchMinor(),
|
'planLabel' => $isEnterprise ? 'Enterprise' : 'Pro',
|
||||||
|
'proPriceMinor' => $isEnterprise
|
||||||
|
? $plans->enterprisePricePerBranchMinor()
|
||||||
|
: $plans->proPricePerBranchMinor(),
|
||||||
|
'upgradeBody' => $upgradeBody,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Http\Controllers\Care;
|
namespace App\Http\Controllers\Care;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Controllers\Care\Concerns\RequiresPlanFeature;
|
||||||
use App\Http\Controllers\Care\Concerns\ScopesToAccount;
|
use App\Http\Controllers\Care\Concerns\ScopesToAccount;
|
||||||
use App\Models\Branch;
|
use App\Models\Branch;
|
||||||
use App\Services\Care\AdminOverviewService;
|
use App\Services\Care\AdminOverviewService;
|
||||||
@@ -10,6 +11,7 @@ use App\Services\Care\CareFeatures;
|
|||||||
use App\Services\Care\OrganizationResolver;
|
use App\Services\Care\OrganizationResolver;
|
||||||
use App\Services\Care\PlanService;
|
use App\Services\Care\PlanService;
|
||||||
use App\Services\Care\ReportService;
|
use App\Services\Care\ReportService;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
@@ -17,8 +19,11 @@ use Symfony\Component\HttpFoundation\StreamedResponse;
|
|||||||
|
|
||||||
class ReportController extends Controller
|
class ReportController extends Controller
|
||||||
{
|
{
|
||||||
|
use RequiresPlanFeature;
|
||||||
use ScopesToAccount;
|
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(
|
public function __construct(
|
||||||
protected ReportService $reports,
|
protected ReportService $reports,
|
||||||
protected AdminOverviewService $adminOverview,
|
protected AdminOverviewService $adminOverview,
|
||||||
@@ -54,6 +59,21 @@ class ReportController extends Controller
|
|||||||
public function show(Request $request, string $type): View
|
public function show(Request $request, string $type): View
|
||||||
{
|
{
|
||||||
$this->authorizeReport($request, $type);
|
$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);
|
$organization = $this->organization($request);
|
||||||
$branchScope = app(OrganizationResolver::class)->branchScope($this->member($request));
|
$branchScope = app(OrganizationResolver::class)->branchScope($this->member($request));
|
||||||
$branchId = $request->input('branch_id') ? (int) $request->input('branch_id') : $branchScope;
|
$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);
|
$this->authorizeReport($request, $type);
|
||||||
|
|
||||||
|
if ($type === 'assessments') {
|
||||||
|
if ($deny = $this->denyWithoutFeature($request, 'assessment_analytics', self::ASSESSMENT_ANALYTICS_UPSELL)) {
|
||||||
|
return $deny;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
abort_unless(
|
abort_unless(
|
||||||
app(\App\Services\Care\CarePermissions::class)->can($this->member($request), 'reports.finance.export'),
|
app(\App\Services\Care\CarePermissions::class)->can($this->member($request), 'reports.finance.export'),
|
||||||
403,
|
403,
|
||||||
@@ -155,11 +182,7 @@ class ReportController extends Controller
|
|||||||
app(CareFeatures::class)->enabled($organization, CareFeatures::ASSESSMENTS_ENGINE),
|
app(CareFeatures::class)->enabled($organization, CareFeatures::ASSESSMENTS_ENGINE),
|
||||||
404,
|
404,
|
||||||
);
|
);
|
||||||
abort_unless(
|
// Plan gate: show() renders upgrade UI; export() redirects via denyWithoutFeature.
|
||||||
app(PlanService::class)->hasFeature($organization, 'assessment_analytics'),
|
|
||||||
403,
|
|
||||||
'Assessment analytics require an Enterprise plan.',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,12 @@
|
|||||||
|
|
||||||
<x-settings.card title="Upgrade your plan">
|
<x-settings.card title="Upgrade your plan">
|
||||||
<p class="text-sm text-slate-600">
|
<p class="text-sm text-slate-600">
|
||||||
GHS {{ number_format($proPriceMinor / 100, 0) }} / branch / month for Pro —
|
@if (! empty($upgradeBody))
|
||||||
multi-branch locations, laboratory, pharmacy, encounter billing, and in-app service queues.
|
{{ $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>
|
</p>
|
||||||
<div class="mt-4 flex flex-wrap items-center gap-3">
|
<div class="mt-4 flex flex-wrap items-center gap-3">
|
||||||
<a href="{{ route('care.pro.index') }}" class="btn-primary">View plans</a>
|
<a href="{{ route('care.pro.index') }}" class="btn-primary">View plans</a>
|
||||||
|
|||||||
@@ -111,7 +111,10 @@ class CareAssessmentExtrasTest extends TestCase
|
|||||||
|
|
||||||
$this->actingAs($this->user)
|
$this->actingAs($this->user)
|
||||||
->get(route('care.reports.show', ['type' => 'assessments']))
|
->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
|
public function test_fhir_export_web_and_api(): void
|
||||||
|
|||||||
Reference in New Issue
Block a user