PaymentStep sent the toman amount straight through as amount_rials (and the fixed discount value as discount_value), so a 500,000 toman payment was stored as 5,000,000... no — as 500,000 rial (10x too small). Apply tomanToRial before sending the payment amount and the fixed-discount value; percent discount and rule-based discount are unaffected. Verified stored value is now correct rial. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9.5 KiB
پرکردن خودکار پرونده (Session) هنگام قطعی شدن نوبت: تاریخ/ساعت مراجعه + آیتمهای هزینه + جمع کل
پروژه
clinicpro (backend Symfony + پنل ادمین React). تک-ریپو.
تست: پنل ادمین با
09390039833 / 09390039833. اجرا داخل ddev.
زمینه
وقتی نوبت به وضعیت قطعی/تأیید (confirmed) تغییر میکند، AppointmentController تابع PatientService::autoCreateOnAppointmentConfirm() را صدا میزند تا برای بیمار یک پرونده (PatientSession) بسازد. اما مسیر auto-create فقط new PatientSession($record, $appointment) میسازد و ذخیره میکند — هیچ دادهی هزینه یا زمان مراجعهای ثبت نمیشود. در نتیجه پروندهی ساختهشده از نوبت: session_at = null (به created_at برمیگردد)، visit_price_rials = 0، services_total_rials = 0، final_price_rials = 0 و لیست سرویسها خالی است — درحالیکه خودِ Appointment هم visit_price_rials و هم serviceItems را دارد. این یک باگ است.
مسیر دیگر (ویزارد دستیِ ثبت مراجعه createSession()) همهی اینها را درست پر میکند و الگوی مرجع است.
هدف
مسیر auto-create پرونده از نوبت باید مثل ویزارد، اینها را خودکار و بدون ورود دستی قیمت پر کند:
- تاریخ/ساعت مراجعه (
session_at) از زمان واقعی نوبت (Appointment::getSlotStart()). - آیتمهای هزینهی سرویس بهصورت تفکیکشده: برای هر سرویسِ نوبت یک
SessionServiceبا قیمت snapshot از خود سرویس (ServiceItem::getPriceRials()). - هزینه ویزیت (
visit_price_rials) از نوبت (و اگر نوبت مقدار نداشت، از تنظیم «قیمت ویزیت آزاد»). - جمع کل:
services_total_rials= مجموع خطوط سرویس؛final_price_rials=services_total_rials + visit_price_rials. - نمایش در UI پرونده: تاریخ/ساعت مراجعه بهعنوان اولین اطلاعات + جدول تفکیکشدهی هزینهها (ویزیت + هر سرویس) + جمع کل.
فایلهای مرتبط
| فایل | نقش |
|---|---|
src/Patient/Service/PatientService.php |
autoCreateOnAppointmentConfirm() (L94-135) و autoCreateForEntity() (L119-135) — محل باگ؛ createSession() (L137-237) الگوی مرجع |
src/Patient/Entity/PatientSession.php |
Setterها: setSessionAt() (L204)، setVisitPriceRials() (L190)، setServicesTotalRials() (L193)، setFinalPriceRials() (L194)، addService() (L121) |
src/Patient/Entity/SessionService.php |
خط هزینه؛ constructor قیمت را از ServiceItem::getPriceRials() snapshot میکند (L44-53)؛ getLineTotalRials() (L62) |
src/ClinicService/Entity/ServiceItem.php |
getPriceRials() (L87) — منبع قیمت سرویس |
src/Appointment/Entity/Appointment.php |
getSlotStart()، getVisitPriceRials() (L251)، getServiceItems() (L235، ManyToMany appointment_service_items) |
src/Insurance/Service/VisitPriceRequirementResolver.php |
resolve تنظیم ویزیت (L22-37) — منبع fallback قیمت ویزیت آزاد |
src/Insurance/Entity/EntityInsurancePricing.php |
ردیف free-visit (insurance_id = null): getPatientShareRials() (L54) = قیمت ویزیت آزاد پیشفرض |
assets/admin/components/session/DetailsStep.tsx |
نمایش خلاصه پرونده — الان session_at و جدول تفکیکی ندارد |
docs/api/patient.md، docs/api/appointment.md |
بهروزرسانی مستندات (Standing Rule) |
وضعیت فعلی (باگ)
src/Patient/Service/PatientService.php — auto-create فقط میسازد و ذخیره میکند، بدون هیچ دادهای:
private function autoCreateForEntity(string $entityType, int $entityId, Appointment $appointment, int $createdById): void
{
if (!$this->subscriptionService->hasFeature($entityType, $entityId, 'patient_records')) {
return;
}
$patient = $appointment->getUser();
$record = $this->recordRepo->findByEntityAndUser($entityType, $entityId, $patient);
if ($record === null) {
$record = new PatientRecord($entityType, $entityId, $patient, 'system', $createdById);
$this->recordRepo->save($record);
}
$session = new PatientSession($record, $appointment); // ← هیچچیز دیگر ست نمیشود
$this->sessionRepo->save($session);
}
الگوی مرجع در createSession() (چطور باید پر شود) — خطوط کلیدی:
// visit price
$session->setVisitPriceRials((int) ($data['visit_price_rials'] ?? 0));
// session_at (زمان مراجعه)
if (!empty($data['session_at'])) { $session->setSessionAt((int) $data['session_at']); }
// خطوط سرویس + مجموع
foreach ($data['services'] as $s) {
$item = $this->serviceItemRepo->findByUuid($s['uuid']);
$session->addService(new SessionService($session, $item, $staff, (int)($s['qty'] ?? 1)));
}
$session->setServicesTotalRials($servicesTotal);
$session->setFinalPriceRials($servicesTotal + $visitPrice + ...);
وظایف
۱. پرکردن پروندهی auto-create از روی نوبت (backend)
در autoCreateForEntity() بعد از ساخت $session و قبل از save()، از $appointment پر کن:
$session = new PatientSession($record, $appointment);
// ۱) زمان مراجعه = زمان واقعی نوبت
$session->setSessionAt($appointment->getSlotStart());
// ۲) هزینه ویزیت: از نوبت، fallback به «قیمت ویزیت آزاد» تنظیمات
$visitPrice = $appointment->getVisitPriceRials()
?? $this->resolveFreeVisitPrice($entityType, $entityId);
$session->setVisitPriceRials($visitPrice ?? 0);
// ۳) خطوط سرویس تفکیکشده (قیمت snapshot از خود سرویس)
$servicesTotal = 0;
foreach ($appointment->getServiceItems() as $item) {
$line = new SessionService($session, $item, null, 1); // قیمت از ServiceItem::getPriceRials()
$session->addService($line);
$servicesTotal += $line->getLineTotalRials();
}
// ۴) جمع کل
$session->setServicesTotalRials($servicesTotal);
$session->setFinalPriceRials($servicesTotal + ($visitPrice ?? 0));
$this->sessionRepo->save($session);
resolveFreeVisitPrice($entityType, $entityId): یک helper که ردیف free-visit (insurance_id = null) را برای این doctor/clinic ازpricingRepo->findOneForInsurance(TYPE_DOCTOR|TYPE_CLINIC, $entityId, null)میخواند وgetPatientShareRials()را برمیگرداند (یا null). از منطق موجودVisitPriceRequirementResolverالگو بگیر.- توجه به مسیر دوگانه:
autoCreateOnAppointmentConfirmاین متد را هم برایdoctorو هم (در صورت وجود)clinicصدا میزند، پس ممکن است دو پرونده ساخته شود (یکی برای پزشک، یکی برای کلینیک). این رفتار فعلی است؛ آن را تغییر نده، فقط هر دو را درست پر کن. - قیمت دستی وارد نشود — همیشه از
ServiceItem::getPriceRials()و تنظیم ویزیت خوانده شود.
۲. نمایش تاریخ/ساعت مراجعه + جدول هزینه تفکیکی در UI پرونده (frontend)
در assets/admin/components/session/DetailsStep.tsx:
- اولین اطلاعات: «تاریخ و ساعت مراجعه» از
session_at(شمسی باformatDateTime/formatDate— Unix timestamp صحیح). اگرsession_atخالی بود، ازcreated_at. - جدول هزینهی تفکیکشده: یک ردیف «ویزیت: {formatRial(visit_price_rials)}» وقتی
visit_price_rials > 0، سپس هر خط سرویس از آرایهیservices(service_name+line_total_rials)، و در انتها «جمع کل: {final_price_rials}». ازtoArray()پرونده کهservices،visit_price_rials،services_total_rials،final_price_rials،session_atرا میدهد استفاده کن.
نکات مهم
- تاریخها Unix timestamp صحیح (
setSessionAt(int)), نمایش شمسی باformatDate/formatDateTime. - قیمتها ریالی ذخیره؛ نمایش با
formatRial. تبدیل تومان↔ریال باtomanToRial/rialToToman. - تغییری در
Appointmentیا schema لازم نیست (فقط خواندن)؛ بدون migration مگر بخواهی ستون audit اضافه کنی (لازم نیست). - تست: یک نوبت را از
pendingبهconfirmedببر (PATCH /api/v1/appointment/{uuid}/status) و بررسی کن پروندهی ساختهشدهsession_at،visit_price_rials، خطوط سرویس وfinal_price_rialsدرست دارد (endpointGET /api/v1/patient/{recordUuid}/sessions). - بعد از تغییر،
docs/api/patient.mdرا اگر خروجی session تغییر معنایی کرد بهروز کن. - این پرامپت پیشنیاز منطقی پرامپت
discount-rules-engine.mdاست (تخفیف رویfinal_price_rialsاعمال میشود که اینجا درست میشود).