fix(patient): populate auto-created session from the confirmed appointment
When an appointment is confirmed, the auto-created PatientSession was saved empty: session_at null, visit_price_rials 0, no service line items, totals 0. Now it copies the visit datetime from the appointment slot, the visit price (falling back to the tenant free-visit price), a SessionService line per appointment service (price snapshot from the service), and computes services_total_rials / final_price_rials. Verified end-to-end. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -225,6 +225,8 @@ Book an appointment slot.
|
||||
> **تضمین عدم رزرو دوگانه:** هر سه مسیر رزرو از `AppointmentRepository::bookAtomically()` عبور میکنند و یک قید یکتای دیتابیسی (`active_slot_key`) پشت آن قرار دارد؛ بنابراین حتی در شرایط رقابتی (race) فقط یک نوبتِ زنده روی هر `(doctor, slot_start)` ممکن است و درخواست بازنده `409 SLOT_TAKEN` میگیرد. نوبتهای لغو/منقضی اسلات را آزاد میکنند (کلید `NULL`). علاوه بر این، `bookAtomically` داخل تراکنش یک قفلِ per-doctor (`PESSIMISTIC_WRITE` روی ردیف پزشک) میگیرد؛ چون در **حالت سرویسی** نوبتها طول متغیر و شروعِ متفاوت دارند و قید یکتای `(doctor, slot_start)` تداخلِ بازهایِ دو رزروِ همزمان با شروعِ متفاوت را نمیگیرد. این قفل بررسیِ overlap و insert را نسبت به سایر رزروهای همان پزشک اتمیک میکند.
|
||||
|
||||
> **Auto-add to clinic:** هنگام تأیید نوبت، اگر آدرس نوبت متعلق به یک کلینیک باشد (`DoctorAddress.clinic_id`)، بیمار علاوه بر پروندهی پزشک، به پروندههای آن کلینیک هم اضافه میشود. اگر آدرس کلینیک نداشت ولی دکتر فقط عضو یک کلینیک بود، به همان کلینیک اضافه میشود. هر شاخه مشروط به فعالبودن `patient_records`. جزئیات در `docs/api/patient.md`.
|
||||
>
|
||||
> **Auto-fill session on confirm:** پروندهای که هنگام تأیید نوبت خودکار ساخته میشود، اکنون از خود نوبت پر میشود: `session_at` = زمان واقعی نوبت (`slot_start`)، `visit_price_rials` = هزینه ویزیت نوبت (در نبود آن، «قیمت ویزیت آزاد» تنظیمات)، برای هر سرویسِ نوبت یک ردیف `SessionService` با قیمت snapshot از خود سرویس، و `services_total_rials`/`final_price_rials` محاسبهشده. قبلاً همهی این مقادیر صفر/خالی ثبت میشدند (باگ).
|
||||
|
||||
> **Payer vs patient:** the authenticated user (`user`) is always the payer; the `patient_*` fields describe who the visit is for and are stored separately. **Temporary lock:** the slot is held by the new `pending` booking for **15 minutes** (`expires_at = created_at + 900`). If payment is not completed in time, the booking is moved to `expired` and the slot is freed (see `app:cancel-expired-appointments`). An expired pending booking no longer blocks the slot even before the cron runs.
|
||||
|
||||
|
||||
@@ -131,7 +131,34 @@ class PatientService
|
||||
}
|
||||
|
||||
$session = new PatientSession($record, $appointment);
|
||||
|
||||
// زمان مراجعه = زمان واقعی نوبت.
|
||||
$session->setSessionAt($appointment->getSlotStart());
|
||||
|
||||
// هزینه ویزیت: از نوبت، در نبود آن از «قیمت ویزیت آزاد» تنظیمات همین tenant.
|
||||
$visitPrice = (int) ($appointment->getVisitPriceRials()
|
||||
?? $this->pricingRepo->findOneForInsurance($entityType, $entityId, null)?->getPatientShareRials()
|
||||
?? 0);
|
||||
$session->setVisitPriceRials($visitPrice);
|
||||
|
||||
// خطوط هزینهی سرویس (قیمت snapshot از خود سرویس، بدون ورود دستی).
|
||||
$servicesTotal = 0;
|
||||
$lines = [];
|
||||
foreach ($appointment->getServiceItems() as $item) {
|
||||
$line = new SessionService($session, $item, null, 1);
|
||||
$lines[] = $line;
|
||||
$servicesTotal += $line->getLineTotalRials();
|
||||
}
|
||||
|
||||
$session->setServicesTotalRials($servicesTotal);
|
||||
$session->setFinalPriceRials($servicesTotal + $visitPrice);
|
||||
|
||||
$this->sessionRepo->save($session);
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$this->sessionServiceRepo->save($line);
|
||||
$session->addService($line);
|
||||
}
|
||||
}
|
||||
|
||||
public function createSession(
|
||||
|
||||
Reference in New Issue
Block a user