Fix patient show 500 when assessment catalog reseed hits live answers.
Deploy Ladill Care / deploy (push) Successful in 1m8s
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>
This commit is contained in:
@@ -8,7 +8,9 @@ use App\Models\Organization;
|
|||||||
use Database\Seeders\AssessmentTemplateSeeder;
|
use Database\Seeders\AssessmentTemplateSeeder;
|
||||||
use Database\Seeders\ClinicalPathwaySeeder;
|
use Database\Seeders\ClinicalPathwaySeeder;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Product rollout flags stored under organization settings.rollout.*
|
* Product rollout flags stored under organization settings.rollout.*
|
||||||
@@ -66,8 +68,21 @@ class CareFeatures
|
|||||||
/**
|
/**
|
||||||
* Ensure platform assessment/pathway catalog exists (idempotent seed).
|
* Ensure platform assessment/pathway catalog exists (idempotent seed).
|
||||||
* Call when the engine is enabled so consult/patient UI has templates.
|
* 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
|
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')) {
|
if (! Schema::hasTable('care_assessment_templates')) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -94,18 +94,16 @@ class AssessmentTemplateSeeder extends Seeder
|
|||||||
->update(['is_current' => false]);
|
->update(['is_current' => false]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace questions for this template version (stable codes within version).
|
// Upsert questions by code so live answers keep their FK targets.
|
||||||
AssessmentQuestion::query()->where('template_id', $template->id)->delete();
|
$keptCodes = [];
|
||||||
|
|
||||||
$sort = 0;
|
$sort = 0;
|
||||||
foreach ($data['questions'] as $q) {
|
foreach ($data['questions'] as $q) {
|
||||||
if (empty($q['code']) || empty($q['label']) || empty($q['answer_type'])) {
|
if (empty($q['code']) || empty($q['label']) || empty($q['answer_type'])) {
|
||||||
throw new RuntimeException("Invalid question in pack {$path}");
|
throw new RuntimeException("Invalid question in pack {$path}");
|
||||||
}
|
}
|
||||||
$sort++;
|
$sort++;
|
||||||
AssessmentQuestion::create([
|
$keptCodes[] = $q['code'];
|
||||||
'template_id' => $template->id,
|
$attributes = [
|
||||||
'code' => $q['code'],
|
|
||||||
'section' => $q['section'] ?? null,
|
'section' => $q['section'] ?? null,
|
||||||
'label' => $q['label'],
|
'label' => $q['label'],
|
||||||
'help_text' => $q['help_text'] ?? null,
|
'help_text' => $q['help_text'] ?? null,
|
||||||
@@ -115,8 +113,29 @@ class AssessmentTemplateSeeder extends Seeder
|
|||||||
'sort_order' => (int) ($q['sort_order'] ?? $sort),
|
'sort_order' => (int) ($q['sort_order'] ?? $sort),
|
||||||
'score_key' => $q['score_key'] ?? null,
|
'score_key' => $q['score_key'] ?? null,
|
||||||
'validation' => $q['validation'] ?? null,
|
'validation' => $q['validation'] ?? null,
|
||||||
]);
|
];
|
||||||
|
|
||||||
|
$question = AssessmentQuestion::query()
|
||||||
|
->where('template_id', $template->id)
|
||||||
|
->where('code', $q['code'])
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($question) {
|
||||||
|
$question->fill($attributes)->save();
|
||||||
|
} else {
|
||||||
|
AssessmentQuestion::create(array_merge($attributes, [
|
||||||
|
'template_id' => $template->id,
|
||||||
|
'code' => $q['code'],
|
||||||
|
]));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop obsolete pack questions only when nothing references them.
|
||||||
|
AssessmentQuestion::query()
|
||||||
|
->where('template_id', $template->id)
|
||||||
|
->whereNotIn('code', $keptCodes)
|
||||||
|
->whereDoesntHave('answers')
|
||||||
|
->delete();
|
||||||
|
|
||||||
$this->command?->info(sprintf(
|
$this->command?->info(sprintf(
|
||||||
'Seeded assessment pack %s v%d (%d questions)',
|
'Seeded assessment pack %s v%d (%d questions)',
|
||||||
|
|||||||
@@ -91,4 +91,75 @@ class CareContentPacksTest extends TestCase
|
|||||||
$this->assertTrue($copd->questions()->where('code', 'mmrc')->exists());
|
$this->assertTrue($copd->questions()->where('code', 'mmrc')->exists());
|
||||||
$this->assertTrue($hf->questions()->where('code', 'nyha')->exists());
|
$this->assertTrue($hf->questions()->where('code', 'nyha')->exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_reseed_preserves_questions_that_have_answers(): void
|
||||||
|
{
|
||||||
|
$this->seed(AssessmentTemplateSeeder::class);
|
||||||
|
|
||||||
|
$user = \App\Models\User::create([
|
||||||
|
'public_id' => 'pack-reseed-user',
|
||||||
|
'name' => 'Pack',
|
||||||
|
'email' => 'pack-reseed@example.com',
|
||||||
|
]);
|
||||||
|
$organization = \App\Models\Organization::create([
|
||||||
|
'owner_ref' => $user->public_id,
|
||||||
|
'name' => 'Pack Clinic',
|
||||||
|
'slug' => 'pack-clinic',
|
||||||
|
'settings' => ['onboarded' => true],
|
||||||
|
]);
|
||||||
|
$branch = \App\Models\Branch::create([
|
||||||
|
'owner_ref' => $user->public_id,
|
||||||
|
'organization_id' => $organization->id,
|
||||||
|
'name' => 'Main',
|
||||||
|
'is_active' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$template = AssessmentTemplate::currentSystemByCode('universal_intake');
|
||||||
|
$this->assertNotNull($template);
|
||||||
|
$question = $template->questions()->orderBy('id')->first();
|
||||||
|
$this->assertNotNull($question);
|
||||||
|
$questionId = $question->id;
|
||||||
|
|
||||||
|
$patient = \App\Models\Patient::create([
|
||||||
|
'owner_ref' => $user->public_id,
|
||||||
|
'organization_id' => $organization->id,
|
||||||
|
'branch_id' => $branch->id,
|
||||||
|
'patient_number' => 'P-PACK-1',
|
||||||
|
'first_name' => 'Pack',
|
||||||
|
'last_name' => 'Patient',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$assessment = \App\Models\Assessment::create([
|
||||||
|
'owner_ref' => $user->public_id,
|
||||||
|
'organization_id' => $organization->id,
|
||||||
|
'branch_id' => $branch->id,
|
||||||
|
'patient_id' => $patient->id,
|
||||||
|
'template_id' => $template->id,
|
||||||
|
'status' => \App\Models\Assessment::STATUS_DRAFT,
|
||||||
|
'assessed_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
\App\Models\AssessmentAnswer::create([
|
||||||
|
'owner_ref' => $user->public_id,
|
||||||
|
'assessment_id' => $assessment->id,
|
||||||
|
'question_id' => $questionId,
|
||||||
|
'value_text' => 'kept',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Delete nursing packs so ensureAssessmentCatalog re-runs the seeder.
|
||||||
|
AssessmentTemplate::query()
|
||||||
|
->whereNull('organization_id')
|
||||||
|
->whereIn('code', ['news2', 'braden', 'morse', 'pain_nrs'])
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
app(\App\Services\Care\CareFeatures::class)->ensureAssessmentCatalog();
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('care_assessment_questions', ['id' => $questionId]);
|
||||||
|
$this->assertDatabaseHas('care_assessment_answers', [
|
||||||
|
'assessment_id' => $assessment->id,
|
||||||
|
'question_id' => $questionId,
|
||||||
|
'value_text' => 'kept',
|
||||||
|
]);
|
||||||
|
$this->assertNotNull(AssessmentTemplate::currentSystemByCode('news2'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user