feat(migrations): update franchise to percentage in tenant_insurances and tenant_service_coverage
- Changed franchise_rials to franchise_percent in tenant_insurances and tenant_service_coverage tables. - Reset old rial values to 0/NULL as they are not convertible to percentage. feat(command): add SeedInsuranceScenarioCommand for seeding insurance data - Implemented a command to seed supplementary insurance contracts, patients, and claims for a specified doctor. - Includes functionality for purging existing scenario data and generating new entries with predefined contracts and patient scenarios.
This commit is contained in:
@@ -30,6 +30,7 @@ class TenantInsuranceService
|
||||
private readonly ServiceItemRepository $serviceItemRepo,
|
||||
private readonly TenantInsuranceCategoryCoverageRepository $categoryCoverageRepo,
|
||||
private readonly InsuranceCoverageDefaultService $coverageDefaults,
|
||||
private readonly TenantServiceCategoryService $serviceCategories,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -41,7 +42,7 @@ class TenantInsuranceService
|
||||
int $entityId,
|
||||
int $insuranceId,
|
||||
float $coveragePercent,
|
||||
int $franchiseRials = 0,
|
||||
float $franchisePercent = 0.0,
|
||||
?int $annualCeilingRials = null,
|
||||
?int $effectiveFrom = null,
|
||||
?int $effectiveTo = null,
|
||||
@@ -58,8 +59,10 @@ class TenantInsuranceService
|
||||
$contract = new TenantInsurance($entityType, $entityId, $insuranceId, $version);
|
||||
}
|
||||
|
||||
$this->assertPercentInRange($franchisePercent, 'فرانشیز باید بین ۰ تا ۱۰۰ باشد', 'franchise_percent');
|
||||
|
||||
$contract->setCoveragePercent($coveragePercent)
|
||||
->setFranchiseRials($franchiseRials)
|
||||
->setFranchisePercent($franchisePercent)
|
||||
->setAnnualCeilingRials($annualCeilingRials)
|
||||
// kind defaults to the catalog type; caller may override to categorise the contract.
|
||||
->setKind($kind ?? $insurance->getType()->value)
|
||||
@@ -85,13 +88,18 @@ class TenantInsuranceService
|
||||
|
||||
/**
|
||||
* Replaces the contract's category overrides. A row whose percentage is null is
|
||||
* dropped, which hands that category back to the central admin default.
|
||||
* dropped, which hands that category back to the central admin default — but only
|
||||
* for a service kind the tenant does not cover; an enabled kind must carry a
|
||||
* percentage, otherwise the contract would silently bill it at zero.
|
||||
*
|
||||
* @param list<array{key?: string, coverage_percent?: mixed}> $rows
|
||||
* @throws AppException on an unknown category or an out-of-range percentage
|
||||
* @throws AppException on an unknown category, an out-of-range percentage, or a
|
||||
* priced-out enabled service kind
|
||||
*/
|
||||
public function setCategoryCoverages(TenantInsurance $contract, array $rows): void
|
||||
{
|
||||
$this->assertEnabledCategoriesArePriced($contract, $rows);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$category = ServiceCategory::tryFromValue(isset($row['key']) ? (string) $row['key'] : null);
|
||||
if ($category === null) {
|
||||
@@ -113,9 +121,7 @@ class TenantInsuranceService
|
||||
}
|
||||
|
||||
$percent = (float) $raw;
|
||||
if ($percent < 0 || $percent > 100) {
|
||||
throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'درصد پوشش باید بین ۰ تا ۱۰۰ باشد', 422);
|
||||
}
|
||||
$this->assertPercentInRange($percent, 'درصد پوشش باید بین ۰ تا ۱۰۰ باشد');
|
||||
|
||||
$entity = $existing ?? new TenantInsuranceCategoryCoverage($contract->getId(), $category);
|
||||
$this->categoryCoverageRepo->save($entity->setCoveragePercent($percent), false);
|
||||
@@ -124,6 +130,58 @@ class TenantInsuranceService
|
||||
$this->categoryCoverageRepo->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* هر نوع خدمتی که tenant آن را بیمهای کرده باید در پایانِ این ذخیرهسازی درصد
|
||||
* پوشش مؤثر داشته باشد — از خودِ payload، از override قبلی، یا از پیشفرض مرکزی
|
||||
* ادمین. fallback زنده حفظ میشود؛ چیزی که رد میشود قراردادی است که نوع خدمتِ
|
||||
* فعال را عملاً صفر درصد میکند.
|
||||
*
|
||||
* نیامدنِ کلید `category_coverages` اصلاً به اینجا نمیرسد — آن حالت یعنی
|
||||
* «قرارداد دستنخورده روی همان مسیر resolve بماند».
|
||||
*
|
||||
* @param list<array{key?: string, coverage_percent?: mixed}> $rows
|
||||
* @throws AppException وقتی نوع خدمتِ فعالی بدون درصد مؤثر بماند
|
||||
*/
|
||||
private function assertEnabledCategoriesArePriced(TenantInsurance $contract, array $rows): void
|
||||
{
|
||||
$sent = [];
|
||||
foreach ($rows as $row) {
|
||||
$sent[(string) ($row['key'] ?? '')] = $row['coverage_percent'] ?? null;
|
||||
}
|
||||
|
||||
$overrides = $this->categoryCoverageRepo->percentMapFor($contract->getId());
|
||||
$defaults = $this->coverageDefaults->percentMap($contract->getInsuranceId());
|
||||
|
||||
foreach ($this->serviceCategories->enabledKeys($contract->getEntityType(), $contract->getEntityId()) as $key) {
|
||||
// ارسال صریحِ null یعنی «override را بردار»، پس نباید خودِ همان override
|
||||
// که همین حالا حذف میشود، اعتبارسنجی را نجات بدهد.
|
||||
// ستون قدیمیِ coverage_percent قرارداد عمداً fallback حساب نمیشود: با آن،
|
||||
// نوع خدمتی که درصدش نیامده بیصدا نرخ نوع دیگر را ارث میبرد.
|
||||
$percent = match (true) {
|
||||
array_key_exists($key, $sent) && $sent[$key] !== null && $sent[$key] !== '' => (float) $sent[$key],
|
||||
array_key_exists($key, $sent) => $defaults[$key] ?? 0.0,
|
||||
default => $overrides[$key] ?? $defaults[$key] ?? 0.0,
|
||||
};
|
||||
|
||||
if ($percent <= 0) {
|
||||
throw new AppException(
|
||||
ErrorCodes::ERR_VALIDATION_001,
|
||||
sprintf('درصد پوشش %s الزامی است', ServiceCategory::from($key)->label()),
|
||||
422,
|
||||
'category_coverages',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @throws AppException وقتی درصد بیرون از بازهٔ ۰ تا ۱۰۰ باشد */
|
||||
private function assertPercentInRange(float $percent, string $message, ?string $field = null): void
|
||||
{
|
||||
if ($percent < 0 || $percent > 100) {
|
||||
throw new AppException(ErrorCodes::ERR_VALIDATION_001, $message, 422, $field);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Effective percentage per category plus where each value came from, so the panel
|
||||
* can tell an explicit override apart from an inherited central default.
|
||||
@@ -277,14 +335,14 @@ class TenantInsuranceService
|
||||
?TenantServiceCoverage $override,
|
||||
): CoverageRule {
|
||||
$franchise = $this->isSupplementary($contract)
|
||||
? ($override?->getFranchiseRials() ?? $contract->getFranchiseRials())
|
||||
: 0;
|
||||
? ($override?->getFranchisePercent() ?? $contract->getFranchisePercent())
|
||||
: 0.0;
|
||||
|
||||
return new CoverageRule(
|
||||
coveragePercent: $this->resolvePercent($contract, $category, $override),
|
||||
franchiseRials: $franchise,
|
||||
ceilingRials: $override?->getCeilingRials() ?? $contract->getAnnualCeilingRials(),
|
||||
covered: true,
|
||||
coveragePercent: $this->resolvePercent($contract, $category, $override),
|
||||
franchisePercent: $franchise,
|
||||
ceilingRials: $override?->getCeilingRials() ?? $contract->getAnnualCeilingRials(),
|
||||
covered: true,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -324,7 +382,7 @@ class TenantInsuranceService
|
||||
return $kind === InsuranceType::Supplementary->value;
|
||||
}
|
||||
|
||||
/** @return array{covered: bool, percent: float|null, franchise: int|null, ceiling: int|null}|null */
|
||||
/** @return array{covered: bool, coverage_percent: float|null, franchise_percent: float|null, ceiling_rials: int|null}|null */
|
||||
public function getServiceCoverage(int $tenantInsuranceId, int $serviceItemId): ?array
|
||||
{
|
||||
$override = $this->coverageRepo->findOneFor($tenantInsuranceId, $serviceItemId);
|
||||
@@ -336,15 +394,22 @@ class TenantInsuranceService
|
||||
int $serviceItemId,
|
||||
bool $covered,
|
||||
?float $coveragePercent,
|
||||
?int $franchiseRials,
|
||||
?float $franchisePercent,
|
||||
?int $ceilingRials,
|
||||
): void {
|
||||
if ($coveragePercent !== null) {
|
||||
$this->assertPercentInRange($coveragePercent, 'درصد پوشش باید بین ۰ تا ۱۰۰ باشد', 'coverage_percent');
|
||||
}
|
||||
if ($franchisePercent !== null) {
|
||||
$this->assertPercentInRange($franchisePercent, 'فرانشیز باید بین ۰ تا ۱۰۰ باشد', 'franchise_percent');
|
||||
}
|
||||
|
||||
$override = $this->coverageRepo->findOneFor($contract->getId(), $serviceItemId)
|
||||
?? new TenantServiceCoverage($contract->getId(), $serviceItemId);
|
||||
|
||||
$override->setCovered($covered)
|
||||
->setCoveragePercent($coveragePercent)
|
||||
->setFranchiseRials($franchiseRials)
|
||||
->setFranchisePercent($franchisePercent)
|
||||
->setCeilingRials($ceilingRials);
|
||||
|
||||
$this->coverageRepo->save($override);
|
||||
|
||||
Reference in New Issue
Block a user