Require package reinstall after industry change and wipe old queues.
Deploy Ladill Queue / deploy (push) Successful in 1m5s
Deploy Ladill Queue / deploy (push) Successful in 1m5s
Changing industry now flags a pending reinstall with an app banner; reinstall clears queues, tickets, and service points before applying the new template. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -35,6 +35,8 @@ class SettingsController extends Controller
|
|||||||
?? data_get($organization->settings, 'industry')
|
?? data_get($organization->settings, 'industry')
|
||||||
?? 'custom';
|
?? 'custom';
|
||||||
$activePackage = $packages->get($packageKey);
|
$activePackage = $packages->get($packageKey);
|
||||||
|
$selectedIndustry = data_get($organization->settings, 'industry', $packageKey);
|
||||||
|
$selectedPackage = $packages->get($selectedIndustry) ?? $activePackage;
|
||||||
|
|
||||||
return view('qms.settings.edit', [
|
return view('qms.settings.edit', [
|
||||||
'organization' => $organization,
|
'organization' => $organization,
|
||||||
@@ -43,7 +45,9 @@ class SettingsController extends Controller
|
|||||||
'appointmentModes' => config('qms.appointment_modes'),
|
'appointmentModes' => config('qms.appointment_modes'),
|
||||||
'industries' => $packages->labels(),
|
'industries' => $packages->labels(),
|
||||||
'activePackage' => $activePackage,
|
'activePackage' => $activePackage,
|
||||||
|
'selectedPackage' => $selectedPackage,
|
||||||
'packageMeta' => data_get($organization->settings, 'industry_package'),
|
'packageMeta' => data_get($organization->settings, 'industry_package'),
|
||||||
|
'needsPackageReinstall' => IndustryPackageInstaller::needsReinstall($organization),
|
||||||
'hasPaidPlan' => $plans->hasPaidPlan($organization),
|
'hasPaidPlan' => $plans->hasPaidPlan($organization),
|
||||||
'isPro' => $plans->isPro($organization),
|
'isPro' => $plans->isPro($organization),
|
||||||
'isEnterprise' => $plans->isEnterprise($organization),
|
'isEnterprise' => $plans->isEnterprise($organization),
|
||||||
@@ -82,6 +86,7 @@ class SettingsController extends Controller
|
|||||||
|
|
||||||
$settings['appointment_mode'] = $validated['appointment_mode'];
|
$settings['appointment_mode'] = $validated['appointment_mode'];
|
||||||
$previousIndustry = $settings['industry'] ?? null;
|
$previousIndustry = $settings['industry'] ?? null;
|
||||||
|
$installedPackage = data_get($settings, 'industry_package.key');
|
||||||
if ($industry !== null) {
|
if ($industry !== null) {
|
||||||
$settings['industry'] = $industry;
|
$settings['industry'] = $industry;
|
||||||
}
|
}
|
||||||
@@ -95,6 +100,17 @@ class SettingsController extends Controller
|
|||||||
}
|
}
|
||||||
$settings['integrations'] = $integrations;
|
$settings['integrations'] = $integrations;
|
||||||
|
|
||||||
|
$industryChanged = $industry !== null
|
||||||
|
&& $industry !== $previousIndustry;
|
||||||
|
$outOfSyncWithPackage = $industry !== null
|
||||||
|
&& is_string($installedPackage)
|
||||||
|
&& $installedPackage !== ''
|
||||||
|
&& $industry !== $installedPackage;
|
||||||
|
|
||||||
|
if ($industryChanged || $outOfSyncWithPackage) {
|
||||||
|
$settings['industry_package_needs_reinstall'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
$logoPath = $organization->logo_path;
|
$logoPath = $organization->logo_path;
|
||||||
|
|
||||||
if ($request->boolean('remove_logo')) {
|
if ($request->boolean('remove_logo')) {
|
||||||
@@ -115,28 +131,21 @@ class SettingsController extends Controller
|
|||||||
|
|
||||||
AuditLogger::record($owner, 'organization.updated', $organization->id, $owner, \App\Models\Organization::class, $organization->id);
|
AuditLogger::record($owner, 'organization.updated', $organization->id, $owner, \App\Models\Organization::class, $organization->id);
|
||||||
|
|
||||||
$industryChanged = ($settings['industry'] ?? null) && ($settings['industry'] ?? null) !== $previousIndustry;
|
|
||||||
$packageNote = '';
|
$packageNote = '';
|
||||||
if ($industryChanged) {
|
if ($industryChanged || $outOfSyncWithPackage) {
|
||||||
$branch = Branch::query()
|
$label = $registry->get($industry)?->label() ?? $industry;
|
||||||
->where('organization_id', $organization->id)
|
$packageNote = " Industry changed to {$label} — reinstall the industry package to replace queues and service points.";
|
||||||
->where('is_active', true)
|
|
||||||
->orderBy('id')
|
|
||||||
->first();
|
|
||||||
if ($branch) {
|
|
||||||
try {
|
|
||||||
app(IndustryPackageInstaller::class)->install($organization->fresh(), $branch, $settings['industry'], [
|
|
||||||
'actor_ref' => $owner,
|
|
||||||
]);
|
|
||||||
$packageNote = ' Industry package re-provisioned.';
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
report($e);
|
|
||||||
$packageNote = ' Settings saved, but industry package re-provision failed — try Reinstall package.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return back()->with('success', 'Settings saved.'.$packageNote);
|
$redirect = back()->with('success', 'Settings saved.'.$packageNote);
|
||||||
|
if ($industryChanged || $outOfSyncWithPackage) {
|
||||||
|
$redirect->with(
|
||||||
|
'warning',
|
||||||
|
'Reinstall the industry package to apply the new template. This will clear existing queues, tickets, and service points.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $redirect;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function reinstallPackage(Request $request, IndustryPackageInstaller $installer): RedirectResponse
|
public function reinstallPackage(Request $request, IndustryPackageInstaller $installer): RedirectResponse
|
||||||
@@ -159,9 +168,13 @@ class SettingsController extends Controller
|
|||||||
$result = $installer->install($organization, $branch, $key, [
|
$result = $installer->install($organization, $branch, $key, [
|
||||||
'actor_ref' => $owner,
|
'actor_ref' => $owner,
|
||||||
'include_optional' => true,
|
'include_optional' => true,
|
||||||
|
'replace' => true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$message = 'Industry package synced.';
|
$message = ($result['replaced'] ?? false)
|
||||||
|
? 'Industry package reinstalled. Previous queues, tickets, and service points were cleared.'
|
||||||
|
: 'Industry package synced.';
|
||||||
|
|
||||||
if (($result['skipped_reason'] ?? null) === 'plan_queue_limit') {
|
if (($result['skipped_reason'] ?? null) === 'plan_queue_limit') {
|
||||||
$message .= ' Some stages were skipped because of your plan queue limit — upgrade to Pro for the full template.';
|
$message .= ' Some stages were skipped because of your plan queue limit — upgrade to Pro for the full template.';
|
||||||
} elseif (($result['skipped_reason'] ?? null) === 'stages_managed_by_integration') {
|
} elseif (($result['skipped_reason'] ?? null) === 'stages_managed_by_integration') {
|
||||||
|
|||||||
@@ -4,9 +4,19 @@ namespace App\Services\Qms\Industry;
|
|||||||
|
|
||||||
use App\Models\Branch;
|
use App\Models\Branch;
|
||||||
use App\Models\Counter;
|
use App\Models\Counter;
|
||||||
|
use App\Models\CustomerFeedback;
|
||||||
use App\Models\Department;
|
use App\Models\Department;
|
||||||
|
use App\Models\Device;
|
||||||
|
use App\Models\DisplayScreen;
|
||||||
use App\Models\Organization;
|
use App\Models\Organization;
|
||||||
|
use App\Models\QueueAppointment;
|
||||||
|
use App\Models\QueueRule;
|
||||||
use App\Models\ServiceQueue;
|
use App\Models\ServiceQueue;
|
||||||
|
use App\Models\ServiceSession;
|
||||||
|
use App\Models\Ticket;
|
||||||
|
use App\Models\TicketEvent;
|
||||||
|
use App\Models\TicketTransfer;
|
||||||
|
use App\Models\VoiceAnnouncement;
|
||||||
use App\Models\Workflow;
|
use App\Models\Workflow;
|
||||||
use App\Models\WorkflowStep;
|
use App\Models\WorkflowStep;
|
||||||
use App\Services\Qms\AuditLogger;
|
use App\Services\Qms\AuditLogger;
|
||||||
@@ -27,7 +37,7 @@ class IndustryPackageInstaller
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array{skip_stages?: bool, include_optional?: bool, actor_ref?: string|null} $options
|
* @param array{skip_stages?: bool, include_optional?: bool, actor_ref?: string|null, replace?: bool} $options
|
||||||
* @return array{
|
* @return array{
|
||||||
* package: string,
|
* package: string,
|
||||||
* departments: int,
|
* departments: int,
|
||||||
@@ -35,7 +45,8 @@ class IndustryPackageInstaller
|
|||||||
* service_points: int,
|
* service_points: int,
|
||||||
* workflow_id: int|null,
|
* workflow_id: int|null,
|
||||||
* skipped_stages: list<string>,
|
* skipped_stages: list<string>,
|
||||||
* skipped_reason: string|null
|
* skipped_reason: string|null,
|
||||||
|
* replaced: bool
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
public function install(Organization $organization, Branch $branch, ?string $packageKey = null, array $options = []): array
|
public function install(Organization $organization, Branch $branch, ?string $packageKey = null, array $options = []): array
|
||||||
@@ -49,6 +60,7 @@ class IndustryPackageInstaller
|
|||||||
|
|
||||||
$skipStages = (bool) ($options['skip_stages'] ?? false);
|
$skipStages = (bool) ($options['skip_stages'] ?? false);
|
||||||
$includeOptional = (bool) ($options['include_optional'] ?? true);
|
$includeOptional = (bool) ($options['include_optional'] ?? true);
|
||||||
|
$replace = (bool) ($options['replace'] ?? false);
|
||||||
$actor = $options['actor_ref'] ?? $organization->owner_ref;
|
$actor = $options['actor_ref'] ?? $organization->owner_ref;
|
||||||
|
|
||||||
// Care (and similar) own clinical stage provisioning — still apply terminology.
|
// Care (and similar) own clinical stage provisioning — still apply terminology.
|
||||||
@@ -64,9 +76,18 @@ class IndustryPackageInstaller
|
|||||||
'workflow_id' => null,
|
'workflow_id' => null,
|
||||||
'skipped_stages' => [],
|
'skipped_stages' => [],
|
||||||
'skipped_reason' => $skipStages ? 'stages_managed_by_integration' : null,
|
'skipped_reason' => $skipStages ? 'stages_managed_by_integration' : null,
|
||||||
|
'replaced' => false,
|
||||||
];
|
];
|
||||||
|
|
||||||
return DB::transaction(function () use ($organization, $branch, $package, $key, $skipStages, $includeOptional, $actor, &$result) {
|
return DB::transaction(function () use ($organization, $branch, $package, $key, $skipStages, $includeOptional, $replace, $actor, &$result) {
|
||||||
|
// Replacing a package wipes queues/tickets/points so the new template is clean.
|
||||||
|
// Care-managed healthcare keeps clinical queues (skip_stages).
|
||||||
|
if ($replace && ! $skipStages) {
|
||||||
|
$this->clearOperationalData($organization);
|
||||||
|
$result['replaced'] = true;
|
||||||
|
$organization->refresh();
|
||||||
|
}
|
||||||
|
|
||||||
$this->applyPackageSettings($organization, $package);
|
$this->applyPackageSettings($organization, $package);
|
||||||
|
|
||||||
$departmentsByCode = [];
|
$departmentsByCode = [];
|
||||||
@@ -122,6 +143,7 @@ class IndustryPackageInstaller
|
|||||||
|
|
||||||
$settings = $organization->settings ?? [];
|
$settings = $organization->settings ?? [];
|
||||||
$settings['industry'] = $key;
|
$settings['industry'] = $key;
|
||||||
|
unset($settings['industry_package_needs_reinstall']);
|
||||||
$settings['industry_package'] = [
|
$settings['industry_package'] = [
|
||||||
'key' => $key,
|
'key' => $key,
|
||||||
'version' => (int) config('industry_packages.version', 1),
|
'version' => (int) config('industry_packages.version', 1),
|
||||||
@@ -152,6 +174,78 @@ class IndustryPackageInstaller
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the selected industry differs from the installed package template.
|
||||||
|
*/
|
||||||
|
public static function needsReinstall(Organization $organization): bool
|
||||||
|
{
|
||||||
|
if ((bool) data_get($organization->settings, 'industry_package_needs_reinstall')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$selected = data_get($organization->settings, 'industry');
|
||||||
|
$installed = data_get($organization->settings, 'industry_package.key');
|
||||||
|
|
||||||
|
return is_string($selected)
|
||||||
|
&& $selected !== ''
|
||||||
|
&& is_string($installed)
|
||||||
|
&& $installed !== ''
|
||||||
|
&& $selected !== $installed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wipe queues, service points, tickets, workflows, and related operational data.
|
||||||
|
* Keeps organization, branches, and members.
|
||||||
|
*/
|
||||||
|
public function clearOperationalData(Organization $organization): void
|
||||||
|
{
|
||||||
|
$orgId = (int) $organization->id;
|
||||||
|
$ownerRef = (string) $organization->owner_ref;
|
||||||
|
|
||||||
|
$ticketIds = Ticket::withTrashed()->where('organization_id', $orgId)->pluck('id');
|
||||||
|
$queueIds = ServiceQueue::withTrashed()->where('organization_id', $orgId)->pluck('id');
|
||||||
|
$counterIds = Counter::withTrashed()->where('organization_id', $orgId)->pluck('id');
|
||||||
|
$workflowIds = Workflow::withTrashed()->where('organization_id', $orgId)->pluck('id');
|
||||||
|
|
||||||
|
CustomerFeedback::query()->where('organization_id', $orgId)->delete();
|
||||||
|
VoiceAnnouncement::query()->where('organization_id', $orgId)->delete();
|
||||||
|
ServiceSession::query()->where('owner_ref', $ownerRef)->delete();
|
||||||
|
TicketTransfer::query()->where('owner_ref', $ownerRef)->delete();
|
||||||
|
TicketEvent::query()->where('owner_ref', $ownerRef)->delete();
|
||||||
|
QueueAppointment::withTrashed()->where('organization_id', $orgId)->forceDelete();
|
||||||
|
|
||||||
|
if ($ticketIds->isNotEmpty()) {
|
||||||
|
Ticket::withTrashed()->whereIn('id', $ticketIds)->forceDelete();
|
||||||
|
}
|
||||||
|
|
||||||
|
QueueRule::query()->where('owner_ref', $ownerRef)->delete();
|
||||||
|
|
||||||
|
if ($workflowIds->isNotEmpty()) {
|
||||||
|
WorkflowStep::query()->whereIn('workflow_id', $workflowIds)->delete();
|
||||||
|
Workflow::withTrashed()->whereIn('id', $workflowIds)->forceDelete();
|
||||||
|
}
|
||||||
|
|
||||||
|
DisplayScreen::withTrashed()->where('organization_id', $orgId)->forceDelete();
|
||||||
|
Device::withTrashed()->where('organization_id', $orgId)->forceDelete();
|
||||||
|
|
||||||
|
DB::table('queue_staff_sessions')->where('owner_ref', $ownerRef)->delete();
|
||||||
|
DB::table('queue_counter_assignments')->where('owner_ref', $ownerRef)->delete();
|
||||||
|
|
||||||
|
if ($counterIds->isNotEmpty()) {
|
||||||
|
DB::table('queue_counter_service_queue')->whereIn('counter_id', $counterIds)->delete();
|
||||||
|
Counter::withTrashed()->whereIn('id', $counterIds)->forceDelete();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($queueIds->isNotEmpty()) {
|
||||||
|
ServiceQueue::withTrashed()->whereIn('id', $queueIds)->forceDelete();
|
||||||
|
}
|
||||||
|
|
||||||
|
Department::withTrashed()
|
||||||
|
->where('owner_ref', $ownerRef)
|
||||||
|
->whereIn('branch_id', Branch::query()->where('organization_id', $orgId)->pluck('id'))
|
||||||
|
->forceDelete();
|
||||||
|
}
|
||||||
|
|
||||||
protected function applyPackageSettings(Organization $organization, IndustryPackage $package): void
|
protected function applyPackageSettings(Organization $organization, IndustryPackage $package): void
|
||||||
{
|
{
|
||||||
// Persisted fully after install; noop placeholder for future hooks.
|
// Persisted fully after install; noop placeholder for future hooks.
|
||||||
|
|||||||
@@ -22,6 +22,38 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@auth
|
||||||
|
@php
|
||||||
|
$industryBannerOrg = app(\App\Services\Qms\OrganizationResolver::class)->resolveForUser(auth()->user());
|
||||||
|
$industryNeedsReinstall = $industryBannerOrg
|
||||||
|
&& \App\Services\Qms\Industry\IndustryPackageInstaller::needsReinstall($industryBannerOrg);
|
||||||
|
$industryCanManage = $industryNeedsReinstall
|
||||||
|
&& app(\App\Services\Qms\QmsPermissions::class)->can(
|
||||||
|
app(\App\Services\Qms\OrganizationResolver::class)->memberFor(auth()->user(), $industryBannerOrg),
|
||||||
|
'settings.manage'
|
||||||
|
);
|
||||||
|
$industrySelectedLabel = $industryNeedsReinstall
|
||||||
|
? (app(\App\Services\Qms\Industry\IndustryPackageRegistry::class)
|
||||||
|
->get((string) data_get($industryBannerOrg->settings, 'industry'))
|
||||||
|
?->label() ?? data_get($industryBannerOrg->settings, 'industry'))
|
||||||
|
: null;
|
||||||
|
@endphp
|
||||||
|
@if ($industryCanManage && ! request()->routeIs('qms.settings.*'))
|
||||||
|
<div class="mb-4 rounded-xl border border-amber-300 bg-amber-50 px-4 py-3 text-sm text-amber-950 dark:border-amber-700 dark:bg-amber-950/40 dark:text-amber-100">
|
||||||
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||||
|
<div>
|
||||||
|
<p class="font-semibold">Reinstall industry package required</p>
|
||||||
|
<p class="mt-1">
|
||||||
|
Industry is set to <span class="font-medium">{{ $industrySelectedLabel }}</span>, but the installed queues still match the previous template.
|
||||||
|
Reinstall to clear old queues and apply the new package.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<a href="{{ route('qms.settings.edit') }}" class="btn-primary shrink-0 text-sm">Open settings</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@endauth
|
||||||
|
|
||||||
@if ($errors->any())
|
@if ($errors->any())
|
||||||
<div class="mb-4 rounded-lg bg-red-50 border border-red-200 p-4">
|
<div class="mb-4 rounded-lg bg-red-50 border border-red-200 p-4">
|
||||||
<p class="text-sm font-medium text-red-800">Please fix the following:</p>
|
<p class="text-sm font-medium text-red-800">Please fix the following:</p>
|
||||||
|
|||||||
@@ -153,18 +153,34 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
{{-- Separate form: nested forms break Save settings in browsers --}}
|
{{-- Separate form: nested forms break Save settings in browsers --}}
|
||||||
@if ($canManage && $activePackage)
|
@if ($canManage && ($selectedPackage ?? $activePackage))
|
||||||
|
@php $templatePackage = $selectedPackage ?? $activePackage; @endphp
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<x-settings.card title="Industry template" description="Re-sync departments, stages, service points, workflow, and announcement copy from the selected industry package. Existing tickets are kept.">
|
@if ($needsPackageReinstall ?? false)
|
||||||
|
<div class="mb-4 rounded-xl border border-amber-300 bg-amber-50 px-4 py-3 text-sm text-amber-950 dark:border-amber-700 dark:bg-amber-950/40 dark:text-amber-100">
|
||||||
|
<p class="font-semibold">Industry package reinstall required</p>
|
||||||
|
<p class="mt-1">
|
||||||
|
You selected <span class="font-medium">{{ $templatePackage->label() }}</span>, but the installed template still matches
|
||||||
|
<span class="font-medium">{{ $activePackage?->label() ?? 'the previous industry' }}</span>.
|
||||||
|
Reinstall to clear old queues and apply the new service points.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<x-settings.card
|
||||||
|
title="Industry template"
|
||||||
|
description="Reinstall replaces departments, stages, service points, and workflow from the selected industry package. Existing queues, tickets, and related data are cleared."
|
||||||
|
>
|
||||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||||
<p class="text-sm text-slate-600">
|
<p class="text-sm text-slate-600">
|
||||||
{{ $activePackage->label() }} uses
|
{{ $templatePackage->label() }} uses
|
||||||
<span class="font-medium text-slate-900">{{ count($activePackage->stages()) }}</span> workflow stages and
|
<span class="font-medium text-slate-900">{{ count($templatePackage->stages()) }}</span> workflow stages and
|
||||||
industry-specific terminology ({{ $activePackage->terminology()['ticket'] ?? 'Ticket' }}).
|
industry-specific terminology ({{ $templatePackage->terminology()['ticket'] ?? 'Ticket' }}).
|
||||||
</p>
|
</p>
|
||||||
<form method="POST" action="{{ route('qms.settings.package.reinstall') }}">
|
<form method="POST" action="{{ route('qms.settings.package.reinstall') }}">
|
||||||
@csrf
|
@csrf
|
||||||
<button type="submit" class="btn-secondary shrink-0 text-sm">Reinstall package</button>
|
<button type="submit" class="{{ ($needsPackageReinstall ?? false) ? 'btn-primary' : 'btn-secondary' }} shrink-0 text-sm">
|
||||||
|
{{ ($needsPackageReinstall ?? false) ? 'Reinstall package now' : 'Reinstall package' }}
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</x-settings.card>
|
</x-settings.card>
|
||||||
|
|||||||
@@ -206,4 +206,96 @@ class IndustryPackageTest extends TestCase
|
|||||||
$this->assertSame($before, ServiceQueue::where('organization_id', $org->id)->count());
|
$this->assertSame($before, ServiceQueue::where('organization_id', $org->id)->count());
|
||||||
$this->assertSame('patient', data_get($org->fresh()->settings, 'industry_package.ticket_entity'));
|
$this->assertSame('patient', data_get($org->fresh()->settings, 'industry_package.ticket_entity'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_replace_install_clears_previous_queues_and_tickets(): void
|
||||||
|
{
|
||||||
|
$user = User::create([
|
||||||
|
'public_id' => 'pkg-replace-owner',
|
||||||
|
'name' => 'Replace Owner',
|
||||||
|
'email' => 'replace-owner@example.com',
|
||||||
|
'password' => bcrypt('password'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$org = app(OrganizationResolver::class)->completeOnboarding($user, [
|
||||||
|
'organization_name' => 'Cafe',
|
||||||
|
'industry' => 'restaurant',
|
||||||
|
'appointment_mode' => 'hybrid',
|
||||||
|
'branch_name' => 'Main',
|
||||||
|
'timezone' => 'UTC',
|
||||||
|
]);
|
||||||
|
$org->update([
|
||||||
|
'settings' => array_merge($org->settings ?? [], [
|
||||||
|
'plan' => 'pro',
|
||||||
|
'plan_expires_at' => now()->addMonth()->toIso8601String(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$branch = Branch::where('organization_id', $org->id)->first();
|
||||||
|
$oldQueue = ServiceQueue::where('organization_id', $org->id)->first();
|
||||||
|
$this->assertNotNull($oldQueue);
|
||||||
|
|
||||||
|
app(QueueEngine::class)->issueTicket($oldQueue, $user->public_id, [
|
||||||
|
'customer_name' => 'Old Ticket',
|
||||||
|
'source' => 'reception',
|
||||||
|
]);
|
||||||
|
$this->assertGreaterThan(0, \App\Models\Ticket::where('organization_id', $org->id)->count());
|
||||||
|
|
||||||
|
$result = app(IndustryPackageInstaller::class)->install($org->fresh(), $branch, 'banking', [
|
||||||
|
'replace' => true,
|
||||||
|
'include_optional' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertTrue($result['replaced']);
|
||||||
|
$this->assertSame(0, \App\Models\Ticket::where('organization_id', $org->id)->count());
|
||||||
|
$this->assertFalse(
|
||||||
|
ServiceQueue::where('organization_id', $org->id)->where('id', $oldQueue->id)->exists()
|
||||||
|
);
|
||||||
|
$this->assertSame('banking', data_get($org->fresh()->settings, 'industry_package.key'));
|
||||||
|
$this->assertNull(data_get($org->fresh()->settings, 'industry_package_needs_reinstall'));
|
||||||
|
$this->assertGreaterThanOrEqual(3, ServiceQueue::where('organization_id', $org->id)->count());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_changing_industry_requires_reinstall_without_auto_provision(): void
|
||||||
|
{
|
||||||
|
$this->withoutMiddleware(\App\Http\Middleware\EnsurePlatformSession::class);
|
||||||
|
|
||||||
|
$user = User::create([
|
||||||
|
'public_id' => 'pkg-settings-owner',
|
||||||
|
'name' => 'Settings Owner',
|
||||||
|
'email' => 'settings-pkg@example.com',
|
||||||
|
'password' => bcrypt('password'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$org = app(OrganizationResolver::class)->completeOnboarding($user, [
|
||||||
|
'organization_name' => 'Shop',
|
||||||
|
'industry' => 'restaurant',
|
||||||
|
'appointment_mode' => 'hybrid',
|
||||||
|
'branch_name' => 'Main',
|
||||||
|
'timezone' => 'UTC',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$beforeQueues = ServiceQueue::where('organization_id', $org->id)->count();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('qms.settings.update'), [
|
||||||
|
'name' => $org->name,
|
||||||
|
'timezone' => $org->timezone,
|
||||||
|
'appointment_mode' => 'hybrid',
|
||||||
|
'industry' => 'banking',
|
||||||
|
'notifications_enabled' => '1',
|
||||||
|
])
|
||||||
|
->assertRedirect();
|
||||||
|
|
||||||
|
$org->refresh();
|
||||||
|
$this->assertSame('banking', data_get($org->settings, 'industry'));
|
||||||
|
$this->assertSame('restaurant', data_get($org->settings, 'industry_package.key'));
|
||||||
|
$this->assertTrue((bool) data_get($org->settings, 'industry_package_needs_reinstall'));
|
||||||
|
$this->assertTrue(IndustryPackageInstaller::needsReinstall($org));
|
||||||
|
$this->assertSame($beforeQueues, ServiceQueue::where('organization_id', $org->id)->count());
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('qms.dashboard'))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Reinstall industry package required');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user