Refactor insurance share calculation logic in PatientService
- Consolidated the calculation of patient and insurance shares into a single method using BillingCalculator. - Introduced new fields in PatientSession to store breakdown of insurance shares and patient share. - Updated the API responses to include the new fields for consistency across payment, invoice, and claims dashboard. - Added migration to backfill existing sessions with appropriate values for the new fields. - Implemented tests to ensure the correctness of the new logic and verify that the breakdown sums to the gross total. - Redesigned the claims dashboard to provide a more user-friendly overview of patient claims and their statuses.
This commit is contained in:
@@ -9,11 +9,10 @@ use App\Billing\Repository\InvoiceItemRepository;
|
||||
use App\Billing\Repository\InvoiceRepository;
|
||||
use App\Billing\Service\ClaimService;
|
||||
use App\Billing\Service\InvoiceService;
|
||||
use App\Clinic\Repository\ClinicRepository;
|
||||
use App\Doctor\Repository\DoctorRepository;
|
||||
use App\Insurance\Repository\InsuranceRepository;
|
||||
use App\Patient\Repository\PatientRecordRepository;
|
||||
use App\Patient\Repository\PatientSessionRepository;
|
||||
use App\Patient\Security\PatientRecordScopeResolver;
|
||||
use App\Shared\Constant\ErrorCodes;
|
||||
use App\Shared\Controller\BaseController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
@@ -35,11 +34,8 @@ class BillingController extends BaseController
|
||||
private readonly ClaimRepository $claimRepo,
|
||||
private readonly PatientSessionRepository $sessionRepo,
|
||||
private readonly PatientRecordRepository $recordRepo,
|
||||
private readonly DoctorRepository $doctorRepo,
|
||||
private readonly ClinicRepository $clinicRepo,
|
||||
private readonly InsuranceRepository $insuranceRepo,
|
||||
private readonly \App\Secretary\Repository\DoctorSecretaryRepository $secretaryRepo,
|
||||
private readonly \App\Auth\Repository\UserActiveContextRepository $contextRepo,
|
||||
private readonly PatientRecordScopeResolver $scopeResolver,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -351,29 +347,13 @@ class BillingController extends BaseController
|
||||
return $record->getEntityType() === $entityType && $record->getEntityId() === $entityId;
|
||||
}
|
||||
|
||||
/**
|
||||
* محیط صورتحساب همان محیط پرونده است — صورتحساب و مطالبه از دل مراجعه بیرون میآیند.
|
||||
* ترتیب نقشها بهتنهایی کافی نبود: مالک کلینیکی که خودش پزشک هم هست به مطب شخصیاش
|
||||
* نگاشت میشد و صورتحسابهای کلینیک خودش را «یافت نشد» میگرفت.
|
||||
*/
|
||||
private function resolveEntity(User $user): array
|
||||
{
|
||||
if ($user->hasRole('ROLE_DOCTOR')) {
|
||||
$doctor = $this->doctorRepo->findByUser($user);
|
||||
return $doctor !== null ? ['doctor', $doctor->getId()] : ['doctor', null];
|
||||
}
|
||||
if ($user->hasRole('ROLE_CLINIC')) {
|
||||
$clinic = $this->clinicRepo->findByUser($user);
|
||||
return $clinic !== null ? ['clinic', $clinic->getId()] : ['clinic', null];
|
||||
}
|
||||
if ($user->hasRole('ROLE_SECRETARY')) {
|
||||
$dbUuid = $this->contextRepo->findByUser($user)?->getDbUuid();
|
||||
if ($dbUuid !== null) {
|
||||
$clinic = $this->clinicRepo->findByUuid($dbUuid);
|
||||
if ($clinic !== null && $this->secretaryRepo->findActiveBySecretaryForClinic($user, $clinic) !== null) {
|
||||
return ['clinic', $clinic->getId()];
|
||||
}
|
||||
$doctor = $this->doctorRepo->findByUuid($dbUuid);
|
||||
if ($doctor !== null && $this->secretaryRepo->findActiveBySecretaryForDoctor($user, $doctor) !== null) {
|
||||
return ['doctor', $doctor->getId()];
|
||||
}
|
||||
}
|
||||
}
|
||||
return ['unknown', null];
|
||||
return $this->scopeResolver->resolve($user)->toLegacyTuple();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,9 +296,6 @@ class ClinicServiceController extends BaseController
|
||||
if (isset($data['insurance_covered'])) {
|
||||
$item->setInsuranceCovered((bool) $data['insurance_covered']);
|
||||
}
|
||||
if (array_key_exists('insurance_price_rials', $data)) {
|
||||
$item->setInsurancePriceRials($data['insurance_price_rials'] !== null ? (int) $data['insurance_price_rials'] : null);
|
||||
}
|
||||
if (array_key_exists('duration_minutes', $data)) {
|
||||
$dm = $data['duration_minutes'];
|
||||
$item->setDurationMinutes(($dm === null || $dm === '') ? null : (int) $dm);
|
||||
@@ -350,9 +347,6 @@ class ClinicServiceController extends BaseController
|
||||
if (isset($data['insurance_covered'])) {
|
||||
$item->setInsuranceCovered((bool) $data['insurance_covered']);
|
||||
}
|
||||
if (array_key_exists('insurance_price_rials', $data)) {
|
||||
$item->setInsurancePriceRials($data['insurance_price_rials'] !== null ? (int) $data['insurance_price_rials'] : null);
|
||||
}
|
||||
if (array_key_exists('duration_minutes', $data)) {
|
||||
$dm = $data['duration_minutes'];
|
||||
$item->setDurationMinutes(($dm === null || $dm === '') ? null : (int) $dm);
|
||||
|
||||
@@ -52,6 +52,10 @@ class ServiceItem
|
||||
#[ORM\Column(name: 'insurance_covered', type: 'boolean')]
|
||||
private bool $insuranceCovered = false;
|
||||
|
||||
/**
|
||||
* @deprecated منبع حقیقتِ پوشش، TenantServiceCoverage است و هیچ محاسبهای این مقدار
|
||||
* را نمیخواند. ستون برای دادهی تاریخی مانده ولی نه نوشته میشود و نه منتشر.
|
||||
*/
|
||||
#[ORM\Column(name: 'insurance_price_rials', type: 'integer', nullable: true)]
|
||||
private ?int $insurancePriceRials = null;
|
||||
|
||||
@@ -148,7 +152,6 @@ class ServiceItem
|
||||
public function getPriceRials(): int { return $this->priceRials; }
|
||||
public function isActive(): bool { return $this->active; }
|
||||
public function isInsuranceCovered(): bool { return $this->insuranceCovered; }
|
||||
public function getInsurancePriceRials(): ?int { return $this->insurancePriceRials; }
|
||||
public function getDurationMinutes(): ?int { return $this->durationMinutes; }
|
||||
public function isBookable(): bool { return $this->bookable; }
|
||||
public function getInventoryPackageId(): ?int { return $this->inventoryPackageId; }
|
||||
@@ -187,7 +190,6 @@ class ServiceItem
|
||||
public function setPriceRials(int $price): self { $this->priceRials = $price; $this->updatedAt = time(); return $this; }
|
||||
public function setActive(bool $active): self { $this->active = $active; $this->updatedAt = time(); return $this; }
|
||||
public function setInsuranceCovered(bool $v): self { $this->insuranceCovered = $v; $this->updatedAt = time(); return $this; }
|
||||
public function setInsurancePriceRials(?int $v): self { $this->insurancePriceRials = $v; $this->updatedAt = time(); return $this; }
|
||||
public function setDurationMinutes(?int $v): self { $this->durationMinutes = $v; $this->updatedAt = time(); return $this; }
|
||||
public function setBookable(bool $v): self { $this->bookable = $v; $this->updatedAt = time(); return $this; }
|
||||
public function setInventoryPackageId(?int $v): self { $this->inventoryPackageId = $v; $this->updatedAt = time(); return $this; }
|
||||
@@ -219,7 +221,6 @@ class ServiceItem
|
||||
'price_rials' => $this->priceRials,
|
||||
'active' => $this->active,
|
||||
'insurance_covered' => $this->insuranceCovered,
|
||||
'insurance_price_rials' => $this->insurancePriceRials,
|
||||
'duration_minutes' => $this->durationMinutes,
|
||||
'bookable' => $this->bookable,
|
||||
'inventory_package_id' => $this->inventoryPackageId,
|
||||
|
||||
@@ -43,6 +43,7 @@ class InsuranceController extends BaseController
|
||||
private readonly ServiceItemRepository $serviceItemRepo,
|
||||
private readonly FileValidatorService $fileValidator,
|
||||
private readonly \App\Clinic\Security\ClinicDoctorPermissionChecker $permChecker,
|
||||
private readonly \App\Patient\Security\PatientRecordScopeResolver $scopeResolver,
|
||||
private readonly string $projectDir,
|
||||
) {}
|
||||
|
||||
@@ -78,17 +79,14 @@ class InsuranceController extends BaseController
|
||||
return ['unknown', null, $this->error(ErrorCodes::ERR_ACCESS_DENIED, 'دسترسی ممنوع', 403)];
|
||||
}
|
||||
|
||||
/**
|
||||
* قرارداد بیمه به همان محیطی تعلق دارد که پروندهها در آن ثبت میشوند، پس همان
|
||||
* رزولوِر مبنا است: مالک کلینیکی که خودش پزشک هم هست باید قراردادهای کلینیکش را
|
||||
* ببیند، نه مطب شخصیاش.
|
||||
*/
|
||||
private function resolveEntity(User $user): array
|
||||
{
|
||||
if ($user->hasRole('ROLE_DOCTOR')) {
|
||||
$doctor = $this->doctorRepo->findByUser($user);
|
||||
return $doctor !== null ? [EntityInsurancePricing::TYPE_DOCTOR, $doctor->getId()] : [EntityInsurancePricing::TYPE_DOCTOR, null];
|
||||
}
|
||||
if ($user->hasRole('ROLE_CLINIC')) {
|
||||
$clinic = $this->clinicRepo->findByUser($user);
|
||||
return $clinic !== null ? [EntityInsurancePricing::TYPE_CLINIC, $clinic->getId()] : [EntityInsurancePricing::TYPE_CLINIC, null];
|
||||
}
|
||||
return ['unknown', null];
|
||||
return $this->scopeResolver->resolve($user)->toLegacyTuple();
|
||||
}
|
||||
|
||||
// ── Public list ───────────────────────────────────────────────────────────
|
||||
|
||||
@@ -58,6 +58,24 @@ class PatientSession
|
||||
#[ORM\Column(name: 'services_total_rials', type: 'integer')]
|
||||
private int $servicesTotalRials = 0;
|
||||
|
||||
/**
|
||||
* تفکیک بیمهی این مراجعه، محاسبهشده توسط PatientService::calculateFinalPrice.
|
||||
* پایاست تا صفحهی پرداخت بدون صدور فاکتور هم سهمها را داشته باشد.
|
||||
* ثابت: gross = baseInsurance + supplementaryInsurance + patientShare
|
||||
*/
|
||||
#[ORM\Column(name: 'gross_total_rials', type: 'integer', options: ['default' => 0])]
|
||||
private int $grossTotalRials = 0;
|
||||
|
||||
#[ORM\Column(name: 'base_insurance_rials', type: 'integer', options: ['default' => 0])]
|
||||
private int $baseInsuranceRials = 0;
|
||||
|
||||
#[ORM\Column(name: 'supplementary_insurance_rials', type: 'integer', options: ['default' => 0])]
|
||||
private int $supplementaryInsuranceRials = 0;
|
||||
|
||||
#[ORM\Column(name: 'patient_share_rials', type: 'integer', options: ['default' => 0])]
|
||||
private int $patientShareRials = 0;
|
||||
|
||||
/** سهم بیمار پیش از تخفیف دستی — همیشه برابر patientShareRials */
|
||||
#[ORM\Column(name: 'final_price_rials', type: 'integer')]
|
||||
private int $finalPriceRials = 0;
|
||||
|
||||
@@ -144,6 +162,10 @@ class PatientSession
|
||||
public function getBaseInsuranceDiscountPercent(): float { return (float) $this->baseInsuranceDiscountPercent; }
|
||||
public function getSupplementaryDiscountPercent(): float { return (float) $this->supplementaryDiscountPercent; }
|
||||
public function getServicesTotalRials(): int { return $this->servicesTotalRials; }
|
||||
public function getGrossTotalRials(): int { return $this->grossTotalRials; }
|
||||
public function getBaseInsuranceRials(): int { return $this->baseInsuranceRials; }
|
||||
public function getSupplementaryInsuranceRials(): int { return $this->supplementaryInsuranceRials; }
|
||||
public function getPatientShareRials(): int { return $this->patientShareRials; }
|
||||
public function getFinalPriceRials(): int { return $this->finalPriceRials; }
|
||||
public function getPaymentMethod(): string { return $this->paymentMethod; }
|
||||
public function getDiscountType(): ?string { return $this->discountType; }
|
||||
@@ -169,10 +191,16 @@ class PatientSession
|
||||
));
|
||||
}
|
||||
|
||||
/** مبلغ قابل پرداخت بیمار: سهم بیمار پس از کسر تخفیف دستی */
|
||||
public function getPayableRials(): int
|
||||
{
|
||||
return max(0, $this->finalPriceRials - $this->discountRials);
|
||||
}
|
||||
|
||||
/** ماندهی بدهی پس از کسر تخفیف و پرداختها؛ هرگز منفی نمیشود */
|
||||
public function getRemainingRials(): int
|
||||
{
|
||||
return max(0, $this->finalPriceRials - $this->discountRials - $this->getPaidTotalRials());
|
||||
return max(0, $this->getPayableRials() - $this->getPaidTotalRials());
|
||||
}
|
||||
|
||||
public function getSessionAt(): ?int { return $this->sessionAt; }
|
||||
@@ -206,7 +234,22 @@ class PatientSession
|
||||
public function setBaseInsuranceDiscountPercent(float $v): self { $this->baseInsuranceDiscountPercent = (string) $v; $this->updatedAt = time(); return $this; }
|
||||
public function setSupplementaryDiscountPercent(float $v): self { $this->supplementaryDiscountPercent = (string) $v; $this->updatedAt = time(); return $this; }
|
||||
public function setServicesTotalRials(int $v): self { $this->servicesTotalRials = $v; $this->updatedAt = time(); return $this; }
|
||||
public function setFinalPriceRials(int $v): self { $this->finalPriceRials = $v; $this->updatedAt = time(); return $this; }
|
||||
public function setFinalPriceRials(int $v): self { $this->finalPriceRials = $v; $this->patientShareRials = $v; $this->updatedAt = time(); return $this; }
|
||||
|
||||
/**
|
||||
* تفکیک بیمه را یکجا مینشاند تا سهمها و مبلغ نهایی نتوانند ناسازگار شوند.
|
||||
* مبلغ نهایی همیشه سهم بیمار است؛ تخفیف دستی جدا و بعد از این اعمال میشود.
|
||||
*/
|
||||
public function applyShares(int $gross, int $baseInsurance, int $supplementaryInsurance, int $patientShare): self
|
||||
{
|
||||
$this->grossTotalRials = $gross;
|
||||
$this->baseInsuranceRials = $baseInsurance;
|
||||
$this->supplementaryInsuranceRials = $supplementaryInsurance;
|
||||
$this->patientShareRials = $patientShare;
|
||||
$this->finalPriceRials = $patientShare;
|
||||
$this->updatedAt = time();
|
||||
return $this;
|
||||
}
|
||||
public function setPaymentMethod(string $v): self { $this->paymentMethod = $v; $this->updatedAt = time(); return $this; }
|
||||
public function setDiscount(?string $type, int $value, int $rials, ?int $ruleId = null, ?string $ruleLabel = null): self
|
||||
{
|
||||
@@ -242,7 +285,12 @@ class PatientSession
|
||||
'base_insurance_discount_percent' => (float) $this->baseInsuranceDiscountPercent,
|
||||
'supplementary_discount_percent' => (float) $this->supplementaryDiscountPercent,
|
||||
'services_total_rials' => $this->servicesTotalRials,
|
||||
'gross_total_rials' => $this->grossTotalRials,
|
||||
'base_insurance_rials' => $this->baseInsuranceRials,
|
||||
'supplementary_insurance_rials' => $this->supplementaryInsuranceRials,
|
||||
'patient_share_rials' => $this->patientShareRials,
|
||||
'final_price_rials' => $this->finalPriceRials,
|
||||
'remaining_rials' => $this->getRemainingRials(),
|
||||
'payment_method' => $this->paymentMethod,
|
||||
'is_paid' => $this->getRemainingRials() === 0,
|
||||
'discount_type' => $this->discountType,
|
||||
|
||||
@@ -86,44 +86,69 @@ class PatientService
|
||||
}
|
||||
|
||||
/**
|
||||
* محاسبهی سهم بیمار.
|
||||
* ویزیت با درصد تخفیف انتخابشده در فرم؛ هر خدمت با قاعدهی پوشش همان بیمهگر برای همان خدمت
|
||||
* (TenantServiceCoverage از طریق BillingCalculator). خدمتی که آن بیمه را پوشش نمیدهد، کامل بر عهدهی بیمار است.
|
||||
* تفکیک سهم بیمهها و سهم بیمار برای یک مراجعه.
|
||||
*
|
||||
* ویزیت با قاعدهی قرارداد بیمه (coverageRule) و هر خدمت با قاعدهی پوشش همان خدمت
|
||||
* (coverageRuleForService) محاسبه میشود — هر دو از طریق BillingCalculator، همان مسیری
|
||||
* که InvoiceService برای صدور فاکتور استفاده میکند. تنها منبع محاسبه همین است تا مبلغ
|
||||
* صفحهی پرداخت و فاکتور نتوانند از هم واگرا شوند.
|
||||
*
|
||||
* @param array<array{item_id: int, price_rials: int}> $serviceItems قیمت کل هر ردیف (با احتساب تعداد)
|
||||
* @return array{services_total_rials: int, gross_total_rials: int, base_insurance_rials: int, supplementary_insurance_rials: int, patient_share_rials: int, final_price_rials: int}
|
||||
*/
|
||||
public function calculateFinalPrice(
|
||||
int $visitPrice,
|
||||
float $baseDiscount,
|
||||
float $suppDiscount,
|
||||
array $serviceItems,
|
||||
string $entityType = 'doctor',
|
||||
int $entityId = 0,
|
||||
?int $baseInsuranceId = null,
|
||||
?int $suppInsuranceId = null,
|
||||
): array {
|
||||
$afterBase = $visitPrice * (1 - $baseDiscount / 100);
|
||||
$afterSupp = $afterBase * (1 - $suppDiscount / 100);
|
||||
$visitShare = (int) round($afterSupp);
|
||||
$baseShare = 0;
|
||||
$suppShare = 0;
|
||||
$patientShare = 0;
|
||||
|
||||
$servicesTotal = 0;
|
||||
$servicesPatient = 0;
|
||||
if ($visitPrice > 0) {
|
||||
$visit = $this->billingCalculator->calculateItem(
|
||||
new Money($visitPrice),
|
||||
$this->tenantInsuranceService->coverageRule($entityType, $entityId, $baseInsuranceId),
|
||||
$this->tenantInsuranceService->coverageRule($entityType, $entityId, $suppInsuranceId),
|
||||
);
|
||||
$baseShare += $visit->baseInsuranceRials;
|
||||
$suppShare += $visit->supplementaryRials;
|
||||
$patientShare += $visit->patientRials;
|
||||
}
|
||||
|
||||
$servicesTotal = 0;
|
||||
foreach ($serviceItems as $svc) {
|
||||
$servicesTotal += $svc['price_rials'];
|
||||
|
||||
$baseRule = $this->tenantInsuranceService->coverageRuleForService($entityType, $entityId, $baseInsuranceId, $svc['item_id']);
|
||||
$suppRule = $this->tenantInsuranceService->coverageRuleForService($entityType, $entityId, $suppInsuranceId, $svc['item_id']);
|
||||
$breakdown = $this->billingCalculator->calculateItem(new Money($svc['price_rials']), $baseRule, $suppRule);
|
||||
|
||||
$servicesPatient += $breakdown->patientRials;
|
||||
$line = $this->billingCalculator->calculateItem(
|
||||
new Money($svc['price_rials']),
|
||||
$this->tenantInsuranceService->coverageRuleForService($entityType, $entityId, $baseInsuranceId, $svc['item_id']),
|
||||
$this->tenantInsuranceService->coverageRuleForService($entityType, $entityId, $suppInsuranceId, $svc['item_id']),
|
||||
);
|
||||
$baseShare += $line->baseInsuranceRials;
|
||||
$suppShare += $line->supplementaryRials;
|
||||
$patientShare += $line->patientRials;
|
||||
}
|
||||
|
||||
return [
|
||||
'services_total_rials' => $servicesTotal,
|
||||
'final_price_rials' => $visitShare + $servicesPatient,
|
||||
'services_total_rials' => $servicesTotal,
|
||||
'gross_total_rials' => $visitPrice + $servicesTotal,
|
||||
'base_insurance_rials' => $baseShare,
|
||||
'supplementary_insurance_rials' => $suppShare,
|
||||
'patient_share_rials' => $patientShare,
|
||||
'final_price_rials' => $patientShare,
|
||||
];
|
||||
}
|
||||
|
||||
/** درصد پوشش قرارداد فعال — snapshot روی مراجعه، نه ورودی محاسبه. */
|
||||
private function contractPercent(string $entityType, int $entityId, ?int $insuranceId): float
|
||||
{
|
||||
return $this->tenantInsuranceService->coverageRule($entityType, $entityId, $insuranceId)->coveragePercent;
|
||||
}
|
||||
|
||||
/**
|
||||
* پرونده و مراجعهٔ خودکار برای یک نوبت قطعیشده.
|
||||
*
|
||||
@@ -190,7 +215,9 @@ class PatientService
|
||||
}
|
||||
|
||||
$session->setServicesTotalRials($servicesTotal);
|
||||
$session->setFinalPriceRials($servicesTotal + $visitPrice);
|
||||
// پذیرش خودکار بیمهای ندارد: تمام مبلغ سهم بیمار است.
|
||||
$gross = $servicesTotal + $visitPrice;
|
||||
$session->applyShares($gross, 0, 0, $gross);
|
||||
|
||||
$this->sessionRepo->save($session);
|
||||
|
||||
@@ -222,8 +249,8 @@ class PatientService
|
||||
$session->setInsuranceBaseId(isset($data['insurance_base_id']) ? (int) $data['insurance_base_id'] : null);
|
||||
$session->setInsuranceSupplementaryId(isset($data['insurance_supplementary_id']) ? (int) $data['insurance_supplementary_id'] : null);
|
||||
$session->setVisitPriceRials((int) ($data['visit_price_rials'] ?? 0));
|
||||
$session->setBaseInsuranceDiscountPercent((float) ($data['base_insurance_discount_percent'] ?? 0));
|
||||
$session->setSupplementaryDiscountPercent((float) ($data['supplementary_discount_percent'] ?? 0));
|
||||
$session->setBaseInsuranceDiscountPercent($this->contractPercent($entityType, $entityId, $session->getInsuranceBaseId()));
|
||||
$session->setSupplementaryDiscountPercent($this->contractPercent($entityType, $entityId, $session->getInsuranceSupplementaryId()));
|
||||
$session->setPaymentMethod($data['payment_method'] ?? 'pending');
|
||||
$session->setNotes($data['notes'] ?? null);
|
||||
|
||||
@@ -252,8 +279,6 @@ class PatientService
|
||||
|
||||
$priceCalc = $this->calculateFinalPrice(
|
||||
$session->getVisitPriceRials(),
|
||||
$session->getBaseInsuranceDiscountPercent(),
|
||||
$session->getSupplementaryDiscountPercent(),
|
||||
$serviceItemsData,
|
||||
$entityType,
|
||||
$entityId,
|
||||
@@ -277,7 +302,13 @@ class PatientService
|
||||
$consumablesTotal += $item->getPrice() * $qty;
|
||||
}
|
||||
|
||||
$session->setFinalPriceRials($priceCalc['final_price_rials'] + $consumablesTotal);
|
||||
// کالاهای مصرفی پوشش بیمه ندارند: مستقیم به سهم بیمار و به جمع کل اضافه میشوند.
|
||||
$session->applyShares(
|
||||
$priceCalc['gross_total_rials'] + $consumablesTotal,
|
||||
$priceCalc['base_insurance_rials'],
|
||||
$priceCalc['supplementary_insurance_rials'],
|
||||
$priceCalc['patient_share_rials'] + $consumablesTotal,
|
||||
);
|
||||
|
||||
$this->sessionRepo->save($session);
|
||||
|
||||
@@ -365,18 +396,25 @@ class PatientService
|
||||
fn(SessionService $s) => ['item_id' => $s->getServiceItem()->getId(), 'price_rials' => $s->getLineTotalRials()],
|
||||
$session->getServices()->toArray(),
|
||||
);
|
||||
$session->setBaseInsuranceDiscountPercent($this->contractPercent($entityType, $entityId, $session->getInsuranceBaseId()));
|
||||
$session->setSupplementaryDiscountPercent($this->contractPercent($entityType, $entityId, $session->getInsuranceSupplementaryId()));
|
||||
|
||||
$priceCalc = $this->calculateFinalPrice(
|
||||
$session->getVisitPriceRials(),
|
||||
$session->getBaseInsuranceDiscountPercent(),
|
||||
$session->getSupplementaryDiscountPercent(),
|
||||
$serviceItemsData,
|
||||
$entityType,
|
||||
$entityId,
|
||||
$session->getInsuranceBaseId(),
|
||||
$session->getInsuranceSupplementaryId(),
|
||||
);
|
||||
$consumablesTotal = $session->getConsumablesTotalRials();
|
||||
$session->setServicesTotalRials($priceCalc['services_total_rials']);
|
||||
$session->setFinalPriceRials($priceCalc['final_price_rials'] + $session->getConsumablesTotalRials());
|
||||
$session->applyShares(
|
||||
$priceCalc['gross_total_rials'] + $consumablesTotal,
|
||||
$priceCalc['base_insurance_rials'],
|
||||
$priceCalc['supplementary_insurance_rials'],
|
||||
$priceCalc['patient_share_rials'] + $consumablesTotal,
|
||||
);
|
||||
$this->sessionRepo->save($session);
|
||||
|
||||
// پس از تغییر مبلغ، وضعیت تسویه بازمحاسبه شود (افزودن سرویس/پکیج → بدهکار).
|
||||
@@ -417,8 +455,7 @@ class PatientService
|
||||
}
|
||||
// مجموع پرداختها (با مقدار جدید) نباید از مبلغِ پس از تخفیف بیشتر شود.
|
||||
$othersTotal = $session->getPaidTotalRials() - $oldAmount;
|
||||
$payable = $session->getFinalPriceRials() - $session->getDiscountRials();
|
||||
if ($othersTotal + $newAmount > $payable) {
|
||||
if ($othersTotal + $newAmount > $session->getPayableRials()) {
|
||||
throw new AppException(ErrorCodes::ERR_SESSION_PAYMENT_EXCEEDS, null, 422, 'amount_rials');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user