- Added `inventory_package_uuid` and `inventory_package_title` fields to the `ServiceItem` interface. - Updated API documentation to reflect new fields in service item responses. - Implemented methods in `ClinicServiceController` to handle inventory package associations. - Created `ServiceItemAuditLog` entity and repository for tracking changes to service items. - Added functionality to log changes to service items, including inventory package associations. - Implemented tests for attaching/detaching inventory packages and auditing changes. - Created database migrations for new fields and audit log table.
206 lines
7.9 KiB
PHP
206 lines
7.9 KiB
PHP
<?php
|
|
|
|
namespace App\Tests\ClinicService;
|
|
|
|
use App\Auth\Entity\User;
|
|
use App\ClinicService\Entity\ServiceItem;
|
|
use App\ClinicService\Entity\ServiceSection;
|
|
use App\Doctor\Entity\Doctor;
|
|
use App\Inventory\Entity\InventoryPackage;
|
|
use App\Tests\ApiTestCase;
|
|
|
|
/**
|
|
* دو قابلیت صفحهی جزئیات سرویس:
|
|
* ۱. اتصال اختیاری خدمت به یک پکیج کالای مصرفی (کالاهای مرتبط)
|
|
* ۲. تاریخچهی تغییرات خدمت (service_item_audit_logs)
|
|
*/
|
|
class ServiceItemPackageAndAuditTest extends ApiTestCase
|
|
{
|
|
/** @return array{0: User, 1: Doctor, 2: ServiceSection} */
|
|
private function makeDoctorWithSection(): array
|
|
{
|
|
$owner = $this->createUser(['ROLE_DOCTOR']);
|
|
$doctor = new Doctor($owner, 'دکتر تست پکیج');
|
|
$this->em->persist($doctor);
|
|
$this->em->flush();
|
|
|
|
$section = new ServiceSection('doctor', $doctor->getId(), 'تزریقات');
|
|
$this->em->persist($section);
|
|
$this->em->flush();
|
|
|
|
return [$owner, $doctor, $section];
|
|
}
|
|
|
|
private function makePackage(Doctor $doctor, string $title = 'پکیج سرم'): InventoryPackage
|
|
{
|
|
$package = new InventoryPackage('doctor', $doctor->getId(), $title);
|
|
$this->em->persist($package);
|
|
$this->em->flush();
|
|
|
|
return $package;
|
|
}
|
|
|
|
private function makeItem(ServiceSection $section, string $name = 'سرم ۵۰۰cc'): ServiceItem
|
|
{
|
|
$item = new ServiceItem($section, $name, 850_000);
|
|
$this->em->persist($item);
|
|
$this->em->flush();
|
|
|
|
return $item;
|
|
}
|
|
|
|
// ── کالاهای مرتبط ────────────────────────────────────────────────────────
|
|
|
|
public function testAttachingAPackageIsReflectedInTheDetailResponse(): void
|
|
{
|
|
[$owner, $doctor, $section] = $this->makeDoctorWithSection();
|
|
$package = $this->makePackage($doctor);
|
|
$item = $this->makeItem($section);
|
|
|
|
$this->authJson('PATCH', '/api/v1/service-item/' . $item->getUuid(), $owner, [
|
|
'inventory_package_uuid' => $package->getUuid(),
|
|
]);
|
|
$this->assertSame(200, $this->responseCode());
|
|
|
|
$body = $this->authJson('GET', '/api/v1/service-item/' . $item->getUuid(), $owner);
|
|
$this->assertSame($package->getUuid(), $body['data']['inventory_package_uuid']);
|
|
$this->assertSame('پکیج سرم', $body['data']['inventory_package_title']);
|
|
}
|
|
|
|
public function testDetachingWithNullClearsThePackage(): void
|
|
{
|
|
[$owner, $doctor, $section] = $this->makeDoctorWithSection();
|
|
$package = $this->makePackage($doctor);
|
|
$item = $this->makeItem($section);
|
|
|
|
$this->authJson('PATCH', '/api/v1/service-item/' . $item->getUuid(), $owner, [
|
|
'inventory_package_uuid' => $package->getUuid(),
|
|
]);
|
|
$this->authJson('PATCH', '/api/v1/service-item/' . $item->getUuid(), $owner, [
|
|
'inventory_package_uuid' => null,
|
|
]);
|
|
|
|
$body = $this->authJson('GET', '/api/v1/service-item/' . $item->getUuid(), $owner);
|
|
$this->assertNull($body['data']['inventory_package_uuid']);
|
|
}
|
|
|
|
public function testPackageOfAnotherTenantIsRejected(): void
|
|
{
|
|
[$owner, , $section] = $this->makeDoctorWithSection();
|
|
$item = $this->makeItem($section);
|
|
|
|
$stranger = $this->createUser(['ROLE_DOCTOR']);
|
|
$other = new Doctor($stranger, 'دکتر غریبه');
|
|
$this->em->persist($other);
|
|
$this->em->flush();
|
|
$foreignPackage = $this->makePackage($other, 'پکیج غریبه');
|
|
|
|
$this->authJson('PATCH', '/api/v1/service-item/' . $item->getUuid(), $owner, [
|
|
'inventory_package_uuid' => $foreignPackage->getUuid(),
|
|
]);
|
|
|
|
$this->assertSame(422, $this->responseCode());
|
|
}
|
|
|
|
public function testUnknownPackageUuidIsRejected(): void
|
|
{
|
|
[$owner, , $section] = $this->makeDoctorWithSection();
|
|
$item = $this->makeItem($section);
|
|
|
|
$this->authJson('PATCH', '/api/v1/service-item/' . $item->getUuid(), $owner, [
|
|
'inventory_package_uuid' => 'no-such-package',
|
|
]);
|
|
|
|
$this->assertSame(422, $this->responseCode());
|
|
}
|
|
|
|
// ── لاگ تغییرات ──────────────────────────────────────────────────────────
|
|
|
|
public function testCreatingAServiceWritesACreateLog(): void
|
|
{
|
|
[$owner, , $section] = $this->makeDoctorWithSection();
|
|
|
|
$created = $this->authJson('POST', '/api/v1/service-item', $owner, [
|
|
'section_uuid' => $section->getUuid(),
|
|
'name' => 'خدمت تازه',
|
|
'price_rials' => 100_000,
|
|
]);
|
|
$this->assertSame(201, $this->responseCode());
|
|
|
|
$body = $this->authJson('GET', '/api/v1/service-item/' . $created['data']['uuid'] . '/audit-logs', $owner);
|
|
$this->assertSame(200, $this->responseCode());
|
|
$logs = $body['data'];
|
|
$this->assertCount(1, $logs);
|
|
$this->assertSame('create', $logs[0]['operation']);
|
|
$this->assertSame('خدمت تازه', $logs[0]['new_value']);
|
|
}
|
|
|
|
public function testEachChangedFieldGetsItsOwnLogRow(): void
|
|
{
|
|
[$owner, , $section] = $this->makeDoctorWithSection();
|
|
$item = $this->makeItem($section);
|
|
|
|
$this->authJson('PATCH', '/api/v1/service-item/' . $item->getUuid(), $owner, [
|
|
'name' => 'نام جدید',
|
|
'price_rials' => 990_000,
|
|
]);
|
|
|
|
$body = $this->authJson('GET', '/api/v1/service-item/' . $item->getUuid() . '/audit-logs', $owner);
|
|
$fields = array_column($body['data'], 'field');
|
|
sort($fields);
|
|
|
|
$this->assertSame(['name', 'price_rials'], $fields);
|
|
|
|
$byField = array_column($body['data'], null, 'field');
|
|
$this->assertSame('سرم ۵۰۰cc', $byField['name']['old_value']);
|
|
$this->assertSame('نام جدید', $byField['name']['new_value']);
|
|
$this->assertSame('850000', $byField['price_rials']['old_value']);
|
|
$this->assertSame('990000', $byField['price_rials']['new_value']);
|
|
}
|
|
|
|
public function testUnchangedFieldsAreNotLogged(): void
|
|
{
|
|
[$owner, , $section] = $this->makeDoctorWithSection();
|
|
$item = $this->makeItem($section);
|
|
|
|
// همان مقادیر فعلی دوباره ارسال میشوند
|
|
$this->authJson('PATCH', '/api/v1/service-item/' . $item->getUuid(), $owner, [
|
|
'name' => 'سرم ۵۰۰cc',
|
|
'price_rials' => 850_000,
|
|
]);
|
|
|
|
$body = $this->authJson('GET', '/api/v1/service-item/' . $item->getUuid() . '/audit-logs', $owner);
|
|
$this->assertSame([], $body['data']);
|
|
}
|
|
|
|
public function testAttachingAPackageIsLogged(): void
|
|
{
|
|
[$owner, $doctor, $section] = $this->makeDoctorWithSection();
|
|
$package = $this->makePackage($doctor);
|
|
$item = $this->makeItem($section);
|
|
|
|
$this->authJson('PATCH', '/api/v1/service-item/' . $item->getUuid(), $owner, [
|
|
'inventory_package_uuid' => $package->getUuid(),
|
|
]);
|
|
|
|
$body = $this->authJson('GET', '/api/v1/service-item/' . $item->getUuid() . '/audit-logs', $owner);
|
|
$fields = array_column($body['data'], 'field');
|
|
|
|
$this->assertContains('inventory_package', $fields);
|
|
}
|
|
|
|
public function testAuditLogsOfAnotherTenantAreNotReadable(): void
|
|
{
|
|
[, , $section] = $this->makeDoctorWithSection();
|
|
$item = $this->makeItem($section);
|
|
|
|
$stranger = $this->createUser(['ROLE_DOCTOR']);
|
|
$this->em->persist(new Doctor($stranger, 'دکتر غریبه'));
|
|
$this->em->flush();
|
|
|
|
$this->authJson('GET', '/api/v1/service-item/' . $item->getUuid() . '/audit-logs', $stranger);
|
|
|
|
$this->assertSame(404, $this->responseCode());
|
|
}
|
|
}
|