feat: add support for individual consumable items in service items
- Introduced `consumables` field in `ServiceItem` to allow multiple individual items alongside inventory packages. - Created `ServiceItemConsumable` entity to manage individual consumable items linked to a service. - Updated `ServiceItemController` to handle CRUD operations for consumables. - Enhanced `ServiceDetailPage` and `ServiceItemFormModal` to display and manage consumables. - Added tests to ensure functionality for adding, updating, and validating consumables. - Updated API documentation to reflect changes in service item structure and consumables.
This commit is contained in:
@@ -6,6 +6,7 @@ use App\Auth\Entity\User;
|
||||
use App\ClinicService\Entity\ServiceItem;
|
||||
use App\ClinicService\Entity\ServiceSection;
|
||||
use App\Doctor\Entity\Doctor;
|
||||
use App\Inventory\Entity\InventoryItem;
|
||||
use App\Inventory\Entity\InventoryPackage;
|
||||
use App\Tests\ApiTestCase;
|
||||
|
||||
@@ -114,6 +115,140 @@ class ServiceItemPackageAndAuditTest extends ApiTestCase
|
||||
$this->assertSame(422, $this->responseCode());
|
||||
}
|
||||
|
||||
// ── کالاهای تکی (مکمل پکیج) ──────────────────────────────────────────────
|
||||
|
||||
private function makeInventoryItem(Doctor $doctor, string $name, int $price = 50_000): InventoryItem
|
||||
{
|
||||
$inventoryItem = new InventoryItem('doctor', $doctor->getId(), $name);
|
||||
$inventoryItem->setPrice($price)->setStock(100);
|
||||
$this->em->persist($inventoryItem);
|
||||
$this->em->flush();
|
||||
|
||||
return $inventoryItem;
|
||||
}
|
||||
|
||||
public function testAServiceCanHaveBothAPackageAndIndividualItems(): void
|
||||
{
|
||||
[$owner, $doctor, $section] = $this->makeDoctorWithSection();
|
||||
$package = $this->makePackage($doctor);
|
||||
$gauze = $this->makeInventoryItem($doctor, 'گاز استریل');
|
||||
$syringe = $this->makeInventoryItem($doctor, 'سرنگ ۵cc');
|
||||
$item = $this->makeItem($section);
|
||||
|
||||
$this->authJson('PATCH', '/api/v1/service-item/' . $item->getUuid(), $owner, [
|
||||
'inventory_package_uuid' => $package->getUuid(),
|
||||
'consumables' => [
|
||||
['item_uuid' => $gauze->getUuid(), 'amount' => 2],
|
||||
['item_uuid' => $syringe->getUuid(), 'amount' => 1],
|
||||
],
|
||||
]);
|
||||
$this->assertSame(200, $this->responseCode());
|
||||
|
||||
$body = $this->authJson('GET', '/api/v1/service-item/' . $item->getUuid(), $owner);
|
||||
$row = $body['data'];
|
||||
|
||||
$this->assertSame($package->getUuid(), $row['inventory_package_uuid'], 'پکیج باید کنار اقلام تکی بماند');
|
||||
$this->assertCount(2, $row['consumables']);
|
||||
|
||||
$byName = array_column($row['consumables'], null, 'name');
|
||||
$this->assertSame(2, $byName['گاز استریل']['amount']);
|
||||
$this->assertSame(1, $byName['سرنگ ۵cc']['amount']);
|
||||
$this->assertSame(50_000, $byName['گاز استریل']['price']);
|
||||
}
|
||||
|
||||
public function testResendingConsumablesReplacesTheWholeList(): void
|
||||
{
|
||||
[$owner, $doctor, $section] = $this->makeDoctorWithSection();
|
||||
$gauze = $this->makeInventoryItem($doctor, 'گاز استریل');
|
||||
$syringe = $this->makeInventoryItem($doctor, 'سرنگ ۵cc');
|
||||
$item = $this->makeItem($section);
|
||||
|
||||
$this->authJson('PATCH', '/api/v1/service-item/' . $item->getUuid(), $owner, [
|
||||
'consumables' => [
|
||||
['item_uuid' => $gauze->getUuid(), 'amount' => 2],
|
||||
['item_uuid' => $syringe->getUuid(), 'amount' => 1],
|
||||
],
|
||||
]);
|
||||
|
||||
// فقط یک قلم با تعداد جدید ارسال میشود
|
||||
$this->authJson('PATCH', '/api/v1/service-item/' . $item->getUuid(), $owner, [
|
||||
'consumables' => [['item_uuid' => $gauze->getUuid(), 'amount' => 5]],
|
||||
]);
|
||||
|
||||
$body = $this->authJson('GET', '/api/v1/service-item/' . $item->getUuid(), $owner);
|
||||
$this->assertCount(1, $body['data']['consumables']);
|
||||
$this->assertSame('گاز استریل', $body['data']['consumables'][0]['name']);
|
||||
$this->assertSame(5, $body['data']['consumables'][0]['amount']);
|
||||
}
|
||||
|
||||
public function testEmptyConsumablesArrayClearsThem(): void
|
||||
{
|
||||
[$owner, $doctor, $section] = $this->makeDoctorWithSection();
|
||||
$gauze = $this->makeInventoryItem($doctor, 'گاز استریل');
|
||||
$item = $this->makeItem($section);
|
||||
|
||||
$this->authJson('PATCH', '/api/v1/service-item/' . $item->getUuid(), $owner, [
|
||||
'consumables' => [['item_uuid' => $gauze->getUuid(), 'amount' => 2]],
|
||||
]);
|
||||
$this->authJson('PATCH', '/api/v1/service-item/' . $item->getUuid(), $owner, ['consumables' => []]);
|
||||
|
||||
$body = $this->authJson('GET', '/api/v1/service-item/' . $item->getUuid(), $owner);
|
||||
$this->assertSame([], $body['data']['consumables']);
|
||||
}
|
||||
|
||||
public function testConsumableOfAnotherTenantIsRejected(): 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();
|
||||
$foreignItem = $this->makeInventoryItem($other, 'کالای غریبه');
|
||||
|
||||
$this->authJson('PATCH', '/api/v1/service-item/' . $item->getUuid(), $owner, [
|
||||
'consumables' => [['item_uuid' => $foreignItem->getUuid(), 'amount' => 1]],
|
||||
]);
|
||||
|
||||
$this->assertSame(422, $this->responseCode());
|
||||
}
|
||||
|
||||
public function testConsumablesCanBeSetAtCreationTime(): void
|
||||
{
|
||||
[$owner, $doctor, $section] = $this->makeDoctorWithSection();
|
||||
$gauze = $this->makeInventoryItem($doctor, 'گاز استریل');
|
||||
|
||||
$created = $this->authJson('POST', '/api/v1/service-item', $owner, [
|
||||
'section_uuid' => $section->getUuid(),
|
||||
'name' => 'پانسمان',
|
||||
'price_rials' => 200_000,
|
||||
'consumables' => [['item_uuid' => $gauze->getUuid(), 'amount' => 3]],
|
||||
]);
|
||||
|
||||
$this->assertSame(201, $this->responseCode());
|
||||
$this->assertCount(1, $created['data']['consumables']);
|
||||
$this->assertSame(3, $created['data']['consumables'][0]['amount']);
|
||||
}
|
||||
|
||||
public function testChangingConsumablesIsLogged(): void
|
||||
{
|
||||
[$owner, $doctor, $section] = $this->makeDoctorWithSection();
|
||||
$gauze = $this->makeInventoryItem($doctor, 'گاز استریل');
|
||||
$item = $this->makeItem($section);
|
||||
|
||||
$this->authJson('PATCH', '/api/v1/service-item/' . $item->getUuid(), $owner, [
|
||||
'consumables' => [['item_uuid' => $gauze->getUuid(), 'amount' => 2]],
|
||||
]);
|
||||
|
||||
$body = $this->authJson('GET', '/api/v1/service-item/' . $item->getUuid() . '/audit-logs', $owner);
|
||||
$byField = array_column($body['data'], null, 'field');
|
||||
|
||||
$this->assertArrayHasKey('consumables', $byField);
|
||||
$this->assertNull($byField['consumables']['old_value']);
|
||||
$this->assertSame('گاز استریل×2', $byField['consumables']['new_value']);
|
||||
}
|
||||
|
||||
// ── لاگ تغییرات ──────────────────────────────────────────────────────────
|
||||
|
||||
public function testCreatingAServiceWritesACreateLog(): void
|
||||
|
||||
Reference in New Issue
Block a user