fix(session): show and keep the doctor's insurance when editing an encounter
The encounter edit form asked for contracts in the panel's own environment, so inside a clinic it got an empty list: no insurance to choose, and the one already recorded on the encounter had no matching option to display. It now scopes the request by the encounter's doctor, the same way the confirm modal does, which is why the session payload's existing doctor_uuid is threaded through. Saving the form had the matching defect on the server. Recalculating the totals looked up the coverage percentage against the record's tenant rather than the contract's, so a single save on a correctly-billed encounter zeroed the insurance share and moved the whole amount back onto the patient. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,8 @@ export interface SessionCardData {
|
||||
base_insurance_discount_percent?: number;
|
||||
supplementary_discount_percent?: number;
|
||||
doctor_name?: string | null;
|
||||
/** پزشکِ نوبتِ این مراجعه — محدودهٔ قرارداد بیمه از روی او حل میشود. */
|
||||
doctor_uuid?: string | null;
|
||||
final_price_rials?: number;
|
||||
// تفکیک بیمه — سرور محاسبه میکند (PatientSession::applyShares)
|
||||
gross_total_rials?: number;
|
||||
|
||||
@@ -105,11 +105,20 @@ export default function CreateStep({ recordUuid, profile, onCreated, onCancel, e
|
||||
const { data: packagesData } = useQuery<ApiResponse<PackageRow[]>>({
|
||||
queryKey: ['inventory-packages'], queryFn: () => api.get('/api/v1/inventory-packages'),
|
||||
});
|
||||
// مراجعهٔ برخاسته از نوبت، قراردادِ پزشکِ همان نوبت را دارد نه قراردادِ محیط پنل:
|
||||
// در کلینیک، قرارداد روی خودِ پزشک ثبت میشود و بدون این محدوده، فهرست بیمهها
|
||||
// خالی میآمد و بیمهٔ ثبتشدهٔ همان مراجعه هم در فرم پیدا نمیشد.
|
||||
const insuranceScope = editSession?.doctor_uuid
|
||||
? `?doctor_uuid=${encodeURIComponent(editSession.doctor_uuid)}&inherit=1`
|
||||
: '';
|
||||
|
||||
const { data: contractsData } = useQuery<{ data: { data: Contract[] } }>({
|
||||
queryKey: ['tenant-insurances'], queryFn: () => api.get('/api/v1/billing/tenant-insurances'),
|
||||
queryKey: ['tenant-insurances', editSession?.doctor_uuid ?? null],
|
||||
queryFn: () => api.get(`/api/v1/billing/tenant-insurances${insuranceScope}`),
|
||||
});
|
||||
const { data: pricingData } = useQuery<{ data: { free_visit_price_rials: number; require_visit_price: boolean } }>({
|
||||
queryKey: ['insurance-pricing'], queryFn: () => api.get('/api/v1/insurance-pricing'),
|
||||
queryKey: ['insurance-pricing', editSession?.doctor_uuid ?? null],
|
||||
queryFn: () => api.get(`/api/v1/insurance-pricing${insuranceScope}`),
|
||||
});
|
||||
const freeVisit = (pricingData as any)?.data?.free_visit_price_rials ?? 0;
|
||||
const requireVisit = (pricingData as any)?.data?.require_visit_price ?? false;
|
||||
|
||||
@@ -165,6 +165,27 @@ class PatientService
|
||||
->coveragePercent;
|
||||
}
|
||||
|
||||
/**
|
||||
* محیطی که قرارداد بیمهٔ این مراجعه در آن ثبت شده — اول پزشکِ نوبت، بعد کلینیکش.
|
||||
*
|
||||
* مراجعهٔ دستی نوبتی ندارد و پزشکی برای پرسیدن نیست، پس همان محیط پرونده میماند.
|
||||
* {@see \App\Insurance\Service\InsuranceScopeResolver}
|
||||
*
|
||||
* @return array{0: string, 1: int}
|
||||
*/
|
||||
private function coverageScopeOf(PatientSession $session, string $entityType, int $entityId): array
|
||||
{
|
||||
$appointment = $session->getAppointment();
|
||||
if ($appointment === null) {
|
||||
return [$entityType, $entityId];
|
||||
}
|
||||
|
||||
return $this->insuranceScope->forContracts(
|
||||
(int) $appointment->getDoctor()->getId(),
|
||||
$appointment->getClinic()?->getId() !== null ? (int) $appointment->getClinic()->getId() : null,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* سهمهای مراجعهٔ یک نوبت را دوباره حساب میکند — برای وقتی بیمهٔ نوبت *بعد از*
|
||||
* ساختهشدنِ مراجعه عوض میشود.
|
||||
@@ -543,14 +564,17 @@ class PatientService
|
||||
$session->getServices()->toArray(),
|
||||
);
|
||||
$visitCategory = $session->getInsuranceServiceCategory();
|
||||
$session->setBaseInsuranceDiscountPercent($this->contractPercent($entityType, $entityId, $session->getInsuranceBaseId(), $visitCategory));
|
||||
$session->setSupplementaryDiscountPercent($this->contractPercent($entityType, $entityId, $session->getInsuranceSupplementaryId(), $visitCategory));
|
||||
// درصد پوشش از محیطِ قرارداد میآید، نه محیطِ پرونده — همان قاعدهٔ ساخت خودکار.
|
||||
[$coverageType, $coverageId] = $this->coverageScopeOf($session, $entityType, $entityId);
|
||||
|
||||
$session->setBaseInsuranceDiscountPercent($this->contractPercent($coverageType, $coverageId, $session->getInsuranceBaseId(), $visitCategory));
|
||||
$session->setSupplementaryDiscountPercent($this->contractPercent($coverageType, $coverageId, $session->getInsuranceSupplementaryId(), $visitCategory));
|
||||
|
||||
$priceCalc = $this->calculateFinalPrice(
|
||||
$session->getVisitPriceRials(),
|
||||
$serviceItemsData,
|
||||
$entityType,
|
||||
$entityId,
|
||||
$coverageType,
|
||||
$coverageId,
|
||||
$session->getInsuranceBaseId(),
|
||||
$session->getInsuranceSupplementaryId(),
|
||||
$visitCategory,
|
||||
|
||||
@@ -111,6 +111,34 @@ class ClinicAppointmentUsesDoctorContractTest extends ApiTestCase
|
||||
self::assertSame(990_000, $claims[0]->getTotalClaimedRials());
|
||||
}
|
||||
|
||||
/**
|
||||
* ویرایش دستیِ همان مراجعه هم نباید سهم بیمه را دور بیندازد: فرم ویرایش سرویسها
|
||||
* کل مبلغ را دوباره حساب میکند و اگر درصد را از محیط اشتباه بگیرد، مراجعهای که
|
||||
* درست ساخته شده بود با یک ذخیرهٔ ساده خراب میشود.
|
||||
*/
|
||||
public function testEditingTheSessionKeepsTheInsuranceShare(): void
|
||||
{
|
||||
[$owner, $doctor, $clinic, $insurance] = $this->scenario(30);
|
||||
$appointment = $this->appointment($doctor, $clinic, $insurance);
|
||||
|
||||
$session = static::getContainer()->get(PatientService::class)
|
||||
->autoCreateOnAppointmentConfirm($appointment);
|
||||
|
||||
$this->authJson('PATCH', '/api/v1/session/' . $session->getUuid(), $owner, [
|
||||
'visit_price_rials' => self::VISIT_PRICE,
|
||||
]);
|
||||
self::assertSame(200, $this->responseCode());
|
||||
|
||||
// درخواستِ کرنل روی EntityManager دیگری نوشت، پس ردیف دوباره خوانده میشود.
|
||||
$row = $this->em->getConnection()->fetchAssociative(
|
||||
'SELECT base_insurance_rials, patient_share_rials FROM patient_sessions WHERE uuid = ?',
|
||||
[$session->getUuid()],
|
||||
);
|
||||
|
||||
self::assertSame(990_000, (int) $row['base_insurance_rials']);
|
||||
self::assertSame(2_310_000, (int) $row['patient_share_rials']);
|
||||
}
|
||||
|
||||
/** نوبتِ بدون قرارداد، مثل قبل کاملاً سهم بیمار میماند. */
|
||||
public function testAnAppointmentWithoutAnyContractStillBillsThePatientInFull(): void
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user