Fix Care settings 500s and slow saves under Queue/demo load.
Deploy Ladill Care / deploy (push) Successful in 52s
Deploy Ladill Care / deploy (push) Successful in 52s
Defer Queue provision and ticket backfill after the settings response, harden messaging credential creation against lock races, and stop wrapping demo seed in a long-held transaction. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -79,7 +79,7 @@ class SettingsController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, QueueClient $queue, CareQueueProvisioner $provisioner): RedirectResponse
|
||||
public function update(Request $request, QueueClient $queue): RedirectResponse
|
||||
{
|
||||
$this->authorizeAbility($request, 'settings.manage');
|
||||
$organization = $this->organization($request);
|
||||
@@ -136,6 +136,8 @@ class SettingsController extends Controller
|
||||
$wantsQueue = false;
|
||||
}
|
||||
$settings['queue_integration_enabled'] = $wantsQueue;
|
||||
$queueWasEnabled = (bool) data_get($organization->settings, 'queue_integration_enabled');
|
||||
$queueJustEnabled = $wantsQueue && ! $queueWasEnabled;
|
||||
|
||||
$settings[AppointmentPatientNotifier::SETTING_BOOKED_SMS] = $request->boolean('notify_appointment_booked_sms');
|
||||
$settings[AppointmentPatientNotifier::SETTING_BOOKED_EMAIL] = $request->boolean('notify_appointment_booked_email');
|
||||
@@ -184,27 +186,49 @@ class SettingsController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
// Queue department sync + waiting-ticket backfill can take minutes over HTTP.
|
||||
// Never block the settings response on it — run after the redirect goes out.
|
||||
if ($wantsQueue && $queue->configured()) {
|
||||
try {
|
||||
$organization = $organization->fresh();
|
||||
$provisioner->provisionOrganization($organization);
|
||||
// Backfill tickets for patients already waiting when Queue was just linked.
|
||||
$bridge = app(CareQueueBridge::class);
|
||||
$branches = Branch::owned($owner)
|
||||
->where('organization_id', $organization->id)
|
||||
->where('is_active', true)
|
||||
->pluck('id');
|
||||
foreach ($branches as $branchId) {
|
||||
foreach (array_keys(CareQueueContexts::departments()) as $context) {
|
||||
if ($context === CareQueueContexts::RECEPTION) {
|
||||
continue;
|
||||
}
|
||||
$bridge->syncMissingTickets($organization, $context, (int) $branchId, 50);
|
||||
}
|
||||
$organizationId = (int) $organization->id;
|
||||
$backfillTickets = $queueJustEnabled;
|
||||
dispatch(function () use ($organizationId, $owner, $backfillTickets) {
|
||||
$organization = Organization::query()->find($organizationId);
|
||||
if (! $organization) {
|
||||
return;
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// Best-effort; role pages will lazy-ensure on next use.
|
||||
}
|
||||
|
||||
try {
|
||||
app(CareQueueProvisioner::class)->provisionOrganization($organization);
|
||||
} catch (\Throwable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $backfillTickets) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$bridge = app(CareQueueBridge::class);
|
||||
$branches = Branch::owned($owner)
|
||||
->where('organization_id', $organization->id)
|
||||
->where('is_active', true)
|
||||
->pluck('id');
|
||||
foreach ($branches as $branchId) {
|
||||
foreach (array_keys(CareQueueContexts::departments()) as $context) {
|
||||
if ($context === CareQueueContexts::RECEPTION) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
$bridge->syncMissingTickets($organization, $context, (int) $branchId, 25);
|
||||
} catch (\Throwable) {
|
||||
// Continue other contexts/branches.
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// Best-effort; role pages will lazy-ensure on next use.
|
||||
}
|
||||
})->afterResponse();
|
||||
}
|
||||
|
||||
AuditLogger::record($owner, 'organization.updated', $organization->id, $owner, Organization::class, $organization->id);
|
||||
|
||||
@@ -97,75 +97,75 @@ class DemoTenantSeeder
|
||||
$this->ensureOnboardedShell($user, $plan);
|
||||
|
||||
if ($reset) {
|
||||
DB::transaction(fn () => $this->resetTenant($ownerRef));
|
||||
$this->resetTenant($ownerRef);
|
||||
$this->ensureOnboardedShell($user, $plan);
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($user, $ownerRef, $plan, $volumes) {
|
||||
$organization = $this->upsertOrganization($user, $ownerRef, $plan, $volumes['branches']);
|
||||
$this->upsertOwnerMember($organization, $ownerRef);
|
||||
// No outer transaction — Enterprise volumes can hold row locks for minutes
|
||||
// and 500 concurrent settings/login requests (messaging credentials, org updates).
|
||||
$organization = $this->upsertOrganization($user, $ownerRef, $plan, $volumes['branches']);
|
||||
$this->upsertOwnerMember($organization, $ownerRef);
|
||||
|
||||
$branches = $this->seedBranches($organization, $ownerRef, $volumes);
|
||||
$this->seedStaffMembers($organization, $ownerRef, $plan, $branches);
|
||||
$departments = $this->seedDepartments($branches, $ownerRef, $volumes);
|
||||
if (in_array($plan, ['pro', 'enterprise'], true)) {
|
||||
$this->seedSpecialtyModules($organization, $ownerRef);
|
||||
$departments = $this->departmentsByBranch($branches, $ownerRef);
|
||||
}
|
||||
$practitioners = $this->seedPractitioners($organization, $branches, $departments, $ownerRef, $volumes);
|
||||
$patients = $this->seedPatients($organization, $branches, $ownerRef, $volumes);
|
||||
$appointmentCount = $this->seedAppointmentsAndClinical(
|
||||
$branches = $this->seedBranches($organization, $ownerRef, $volumes);
|
||||
$this->seedStaffMembers($organization, $ownerRef, $plan, $branches);
|
||||
$departments = $this->seedDepartments($branches, $ownerRef, $volumes);
|
||||
if (in_array($plan, ['pro', 'enterprise'], true)) {
|
||||
$this->seedSpecialtyModules($organization, $ownerRef);
|
||||
$departments = $this->departmentsByBranch($branches, $ownerRef);
|
||||
}
|
||||
$practitioners = $this->seedPractitioners($organization, $branches, $departments, $ownerRef, $volumes);
|
||||
$patients = $this->seedPatients($organization, $branches, $ownerRef, $volumes);
|
||||
$appointmentCount = $this->seedAppointmentsAndClinical(
|
||||
$organization,
|
||||
$branches,
|
||||
$departments,
|
||||
$practitioners,
|
||||
$patients,
|
||||
$ownerRef,
|
||||
$volumes,
|
||||
);
|
||||
if (in_array($plan, ['pro', 'enterprise'], true)) {
|
||||
$appointmentCount += $this->seedSpecialtyDemoData(
|
||||
$organization->fresh(),
|
||||
$branches,
|
||||
$practitioners,
|
||||
$patients,
|
||||
$ownerRef,
|
||||
);
|
||||
}
|
||||
|
||||
if ($volumes['paid_modules']) {
|
||||
$this->seedPaidModules(
|
||||
$organization,
|
||||
$branches,
|
||||
$departments,
|
||||
$practitioners,
|
||||
$patients,
|
||||
$ownerRef,
|
||||
$volumes,
|
||||
);
|
||||
if (in_array($plan, ['pro', 'enterprise'], true)) {
|
||||
$appointmentCount += $this->seedSpecialtyDemoData(
|
||||
$organization->fresh(),
|
||||
$branches,
|
||||
$practitioners,
|
||||
$patients,
|
||||
$ownerRef,
|
||||
);
|
||||
}
|
||||
$this->seedDevices($organization, $branches, $ownerRef);
|
||||
}
|
||||
|
||||
if ($volumes['paid_modules']) {
|
||||
$this->seedPaidModules(
|
||||
$organization,
|
||||
$branches,
|
||||
$practitioners,
|
||||
$patients,
|
||||
$ownerRef,
|
||||
$volumes,
|
||||
);
|
||||
$this->seedDevices($organization, $branches, $ownerRef);
|
||||
}
|
||||
if ($volumes['assessments'] > 0) {
|
||||
$this->seedAssessments(
|
||||
$organization,
|
||||
$branches,
|
||||
$practitioners,
|
||||
$patients,
|
||||
$ownerRef,
|
||||
$volumes['assessments'],
|
||||
);
|
||||
}
|
||||
|
||||
if ($volumes['assessments'] > 0) {
|
||||
$this->seedAssessments(
|
||||
$organization,
|
||||
$branches,
|
||||
$practitioners,
|
||||
$patients,
|
||||
$ownerRef,
|
||||
$volumes['assessments'],
|
||||
);
|
||||
}
|
||||
$this->syncQueueTicketsForDemo($organization->fresh(), $branches);
|
||||
|
||||
$this->syncQueueTicketsForDemo($organization->fresh(), $branches);
|
||||
|
||||
return [
|
||||
'organization' => $organization->fresh(),
|
||||
'branches' => count($branches),
|
||||
'patients' => count($patients),
|
||||
'appointments' => $appointmentCount,
|
||||
'plan' => $plan,
|
||||
];
|
||||
});
|
||||
return [
|
||||
'organization' => $organization->fresh(),
|
||||
'branches' => count($branches),
|
||||
'patients' => count($patients),
|
||||
'appointments' => $appointmentCount,
|
||||
'plan' => $plan,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,10 +14,30 @@ class MessagingCredentialsService
|
||||
|
||||
public function forOrganization(Organization $organization): MessagingCredential
|
||||
{
|
||||
return MessagingCredential::query()->firstOrCreate(
|
||||
['organization_id' => $organization->id],
|
||||
['owner_ref' => $organization->owner_ref],
|
||||
);
|
||||
$existing = MessagingCredential::query()
|
||||
->where('organization_id', $organization->id)
|
||||
->first();
|
||||
if ($existing) {
|
||||
return $existing;
|
||||
}
|
||||
|
||||
try {
|
||||
return MessagingCredential::query()->create([
|
||||
'organization_id' => $organization->id,
|
||||
'owner_ref' => $organization->owner_ref,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
// Concurrent create (or a long demo-seed transaction holding a lock)
|
||||
// — prefer returning the row another request just wrote over 500ing.
|
||||
$existing = MessagingCredential::query()
|
||||
->where('organization_id', $organization->id)
|
||||
->first();
|
||||
if ($existing) {
|
||||
return $existing;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user