Fix nurse onboarding redirects and specialty Documents 404s.
Deploy Ladill Care / deploy (push) Successful in 36s
Deploy Ladill Care / deploy (push) Successful in 36s
Resolve staff memberships by invite/demo email as well as public_id so nurses stay on their employer org; block staff from owner onboarding; keep Documents viewable with module access while upload stays manage-gated. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -26,6 +26,10 @@ class OnboardingController extends Controller
|
|||||||
return redirect()->route('care.dashboard');
|
return redirect()->route('care.dashboard');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! $this->organizations->canCompleteOnboarding($request->user())) {
|
||||||
|
abort(403, 'Your facility setup is incomplete. Ask an administrator to finish onboarding.');
|
||||||
|
}
|
||||||
|
|
||||||
return view('care.onboarding.show', [
|
return view('care.onboarding.show', [
|
||||||
'user' => $request->user(),
|
'user' => $request->user(),
|
||||||
'timezones' => timezone_identifiers_list(),
|
'timezones' => timezone_identifiers_list(),
|
||||||
@@ -41,6 +45,10 @@ class OnboardingController extends Controller
|
|||||||
return redirect()->route('care.dashboard');
|
return redirect()->route('care.dashboard');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! $this->organizations->canCompleteOnboarding($request->user())) {
|
||||||
|
abort(403, 'Your facility setup is incomplete. Ask an administrator to finish onboarding.');
|
||||||
|
}
|
||||||
|
|
||||||
$categoryKeys = array_keys($this->workflows->categories());
|
$categoryKeys = array_keys($this->workflows->categories());
|
||||||
$templateKeys = array_keys($this->workflows->templates());
|
$templateKeys = array_keys($this->workflows->templates());
|
||||||
|
|
||||||
|
|||||||
@@ -347,6 +347,7 @@ class SpecialtyModuleController extends Controller
|
|||||||
|
|
||||||
$organization = $this->organization($request);
|
$organization = $this->organization($request);
|
||||||
$member = $this->member($request);
|
$member = $this->member($request);
|
||||||
|
// Viewing docs is workspace GET (memberCanAccess / memberCanView). Upload needs manage.
|
||||||
abort_unless($modules->memberCanAccess($organization, $member, $module), 403);
|
abort_unless($modules->memberCanAccess($organization, $member, $module), 403);
|
||||||
abort_unless($modules->memberCanManage($organization, $member, $module), 403);
|
abort_unless($modules->memberCanManage($organization, $member, $module), 403);
|
||||||
abort_unless($visit->organization_id === $organization->id, 404);
|
abort_unless($visit->organization_id === $organization->id, 404);
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ class EnsureOrganizationSetup
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (! $this->organizations->isOnboarded($user)) {
|
if (! $this->organizations->isOnboarded($user)) {
|
||||||
|
// Staff already attached to a tenant must not see owner onboarding.
|
||||||
|
if ($this->organizations->membershipFor($user)) {
|
||||||
|
abort(403, 'Your facility setup is incomplete. Ask an administrator to finish onboarding.');
|
||||||
|
}
|
||||||
|
|
||||||
return redirect()->route('care.onboarding.show');
|
return redirect()->route('care.onboarding.show');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -670,17 +670,54 @@ class DemoTenantSeeder
|
|||||||
$branch = $branches[$index % count($branches)];
|
$branch = $branches[$index % count($branches)];
|
||||||
}
|
}
|
||||||
|
|
||||||
Member::query()->updateOrCreate(
|
$email = strtolower((string) $staff['email']);
|
||||||
[
|
// Prefer remapped public_id when the staff user already exists locally,
|
||||||
'organization_id' => $organization->id,
|
// so reseeds do not orphan SSO sessions keyed by public_id.
|
||||||
'user_ref' => strtolower($staff['email']),
|
$publicId = User::query()
|
||||||
],
|
->whereRaw('LOWER(email) = ?', [$email])
|
||||||
[
|
->value('public_id');
|
||||||
|
$userRef = is_string($publicId) && $publicId !== '' ? $publicId : $email;
|
||||||
|
|
||||||
|
$existing = Member::query()
|
||||||
|
->where('organization_id', $organization->id)
|
||||||
|
->where(function ($query) use ($email, $publicId) {
|
||||||
|
$query->where('user_ref', $email);
|
||||||
|
if (is_string($publicId) && $publicId !== '') {
|
||||||
|
$query->orWhere('user_ref', $publicId);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->orderByRaw('CASE WHEN user_ref = ? THEN 0 ELSE 1 END', [$userRef])
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($existing) {
|
||||||
|
$existing->forceFill([
|
||||||
|
'user_ref' => $userRef,
|
||||||
'owner_ref' => $ownerRef,
|
'owner_ref' => $ownerRef,
|
||||||
'role' => $role,
|
'role' => $role,
|
||||||
'branch_id' => $role === 'hospital_admin' ? null : $branch?->id,
|
'branch_id' => $role === 'hospital_admin' ? null : $branch?->id,
|
||||||
],
|
])->save();
|
||||||
);
|
|
||||||
|
Member::query()
|
||||||
|
->where('organization_id', $organization->id)
|
||||||
|
->where('id', '!=', $existing->id)
|
||||||
|
->where(function ($query) use ($email, $publicId) {
|
||||||
|
$query->where('user_ref', $email);
|
||||||
|
if (is_string($publicId) && $publicId !== '') {
|
||||||
|
$query->orWhere('user_ref', $publicId);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Member::query()->create([
|
||||||
|
'organization_id' => $organization->id,
|
||||||
|
'user_ref' => $userRef,
|
||||||
|
'owner_ref' => $ownerRef,
|
||||||
|
'role' => $role,
|
||||||
|
'branch_id' => $role === 'hospital_admin' ? null : $branch?->id,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ class OrganizationResolver
|
|||||||
{
|
{
|
||||||
public function resolveForUser(User $user): ?Organization
|
public function resolveForUser(User $user): ?Organization
|
||||||
{
|
{
|
||||||
$ref = $user->ownerRef();
|
$member = $this->membershipFor($user);
|
||||||
|
|
||||||
$member = Member::where('user_ref', $ref)->first();
|
|
||||||
if ($member) {
|
if ($member) {
|
||||||
$organization = Organization::query()->find($member->organization_id);
|
$organization = Organization::query()->find($member->organization_id);
|
||||||
if ($organization) {
|
if ($organization) {
|
||||||
@@ -27,7 +25,7 @@ class OrganizationResolver
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Organization::owned($ref)->first();
|
return Organization::owned($user->ownerRef())->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function forUser(User $user): Organization
|
public function forUser(User $user): Organization
|
||||||
@@ -44,6 +42,40 @@ class OrganizationResolver
|
|||||||
&& (bool) data_get($organization->settings, 'onboarded', false);
|
&& (bool) data_get($organization->settings, 'onboarded', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any Care membership for this user (public_id or invite/demo email key).
|
||||||
|
* Remaps email-keyed rows to public_id when found so staff stay linked after SSO.
|
||||||
|
*/
|
||||||
|
public function membershipFor(User $user): ?Member
|
||||||
|
{
|
||||||
|
$member = Member::query()->where('user_ref', $user->ownerRef())->first();
|
||||||
|
if ($member) {
|
||||||
|
return $member;
|
||||||
|
}
|
||||||
|
|
||||||
|
$email = strtolower(trim((string) $user->email));
|
||||||
|
if ($email === '' || $email === $user->ownerRef()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$member = Member::query()->where('user_ref', $email)->first();
|
||||||
|
|
||||||
|
return $member ? $this->remapMemberUserRef($member, $user) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True when the user may run owner onboarding (create a facility).
|
||||||
|
* Staff already attached to a tenant must not create a second org.
|
||||||
|
*/
|
||||||
|
public function canCompleteOnboarding(User $user): bool
|
||||||
|
{
|
||||||
|
if ($this->membershipFor($user)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ! $this->isOnboarded($user);
|
||||||
|
}
|
||||||
|
|
||||||
public function memberFor(User $user, ?Organization $organization = null): ?Member
|
public function memberFor(User $user, ?Organization $organization = null): ?Member
|
||||||
{
|
{
|
||||||
$organization ??= $this->resolveForUser($user);
|
$organization ??= $this->resolveForUser($user);
|
||||||
@@ -51,9 +83,52 @@ class OrganizationResolver
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Member::where('organization_id', $organization->id)
|
$member = Member::query()
|
||||||
|
->where('organization_id', $organization->id)
|
||||||
->where('user_ref', $user->ownerRef())
|
->where('user_ref', $user->ownerRef())
|
||||||
->first();
|
->first();
|
||||||
|
if ($member) {
|
||||||
|
return $member;
|
||||||
|
}
|
||||||
|
|
||||||
|
$email = strtolower(trim((string) $user->email));
|
||||||
|
if ($email === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$member = Member::query()
|
||||||
|
->where('organization_id', $organization->id)
|
||||||
|
->where('user_ref', $email)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
return $member ? $this->remapMemberUserRef($member, $user) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Promote invite/demo email user_ref keys to the stable OIDC public_id.
|
||||||
|
*/
|
||||||
|
protected function remapMemberUserRef(Member $member, User $user): Member
|
||||||
|
{
|
||||||
|
$ref = $user->ownerRef();
|
||||||
|
if ($member->user_ref === $ref) {
|
||||||
|
return $member;
|
||||||
|
}
|
||||||
|
|
||||||
|
$conflict = Member::query()
|
||||||
|
->where('organization_id', $member->organization_id)
|
||||||
|
->where('user_ref', $ref)
|
||||||
|
->where('id', '!=', $member->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($conflict) {
|
||||||
|
$member->delete();
|
||||||
|
|
||||||
|
return $conflict;
|
||||||
|
}
|
||||||
|
|
||||||
|
$member->forceFill(['user_ref' => $ref])->save();
|
||||||
|
|
||||||
|
return $member->fresh() ?? $member;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ensureOwnerMember(User $user, Organization $organization): Member
|
public function ensureOwnerMember(User $user, Organization $organization): Member
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
<h3 class="text-sm font-semibold text-slate-900">Documents</h3>
|
<h3 class="text-sm font-semibold text-slate-900">Documents</h3>
|
||||||
<p class="mt-1 text-xs text-slate-500">Specialty documents attach to the patient chart for this episode.</p>
|
<p class="mt-1 text-xs text-slate-500">Specialty documents attach to the patient chart for this episode.</p>
|
||||||
|
|
||||||
@if ($canManageClinical ?? $canManageSpecialty ?? true)
|
@if ($canManageClinical ?? $canManageSpecialty ?? false)
|
||||||
<form method="POST" action="{{ route('care.specialty.documents.upload', ['module' => $moduleKey, 'visit' => $workspaceVisit]) }}" enctype="multipart/form-data" class="mt-4 space-y-3">
|
<form method="POST" action="{{ route('care.specialty.documents.upload', ['module' => $moduleKey, 'visit' => $workspaceVisit]) }}" enctype="multipart/form-data" class="mt-4 space-y-3">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="file" name="attachment" required accept=".pdf,.jpg,.jpeg,.png,.webp" class="block w-full text-sm">
|
<input type="file" name="attachment" required accept=".pdf,.jpg,.jpeg,.png,.webp" class="block w-full text-sm">
|
||||||
|
|||||||
@@ -0,0 +1,384 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Http\Middleware\EnsurePlatformSession;
|
||||||
|
use App\Models\Appointment;
|
||||||
|
use App\Models\Branch;
|
||||||
|
use App\Models\Department;
|
||||||
|
use App\Models\Member;
|
||||||
|
use App\Models\Organization;
|
||||||
|
use App\Models\Patient;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Visit;
|
||||||
|
use App\Services\Care\OrganizationResolver;
|
||||||
|
use App\Services\Care\SpecialtyModuleService;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class CareNurseOrgAccessTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_nurse_with_email_user_ref_membership_is_not_sent_to_onboarding(): void
|
||||||
|
{
|
||||||
|
$this->withoutMiddleware(EnsurePlatformSession::class);
|
||||||
|
|
||||||
|
$owner = User::create([
|
||||||
|
'public_id' => 'owner-pid',
|
||||||
|
'name' => 'Owner',
|
||||||
|
'email' => 'owner@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$nurse = User::create([
|
||||||
|
'public_id' => 'nurse-pid',
|
||||||
|
'name' => 'Nurse',
|
||||||
|
'email' => 'demo-pro-nurse@ladill.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$organization = Organization::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'name' => 'Clinic',
|
||||||
|
'slug' => 'clinic',
|
||||||
|
'settings' => [
|
||||||
|
'onboarded' => true,
|
||||||
|
'plan' => 'pro',
|
||||||
|
'plan_expires_at' => now()->addMonth()->toIso8601String(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
Member::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'organization_id' => $organization->id,
|
||||||
|
'user_ref' => $owner->public_id,
|
||||||
|
'role' => 'hospital_admin',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Demo seeder style: staff member keyed by email until SSO remaps.
|
||||||
|
Member::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'organization_id' => $organization->id,
|
||||||
|
'user_ref' => strtolower($nurse->email),
|
||||||
|
'role' => 'nurse',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$resolver = app(OrganizationResolver::class);
|
||||||
|
$this->assertTrue($resolver->isOnboarded($nurse));
|
||||||
|
$this->assertFalse($resolver->canCompleteOnboarding($nurse));
|
||||||
|
$this->assertSame($organization->id, $resolver->resolveForUser($nurse)?->id);
|
||||||
|
|
||||||
|
// Remap sticks so later requests resolve by public_id.
|
||||||
|
$this->assertDatabaseHas('care_members', [
|
||||||
|
'organization_id' => $organization->id,
|
||||||
|
'user_ref' => $nurse->public_id,
|
||||||
|
'role' => 'nurse',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->actingAs($nurse)
|
||||||
|
->get(route('care.dashboard'))
|
||||||
|
->assertOk();
|
||||||
|
|
||||||
|
$this->actingAs($nurse)
|
||||||
|
->get(route('care.onboarding.show'))
|
||||||
|
->assertRedirect(route('care.dashboard'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_nurse_who_wrongly_owns_empty_org_still_uses_employer_membership(): void
|
||||||
|
{
|
||||||
|
$this->withoutMiddleware(EnsurePlatformSession::class);
|
||||||
|
|
||||||
|
$owner = User::create([
|
||||||
|
'public_id' => 'employer-owner',
|
||||||
|
'name' => 'Owner',
|
||||||
|
'email' => 'employer@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$nurse = User::create([
|
||||||
|
'public_id' => 'orphan-nurse',
|
||||||
|
'name' => 'Nurse',
|
||||||
|
'email' => 'orphan-nurse@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$employer = Organization::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'name' => 'Employer Clinic',
|
||||||
|
'slug' => 'employer-clinic',
|
||||||
|
'settings' => [
|
||||||
|
'onboarded' => true,
|
||||||
|
'plan' => 'pro',
|
||||||
|
'plan_expires_at' => now()->addMonth()->toIso8601String(),
|
||||||
|
'specialty_modules' => ['emergency' => true],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Orphan org from completing owner onboarding while membership was unresolved.
|
||||||
|
Organization::create([
|
||||||
|
'owner_ref' => $nurse->public_id,
|
||||||
|
'name' => 'Nurse Own Clinic',
|
||||||
|
'slug' => 'nurse-own-clinic',
|
||||||
|
'settings' => ['onboarded' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
Member::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'organization_id' => $employer->id,
|
||||||
|
'user_ref' => strtolower($nurse->email),
|
||||||
|
'role' => 'nurse',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$branch = Branch::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'organization_id' => $employer->id,
|
||||||
|
'name' => 'Main',
|
||||||
|
'is_active' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
app(SpecialtyModuleService::class)->activate($employer, $owner->public_id, 'emergency');
|
||||||
|
$dept = Department::query()->where('type', 'emergency')->where('branch_id', $branch->id)->firstOrFail();
|
||||||
|
|
||||||
|
$patient = Patient::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'organization_id' => $employer->id,
|
||||||
|
'branch_id' => $branch->id,
|
||||||
|
'patient_number' => 'P-ORPHAN',
|
||||||
|
'first_name' => 'Ama',
|
||||||
|
'last_name' => 'Patient',
|
||||||
|
'gender' => 'female',
|
||||||
|
'date_of_birth' => '1990-01-01',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$visit = Visit::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'organization_id' => $employer->id,
|
||||||
|
'branch_id' => $branch->id,
|
||||||
|
'patient_id' => $patient->id,
|
||||||
|
'status' => Visit::STATUS_IN_PROGRESS,
|
||||||
|
'checked_in_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Appointment::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'organization_id' => $employer->id,
|
||||||
|
'branch_id' => $branch->id,
|
||||||
|
'patient_id' => $patient->id,
|
||||||
|
'department_id' => $dept->id,
|
||||||
|
'visit_id' => $visit->id,
|
||||||
|
'type' => Appointment::TYPE_WALK_IN,
|
||||||
|
'status' => Appointment::STATUS_IN_CONSULTATION,
|
||||||
|
'scheduled_at' => now(),
|
||||||
|
'checked_in_at' => now(),
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertSame($employer->id, app(OrganizationResolver::class)->resolveForUser($nurse)?->id);
|
||||||
|
|
||||||
|
$this->actingAs($nurse)
|
||||||
|
->get(route('care.specialty.workspace', [
|
||||||
|
'module' => 'emergency',
|
||||||
|
'visit' => $visit,
|
||||||
|
'tab' => 'documents',
|
||||||
|
]))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Documents')
|
||||||
|
->assertSee('Upload document');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_nurse_can_open_emergency_documents_tab(): void
|
||||||
|
{
|
||||||
|
$this->withoutMiddleware(EnsurePlatformSession::class);
|
||||||
|
|
||||||
|
$owner = User::create([
|
||||||
|
'public_id' => 'er-docs-owner',
|
||||||
|
'name' => 'Owner',
|
||||||
|
'email' => 'er-docs-owner@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$nurse = User::create([
|
||||||
|
'public_id' => 'er-docs-nurse',
|
||||||
|
'name' => 'Nurse',
|
||||||
|
'email' => 'er-docs-nurse@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$organization = Organization::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'name' => 'ER Clinic',
|
||||||
|
'slug' => 'er-docs-clinic',
|
||||||
|
'settings' => [
|
||||||
|
'onboarded' => true,
|
||||||
|
'plan' => 'pro',
|
||||||
|
'plan_expires_at' => now()->addMonth()->toIso8601String(),
|
||||||
|
'specialty_modules' => ['emergency' => true],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
Member::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'organization_id' => $organization->id,
|
||||||
|
'user_ref' => $owner->public_id,
|
||||||
|
'role' => 'hospital_admin',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Member::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'organization_id' => $organization->id,
|
||||||
|
'user_ref' => $nurse->public_id,
|
||||||
|
'role' => 'nurse',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$branch = Branch::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'organization_id' => $organization->id,
|
||||||
|
'name' => 'Main',
|
||||||
|
'is_active' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
app(SpecialtyModuleService::class)->activate($organization, $owner->public_id, 'emergency');
|
||||||
|
|
||||||
|
$dept = Department::query()->where('type', 'emergency')->where('branch_id', $branch->id)->firstOrFail();
|
||||||
|
|
||||||
|
$patient = Patient::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'organization_id' => $organization->id,
|
||||||
|
'branch_id' => $branch->id,
|
||||||
|
'patient_number' => 'P-DOCS',
|
||||||
|
'first_name' => 'Ama',
|
||||||
|
'last_name' => 'Docs',
|
||||||
|
'gender' => 'female',
|
||||||
|
'date_of_birth' => '1990-01-01',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$visit = Visit::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'organization_id' => $organization->id,
|
||||||
|
'branch_id' => $branch->id,
|
||||||
|
'patient_id' => $patient->id,
|
||||||
|
'status' => Visit::STATUS_IN_PROGRESS,
|
||||||
|
'checked_in_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Appointment::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'organization_id' => $organization->id,
|
||||||
|
'branch_id' => $branch->id,
|
||||||
|
'patient_id' => $patient->id,
|
||||||
|
'department_id' => $dept->id,
|
||||||
|
'visit_id' => $visit->id,
|
||||||
|
'type' => Appointment::TYPE_WALK_IN,
|
||||||
|
'status' => Appointment::STATUS_IN_CONSULTATION,
|
||||||
|
'scheduled_at' => now(),
|
||||||
|
'checked_in_at' => now(),
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->actingAs($nurse)
|
||||||
|
->get(route('care.specialty.workspace', [
|
||||||
|
'module' => 'emergency',
|
||||||
|
'visit' => $visit,
|
||||||
|
'tab' => 'documents',
|
||||||
|
]))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Documents')
|
||||||
|
->assertSee('Upload document');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_view_only_staff_sees_documents_without_upload(): void
|
||||||
|
{
|
||||||
|
$this->withoutMiddleware(EnsurePlatformSession::class);
|
||||||
|
|
||||||
|
$owner = User::create([
|
||||||
|
'public_id' => 'view-docs-owner',
|
||||||
|
'name' => 'Owner',
|
||||||
|
'email' => 'view-docs-owner@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$lab = User::create([
|
||||||
|
'public_id' => 'view-docs-lab',
|
||||||
|
'name' => 'Lab',
|
||||||
|
'email' => 'view-docs-lab@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$organization = Organization::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'name' => 'Cardio Clinic',
|
||||||
|
'slug' => 'cardio-docs-clinic',
|
||||||
|
'settings' => [
|
||||||
|
'onboarded' => true,
|
||||||
|
'plan' => 'pro',
|
||||||
|
'plan_expires_at' => now()->addMonth()->toIso8601String(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
Member::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'organization_id' => $organization->id,
|
||||||
|
'user_ref' => $owner->public_id,
|
||||||
|
'role' => 'hospital_admin',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Member::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'organization_id' => $organization->id,
|
||||||
|
'user_ref' => $lab->public_id,
|
||||||
|
'role' => 'lab_technician',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$branch = Branch::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'organization_id' => $organization->id,
|
||||||
|
'name' => 'Main',
|
||||||
|
'is_active' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$modules = app(SpecialtyModuleService::class);
|
||||||
|
$modules->activate($organization, $owner->public_id, 'cardiology');
|
||||||
|
|
||||||
|
$dept = Department::query()->where('type', 'cardiology')->where('branch_id', $branch->id)->firstOrFail();
|
||||||
|
|
||||||
|
$patient = Patient::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'organization_id' => $organization->id,
|
||||||
|
'branch_id' => $branch->id,
|
||||||
|
'patient_number' => 'P-VIEW-DOCS',
|
||||||
|
'first_name' => 'Kofi',
|
||||||
|
'last_name' => 'View',
|
||||||
|
'gender' => 'male',
|
||||||
|
'date_of_birth' => '1985-01-01',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$visit = Visit::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'organization_id' => $organization->id,
|
||||||
|
'branch_id' => $branch->id,
|
||||||
|
'patient_id' => $patient->id,
|
||||||
|
'status' => Visit::STATUS_IN_PROGRESS,
|
||||||
|
'checked_in_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Appointment::create([
|
||||||
|
'owner_ref' => $owner->public_id,
|
||||||
|
'organization_id' => $organization->id,
|
||||||
|
'branch_id' => $branch->id,
|
||||||
|
'patient_id' => $patient->id,
|
||||||
|
'department_id' => $dept->id,
|
||||||
|
'visit_id' => $visit->id,
|
||||||
|
'type' => Appointment::TYPE_WALK_IN,
|
||||||
|
'status' => Appointment::STATUS_IN_CONSULTATION,
|
||||||
|
'scheduled_at' => now(),
|
||||||
|
'checked_in_at' => now(),
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$labMember = Member::query()->where('user_ref', $lab->public_id)->firstOrFail();
|
||||||
|
$this->assertSame('view', $modules->memberAccessLevel($organization, $labMember, 'cardiology'));
|
||||||
|
|
||||||
|
$this->actingAs($lab)
|
||||||
|
->get(route('care.specialty.workspace', [
|
||||||
|
'module' => 'cardiology',
|
||||||
|
'visit' => $visit,
|
||||||
|
'tab' => 'documents',
|
||||||
|
]))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Documents')
|
||||||
|
->assertDontSee('Upload document');
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user