Files
ladill-care/app/Services/Care/OrganizationResolver.php
T
isaaccladandCursor 3ee59a0956
Deploy Ladill Care / deploy (push) Failing after 1m9s
Activate Care workflows across check-in and financial clearance.
Enforce service-queue gates, cashier settlements, and clinical stage progression so patient journeys cannot bypass configured payment or authorization rules.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 21:13:06 +00:00

162 lines
5.2 KiB
PHP

<?php
namespace App\Services\Care;
use App\Models\Branch;
use App\Models\Department;
use App\Models\Member;
use App\Models\Organization;
use App\Models\User;
use App\Services\Care\Workflow\WorkflowTemplateInstaller;
use App\Services\Care\Workflow\WorkflowTemplateRegistry;
use Illuminate\Support\Str;
class OrganizationResolver
{
public function resolveForUser(User $user): ?Organization
{
$ref = $user->ownerRef();
$member = Member::where('user_ref', $ref)->first();
if ($member) {
$organization = Organization::query()->find($member->organization_id);
if ($organization) {
return $organization;
}
}
return Organization::owned($ref)->first();
}
public function forUser(User $user): Organization
{
return $this->resolveForUser($user)
?? throw new \RuntimeException('No organization for user.');
}
public function isOnboarded(User $user): bool
{
$organization = $this->resolveForUser($user);
return $organization !== null
&& (bool) data_get($organization->settings, 'onboarded', false);
}
public function memberFor(User $user, ?Organization $organization = null): ?Member
{
$organization ??= $this->resolveForUser($user);
if (! $organization) {
return null;
}
return Member::where('organization_id', $organization->id)
->where('user_ref', $user->ownerRef())
->first();
}
public function ensureOwnerMember(User $user, Organization $organization): Member
{
return Member::firstOrCreate(
[
'organization_id' => $organization->id,
'user_ref' => $user->ownerRef(),
],
[
'owner_ref' => $user->ownerRef(),
'role' => 'hospital_admin',
],
);
}
/**
* @param array<string, mixed> $data
*/
public function completeOnboarding(User $user, array $data): Organization
{
$ref = $user->ownerRef();
$registry = app(WorkflowTemplateRegistry::class);
// New onboarding leads with Facility Category (modules) + Workflow
// Template (patient flow); legacy callers may still pass facility_type.
$category = $data['facility_category'] ?? $this->categoryFromLegacyType($data['facility_type'] ?? null);
$templateKey = $data['workflow_template']
?? $registry->defaultTemplateForCategory($category);
$modules = $registry->modulesForCategory($category);
$organization = Organization::create([
'owner_ref' => $ref,
'name' => $data['organization_name'],
'slug' => Str::slug($data['organization_name']).'-'.substr($ref, 0, 6),
'timezone' => $data['timezone'] ?? config('app.timezone', 'UTC'),
'settings' => [
'onboarded' => true,
'facility_category' => $category,
'facility_type' => $data['facility_type'] ?? $category,
'modules' => $modules,
'workflow_template' => $templateKey,
'rollout' => [
CareFeatures::WORKFLOW_ENGINE => $templateKey !== 'legacy_clinic',
// Financial gates require the paid billing module and are
// explicitly enabled by an administrator in Settings.
CareFeatures::FINANCIAL_GATES => false,
],
],
]);
$this->ensureOwnerMember($user, $organization);
$branch = Branch::create([
'owner_ref' => $ref,
'organization_id' => $organization->id,
'name' => $data['branch_name'],
'address' => $data['branch_address'] ?? null,
'phone' => $data['branch_phone'] ?? null,
'is_active' => true,
]);
Department::create([
'owner_ref' => $ref,
'branch_id' => $branch->id,
'name' => 'General Outpatient',
'type' => 'outpatient',
'is_active' => true,
]);
if ($registry->hasTemplate($templateKey)) {
app(WorkflowTemplateInstaller::class)->install($organization, $branch, $templateKey, [
'category' => $category,
]);
}
AuditLogger::record($ref, 'organization.created', $organization->id, $ref, Organization::class, $organization->id);
AuditLogger::record($ref, 'branch.created', $organization->id, $ref, Branch::class, $branch->id);
return $organization;
}
protected function categoryFromLegacyType(?string $type): string
{
return match ($type) {
'hospital' => 'hospital',
'diagnostic' => 'diagnostic',
'specialist' => 'specialist',
default => 'clinic',
};
}
/** Branch ID the member may access; null = all branches. */
public function branchScope(?Member $member): ?int
{
if ($member === null) {
return null;
}
if (in_array($member->role, ['super_admin', 'hospital_admin', 'accountant'], true)) {
return null;
}
return $member->branch_id;
}
}