Deploy Ladill Care / deploy (push) Failing after 1m9s
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>
162 lines
5.2 KiB
PHP
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;
|
|
}
|
|
}
|