feat: port tauri create-service page as 3-step new-session wizard
Backend: - Add session_at and inventory_package_id to patient_sessions, new session_consumables table (migration Version20260716102537) - New SessionConsumable entity/repository mirroring SessionService; price snapshot, quantity >= 1, tenant-scoped silent skip - PatientService::createSession accepts session_at, consumables[] and inventory_package_uuid; consumables are fully patient-paid (no insurance coverage) and added to final_price_rials - Functional tests: success, foreign-tenant/unknown skip, empty and zero-quantity edges (tests/Patient/SessionConsumableTest.php) - docs/api/patient.md updated for the new Create Session fields Frontend (admin): - NewSessionPage rewritten as the tauri /files/create-service 3-step wizard (ایجاد سرویس ← پرداخت ← جزییات) using SessionStepper - New CreateStep: acceptance date/time (Jalali), section/service/staff, consumables with counters, package select, conditional insurance block (insured service or insured patient profile), price summary - PaymentStep/DetailsStep extracted from SessionPaymentPage and shared between both pages (behavior unchanged, tests still green) - UserTick and FilesServiceAddCard icons ported verbatim from tauri - Vitest coverage for the wizard incl. empty-data states Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,14 +9,18 @@ use App\Billing\ValueObject\Money;
|
||||
use App\ClinicService\Repository\ServiceItemRepository;
|
||||
use App\Clinic\Repository\ClinicRepository;
|
||||
use App\Insurance\Service\TenantInsuranceService;
|
||||
use App\Inventory\Repository\InventoryItemRepository;
|
||||
use App\Inventory\Repository\InventoryPackageRepository;
|
||||
use App\Doctor\Repository\DoctorAddressRepository;
|
||||
use App\Auth\Entity\User;
|
||||
use App\Patient\Entity\PatientRecord;
|
||||
use App\Patient\Entity\PatientSession;
|
||||
use App\Patient\Entity\SessionConsumable;
|
||||
use App\Patient\Entity\SessionPayment;
|
||||
use App\Patient\Entity\SessionService;
|
||||
use App\Patient\Repository\PatientRecordRepository;
|
||||
use App\Patient\Repository\PatientSessionRepository;
|
||||
use App\Patient\Repository\SessionConsumableRepository;
|
||||
use App\Patient\Repository\SessionPaymentRepository;
|
||||
use App\Patient\Repository\SessionServiceRepository;
|
||||
use App\Settlement\Service\WalletService;
|
||||
@@ -32,7 +36,10 @@ class PatientService
|
||||
private readonly PatientSessionRepository $sessionRepo,
|
||||
private readonly SessionServiceRepository $sessionServiceRepo,
|
||||
private readonly SessionPaymentRepository $sessionPaymentRepo,
|
||||
private readonly SessionConsumableRepository $sessionConsumableRepo,
|
||||
private readonly ServiceItemRepository $serviceItemRepo,
|
||||
private readonly InventoryItemRepository $inventoryItemRepo,
|
||||
private readonly InventoryPackageRepository $inventoryPackageRepo,
|
||||
private readonly ClinicStaffRepository $staffRepo,
|
||||
private readonly UserRepository $userRepo,
|
||||
private readonly SubscriptionService $subscriptionService,
|
||||
@@ -145,6 +152,19 @@ class PatientService
|
||||
$session->setPaymentMethod($data['payment_method'] ?? 'pending');
|
||||
$session->setNotes($data['notes'] ?? null);
|
||||
|
||||
// زمان پذیرش (اختیاری — پیشفرض زمان ثبت)
|
||||
if (!empty($data['session_at'])) {
|
||||
$session->setSessionAt((int) $data['session_at']);
|
||||
}
|
||||
|
||||
// پکیج مصرفی (اختیاری، فقط مرجع؛ باید متعلق به همین tenant باشد)
|
||||
if (!empty($data['inventory_package_uuid'])) {
|
||||
$package = $this->inventoryPackageRepo->findByUuid((string) $data['inventory_package_uuid']);
|
||||
if ($package !== null && $package->getEntityType() === $entityType && $package->getEntityId() === $entityId) {
|
||||
$session->setInventoryPackage($package);
|
||||
}
|
||||
}
|
||||
|
||||
// جمعآوری service items (با احتساب تعداد)
|
||||
$serviceItemsData = [];
|
||||
foreach (($data['services'] ?? []) as $svc) {
|
||||
@@ -167,10 +187,32 @@ class PatientService
|
||||
);
|
||||
|
||||
$session->setServicesTotalRials($priceCalc['services_total_rials']);
|
||||
$session->setFinalPriceRials($priceCalc['final_price_rials']);
|
||||
|
||||
// کالاهای مصرفی: بدون پوشش بیمه — تمام مبلغ سهم بیمار است.
|
||||
// فقط آیتمهای متعلق به همین tenant پذیرفته میشوند؛ بقیه بیصدا رد میشوند (همرفتار با services).
|
||||
$consumableRows = [];
|
||||
$consumablesTotal = 0;
|
||||
foreach (($data['consumables'] ?? []) as $row) {
|
||||
$item = $this->inventoryItemRepo->findByUuid((string) ($row['inventory_item_uuid'] ?? ''));
|
||||
if ($item === null || $item->getEntityType() !== $entityType || $item->getEntityId() !== $entityId) {
|
||||
continue;
|
||||
}
|
||||
$qty = max(1, (int) ($row['quantity'] ?? 1));
|
||||
$consumableRows[] = ['item' => $item, 'quantity' => $qty];
|
||||
$consumablesTotal += $item->getPrice() * $qty;
|
||||
}
|
||||
|
||||
$session->setFinalPriceRials($priceCalc['final_price_rials'] + $consumablesTotal);
|
||||
|
||||
$this->sessionRepo->save($session);
|
||||
|
||||
// ثبت session consumables
|
||||
foreach ($consumableRows as $row) {
|
||||
$sc = new SessionConsumable($session, $row['item'], $row['quantity']);
|
||||
$this->sessionConsumableRepo->save($sc);
|
||||
$session->addConsumable($sc);
|
||||
}
|
||||
|
||||
// ثبت session services
|
||||
foreach (($data['services'] ?? []) as $svc) {
|
||||
$item = $this->serviceItemRepo->findByUuid($svc['service_item_uuid'] ?? '');
|
||||
|
||||
Reference in New Issue
Block a user