feat: integrate insurance coverage management for clinic services
- Updated NewSessionPage to calculate patient share based on insurance coverage rules. - Refactored billing calculations to utilize new patientShareOf function for service items. - Enhanced API documentation to reflect changes in service coverage structure. - Implemented ServiceInsuranceModal for managing insurance coverage per service. - Added UI components for displaying and editing insurance coverage details. - Removed obsolete toggle switch styles and adjusted CSS for new components. - Ensured backend endpoints support both service_item_id and service_item_uuid for flexibility.
This commit is contained in:
@@ -84,6 +84,9 @@ class ServiceItem
|
||||
'section_uuid' => $this->section->getUuid(),
|
||||
'staff_uuid' => $this->staff?->getUuid(),
|
||||
'staff_name' => $this->staff?->getFullName(),
|
||||
'staff' => $this->staff !== null
|
||||
? ['uuid' => $this->staff->getUuid(), 'full_name' => $this->staff->getFullName()]
|
||||
: null,
|
||||
'name' => $this->name,
|
||||
'price_rials' => $this->priceRials,
|
||||
'active' => $this->active,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Insurance\Controller;
|
||||
|
||||
use App\Auth\Entity\User;
|
||||
use App\ClinicService\Repository\ServiceItemRepository;
|
||||
use App\Clinic\Repository\ClinicRepository;
|
||||
use App\Doctor\Repository\DoctorRepository;
|
||||
use App\Insurance\Entity\DoctorInsurance;
|
||||
@@ -39,6 +40,7 @@ class InsuranceController extends BaseController
|
||||
private readonly TenantInsuranceRepository $tenantInsuranceRepo,
|
||||
private readonly TenantServiceCoverageRepository $serviceCoverageRepo,
|
||||
private readonly TenantInsuranceService $tenantInsuranceService,
|
||||
private readonly ServiceItemRepository $serviceItemRepo,
|
||||
private readonly FileValidatorService $fileValidator,
|
||||
private readonly string $projectDir,
|
||||
) {}
|
||||
@@ -403,7 +405,14 @@ class InsuranceController extends BaseController
|
||||
|
||||
$rows = $this->serviceCoverageRepo->findByContract($contract->getId());
|
||||
|
||||
return $this->success(['data' => array_map(fn($r) => $r->toArray(), $rows)]);
|
||||
$data = array_map(function ($r) {
|
||||
$row = $r->toArray();
|
||||
$item = $this->serviceItemRepo->find($r->getServiceItemId());
|
||||
$row['service_item_uuid'] = $item?->getUuid();
|
||||
return $row;
|
||||
}, $rows);
|
||||
|
||||
return $this->success(['data' => $data]);
|
||||
}
|
||||
|
||||
#[Route('/api/v1/billing/tenant-insurances/{uuid}/service-coverage', methods: ['PUT'])]
|
||||
@@ -416,12 +425,23 @@ class InsuranceController extends BaseController
|
||||
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'قرارداد یافت نشد', 404);
|
||||
}
|
||||
|
||||
$data = json_decode($request->getContent(), true) ?? [];
|
||||
$serviceItemId = isset($data['service_item_id']) ? (int) $data['service_item_id'] : 0;
|
||||
if ($serviceItemId <= 0) {
|
||||
return $this->error(ErrorCodes::ERR_VALIDATION_001, 'service_item_id الزامی است', 422);
|
||||
$data = json_decode($request->getContent(), true) ?? [];
|
||||
|
||||
$serviceItem = isset($data['service_item_uuid'])
|
||||
? $this->serviceItemRepo->findByUuid((string) $data['service_item_uuid'])
|
||||
: (isset($data['service_item_id']) ? $this->serviceItemRepo->find((int) $data['service_item_id']) : null);
|
||||
|
||||
if ($serviceItem === null) {
|
||||
return $this->error(ErrorCodes::ERR_VALIDATION_001, 'سرویس یافت نشد', 422);
|
||||
}
|
||||
|
||||
$section = $serviceItem->getSection();
|
||||
if ($section->getEntityType() !== $entityType || $section->getEntityId() !== $entityId) {
|
||||
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'این سرویس متعلق به شما نیست', 403);
|
||||
}
|
||||
|
||||
$serviceItemId = $serviceItem->getId();
|
||||
|
||||
$this->tenantInsuranceService->setServiceCoverage(
|
||||
$contract,
|
||||
$serviceItemId,
|
||||
|
||||
@@ -4,8 +4,11 @@ namespace App\Patient\Service;
|
||||
|
||||
use App\Appointment\Entity\Appointment;
|
||||
use App\Auth\Repository\UserRepository;
|
||||
use App\Billing\Service\BillingCalculator;
|
||||
use App\Billing\ValueObject\Money;
|
||||
use App\ClinicService\Repository\ServiceItemRepository;
|
||||
use App\Clinic\Repository\ClinicRepository;
|
||||
use App\Insurance\Service\TenantInsuranceService;
|
||||
use App\Doctor\Repository\DoctorAddressRepository;
|
||||
use App\Patient\Entity\PatientRecord;
|
||||
use App\Patient\Entity\PatientSession;
|
||||
@@ -28,17 +31,46 @@ class PatientService
|
||||
private readonly SubscriptionService $subscriptionService,
|
||||
private readonly DoctorAddressRepository $addressRepo,
|
||||
private readonly ClinicRepository $clinicRepo,
|
||||
private readonly TenantInsuranceService $tenantInsuranceService,
|
||||
private readonly BillingCalculator $billingCalculator,
|
||||
) {}
|
||||
|
||||
public function calculateFinalPrice(int $visitPrice, float $baseDiscount, float $suppDiscount, array $serviceItems): array
|
||||
{
|
||||
$afterBase = $visitPrice * (1 - $baseDiscount / 100);
|
||||
$afterSupp = $afterBase * (1 - $suppDiscount / 100);
|
||||
$servicesTotal = array_sum(array_column($serviceItems, 'price_rials'));
|
||||
/**
|
||||
* محاسبهی سهم بیمار.
|
||||
* ویزیت با درصد تخفیف انتخابشده در فرم؛ هر خدمت با قاعدهی پوشش همان بیمهگر برای همان خدمت
|
||||
* (TenantServiceCoverage از طریق BillingCalculator). خدمتی که آن بیمه را پوشش نمیدهد، کامل بر عهدهی بیمار است.
|
||||
*
|
||||
* @param array<array{item_id: int, price_rials: int}> $serviceItems قیمت کل هر ردیف (با احتساب تعداد)
|
||||
*/
|
||||
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);
|
||||
|
||||
$servicesTotal = 0;
|
||||
$servicesPatient = 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;
|
||||
}
|
||||
|
||||
return [
|
||||
'services_total_rials' => (int) $servicesTotal,
|
||||
'final_price_rials' => (int) round($afterSupp) + (int) $servicesTotal,
|
||||
'services_total_rials' => $servicesTotal,
|
||||
'final_price_rials' => $visitShare + $servicesPatient,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -111,7 +143,7 @@ class PatientService
|
||||
$item = $this->serviceItemRepo->findByUuid($svc['service_item_uuid'] ?? '');
|
||||
if ($item !== null) {
|
||||
$qty = max(1, (int) ($svc['quantity'] ?? 1));
|
||||
$serviceItemsData[] = ['price_rials' => $item->getPriceRials() * $qty];
|
||||
$serviceItemsData[] = ['item_id' => $item->getId(), 'price_rials' => $item->getPriceRials() * $qty];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +151,11 @@ class PatientService
|
||||
$session->getVisitPriceRials(),
|
||||
$session->getBaseInsuranceDiscountPercent(),
|
||||
$session->getSupplementaryDiscountPercent(),
|
||||
$serviceItemsData
|
||||
$serviceItemsData,
|
||||
$entityType,
|
||||
$entityId,
|
||||
$session->getInsuranceBaseId(),
|
||||
$session->getInsuranceSupplementaryId(),
|
||||
);
|
||||
|
||||
$session->setServicesTotalRials($priceCalc['services_total_rials']);
|
||||
|
||||
Reference in New Issue
Block a user