Deploy Ladill Care / deploy (push) Successful in 1m8s
ensureAssessmentCatalog was re-running AssessmentTemplateSeeder whenever nursing packs were missing; the seeder hard-deleted questions and blew up on FK care_assessment_answers. Upsert questions by code, only drop unused orphans, and never let catalog ensure throw on web requests. Co-authored-by: Cursor <cursoragent@cursor.com>
116 lines
3.6 KiB
PHP
116 lines
3.6 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Care;
|
|
|
|
use App\Models\AssessmentTemplate;
|
|
use App\Models\ClinicalPathway;
|
|
use App\Models\Organization;
|
|
use Database\Seeders\AssessmentTemplateSeeder;
|
|
use Database\Seeders\ClinicalPathwaySeeder;
|
|
use Illuminate\Support\Facades\Artisan;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Product rollout flags stored under organization settings.rollout.*
|
|
* Distinct from PlanService entitlements (lab/pharmacy/billing plan features).
|
|
*/
|
|
class CareFeatures
|
|
{
|
|
public const ASSESSMENTS_ENGINE = 'assessments_engine';
|
|
|
|
public const PATHWAY_SUGGESTIONS = 'pathway_suggestions';
|
|
|
|
public const ASSESSMENT_REQUIRED_ON_COMPLETE = 'assessment_required_on_complete';
|
|
|
|
/** Workflow-centric patient journey + financial gates (opt-in). */
|
|
public const WORKFLOW_ENGINE = 'workflow_engine';
|
|
|
|
/** Enforce financial gates (block gated stages until obligations clear). */
|
|
public const FINANCIAL_GATES = 'financial_gates';
|
|
|
|
/**
|
|
* Flags that default ON when unset (opt-out). Others default OFF (opt-in).
|
|
*
|
|
* @var list<string>
|
|
*/
|
|
protected array $defaultOn = [
|
|
self::ASSESSMENTS_ENGINE,
|
|
];
|
|
|
|
public function enabled(Organization $organization, string $flag): bool
|
|
{
|
|
$settings = $organization->settings ?? [];
|
|
$rollout = is_array($settings['rollout'] ?? null) ? $settings['rollout'] : [];
|
|
|
|
if (array_key_exists($flag, $rollout)) {
|
|
return (bool) $rollout[$flag];
|
|
}
|
|
|
|
return in_array($flag, $this->defaultOn, true);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $flags
|
|
*/
|
|
public function setFlags(Organization $organization, array $flags): Organization
|
|
{
|
|
$settings = $organization->settings ?? [];
|
|
$rollout = is_array($settings['rollout'] ?? null) ? $settings['rollout'] : [];
|
|
$settings['rollout'] = array_merge($rollout, $flags);
|
|
$organization->settings = $settings;
|
|
$organization->save();
|
|
|
|
return $organization->fresh();
|
|
}
|
|
|
|
/**
|
|
* Ensure platform assessment/pathway catalog exists (idempotent seed).
|
|
* Call when the engine is enabled so consult/patient UI has templates.
|
|
* Never throws — web requests must not 500 if catalog seed races with live answers.
|
|
*/
|
|
public function ensureAssessmentCatalog(): void
|
|
{
|
|
try {
|
|
$this->seedAssessmentCatalogIfNeeded();
|
|
} catch (Throwable $e) {
|
|
Log::warning('ensureAssessmentCatalog failed', [
|
|
'message' => $e->getMessage(),
|
|
'exception' => $e::class,
|
|
]);
|
|
}
|
|
}
|
|
|
|
protected function seedAssessmentCatalogIfNeeded(): void
|
|
{
|
|
if (! Schema::hasTable('care_assessment_templates')) {
|
|
return;
|
|
}
|
|
|
|
$nursingCodes = ['news2', 'braden', 'morse', 'pain_nrs'];
|
|
$haveNursing = AssessmentTemplate::query()
|
|
->whereNull('organization_id')
|
|
->whereIn('code', $nursingCodes)
|
|
->where('is_current', true)
|
|
->count();
|
|
|
|
$empty = ! AssessmentTemplate::query()->whereNull('organization_id')->exists();
|
|
|
|
if ($empty || $haveNursing < count($nursingCodes)) {
|
|
Artisan::call('db:seed', [
|
|
'--class' => AssessmentTemplateSeeder::class,
|
|
'--force' => true,
|
|
]);
|
|
}
|
|
|
|
if (Schema::hasTable('care_clinical_pathways')
|
|
&& ! ClinicalPathway::query()->whereNull('organization_id')->exists()) {
|
|
Artisan::call('db:seed', [
|
|
'--class' => ClinicalPathwaySeeder::class,
|
|
'--force' => true,
|
|
]);
|
|
}
|
|
}
|
|
}
|