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:
hamed
2026-07-18 22:56:46 +03:30
parent 466b649988
commit b3a5cda808
16 changed files with 842 additions and 110 deletions
+8 -28
View File
@@ -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();
}
}