Memoize specialty nav and branch lookups to stop per-request N+1.
Deploy Ladill Care / deploy (push) Successful in 28s
Deploy Ladill Care / deploy (push) Successful in 28s
Sidebar was re-querying practitioners for every specialty module and also calling ensureDefaultModulesProvisioned on each page render, which showed up as multi-second PHP-FPM slow logs on Care. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -21,6 +21,18 @@ use Illuminate\Support\Facades\Log;
|
||||
*/
|
||||
class SpecialtyModuleService
|
||||
{
|
||||
/** @var array<string, \Illuminate\Support\Collection<int, Practitioner>> */
|
||||
protected array $practitionersCache = [];
|
||||
|
||||
/** @var array<string, bool> */
|
||||
protected array $deskSpecialistCache = [];
|
||||
|
||||
/** @var array<string, list<array{key: string, definition: array<string, mixed>, access_level: string}>> */
|
||||
protected array $enabledModulesForMemberCache = [];
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected array $memberAccessLevelCache = [];
|
||||
|
||||
public function __construct(
|
||||
protected PlanService $plans,
|
||||
) {}
|
||||
@@ -168,6 +180,11 @@ class SpecialtyModuleService
|
||||
*/
|
||||
public function enabledModulesForMember(Organization $organization, ?Member $member): array
|
||||
{
|
||||
$cacheKey = $organization->id.'|'.($member?->id ?? 'guest');
|
||||
if (isset($this->enabledModulesForMemberCache[$cacheKey])) {
|
||||
return $this->enabledModulesForMemberCache[$cacheKey];
|
||||
}
|
||||
|
||||
$out = [];
|
||||
foreach ($this->enabledModules($organization) as $item) {
|
||||
$level = $this->memberAccessLevel($organization, $member, $item['key']);
|
||||
@@ -177,7 +194,7 @@ class SpecialtyModuleService
|
||||
$out[] = array_merge($item, ['access_level' => $level]);
|
||||
}
|
||||
|
||||
return $out;
|
||||
return $this->enabledModulesForMemberCache[$cacheKey] = $out;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,32 +240,37 @@ class SpecialtyModuleService
|
||||
*/
|
||||
public function memberAccessLevel(Organization $organization, ?Member $member, string $key): string
|
||||
{
|
||||
$cacheKey = $organization->id.'|'.($member?->id ?? 'guest').'|'.$key;
|
||||
if (isset($this->memberAccessLevelCache[$cacheKey])) {
|
||||
return $this->memberAccessLevelCache[$cacheKey];
|
||||
}
|
||||
|
||||
if (! $this->isEnabled($organization, $key)) {
|
||||
return 'none';
|
||||
return $this->memberAccessLevelCache[$cacheKey] = 'none';
|
||||
}
|
||||
|
||||
$definition = $this->definition($key);
|
||||
if (! $definition || ! $member) {
|
||||
return 'none';
|
||||
return $this->memberAccessLevelCache[$cacheKey] = 'none';
|
||||
}
|
||||
|
||||
if (app(CarePermissions::class)->isAdmin($member)) {
|
||||
return 'manage';
|
||||
return $this->memberAccessLevelCache[$cacheKey] = 'manage';
|
||||
}
|
||||
|
||||
if ($this->memberCanManage($organization, $member, $key)) {
|
||||
return 'manage';
|
||||
return $this->memberAccessLevelCache[$cacheKey] = 'manage';
|
||||
}
|
||||
|
||||
// Use gated helpers so desk specialists do not inherit GP view/refer on other modules.
|
||||
if ($this->memberCanRefer($organization, $member, $key)) {
|
||||
return 'refer';
|
||||
return $this->memberAccessLevelCache[$cacheKey] = 'refer';
|
||||
}
|
||||
if ($this->memberCanView($organization, $member, $key)) {
|
||||
return 'view';
|
||||
return $this->memberAccessLevelCache[$cacheKey] = 'view';
|
||||
}
|
||||
|
||||
return 'none';
|
||||
return $this->memberAccessLevelCache[$cacheKey] = 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -364,17 +386,22 @@ class SpecialtyModuleService
|
||||
*/
|
||||
public function isDeskSpecialist(Organization $organization, Member $member): bool
|
||||
{
|
||||
$cacheKey = $organization->id.'|'.$member->id;
|
||||
if (isset($this->deskSpecialistCache[$cacheKey])) {
|
||||
return $this->deskSpecialistCache[$cacheKey];
|
||||
}
|
||||
|
||||
if ((string) $member->role !== 'doctor') {
|
||||
return false;
|
||||
return $this->deskSpecialistCache[$cacheKey] = false;
|
||||
}
|
||||
|
||||
foreach (array_keys($this->catalog()) as $key) {
|
||||
if ($this->specialistBelongsToModule($organization, $member, $key)) {
|
||||
return true;
|
||||
return $this->deskSpecialistCache[$cacheKey] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return $this->deskSpecialistCache[$cacheKey] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -508,9 +535,14 @@ class SpecialtyModuleService
|
||||
*/
|
||||
protected function practitionersForMember(Organization $organization, Member $member)
|
||||
{
|
||||
$cacheKey = $organization->id.'|'.$member->id;
|
||||
if (isset($this->practitionersCache[$cacheKey])) {
|
||||
return $this->practitionersCache[$cacheKey];
|
||||
}
|
||||
|
||||
$branchId = app(OrganizationResolver::class)->branchScope($member);
|
||||
|
||||
return Practitioner::owned((string) $organization->owner_ref)
|
||||
return $this->practitionersCache[$cacheKey] = Practitioner::owned((string) $organization->owner_ref)
|
||||
->where('organization_id', $organization->id)
|
||||
->where('is_active', true)
|
||||
->where(function ($query) use ($member) {
|
||||
|
||||
Reference in New Issue
Block a user