insuranceRepo->find($insuranceId); if ($insurance === null) { throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, 'بیمه یافت نشد', 404); } $contract = $this->repo->findActiveContract($entityType, $entityId, $insuranceId); if ($contract === null) { $version = $this->repo->latestVersion($entityType, $entityId, $insuranceId) + 1; $contract = new TenantInsurance($entityType, $entityId, $insuranceId, $version); } $contract->setCoveragePercent($coveragePercent) ->setFranchiseRials($franchiseRials) ->setAnnualCeilingRials($annualCeilingRials) // kind defaults to the catalog type; caller may override to categorise the contract. ->setKind($kind ?? $insurance->getType()->value) ->setEffectiveTo($effectiveTo) ->setActive(true); if ($effectiveFrom !== null) { $contract->setEffectiveFrom($effectiveFrom); } $this->repo->save($contract); return $contract; } public function deactivate(TenantInsurance $contract): void { $contract->setActive(false)->setEffectiveTo(time()); $this->repo->save($contract); } /** * بررسی فعال‌بودن یک بیمه برای tenant. در پذیرش/صورتحساب استفاده می‌شود. */ public function assertActive(string $entityType, int $entityId, int $insuranceId): TenantInsurance { $contract = $this->repo->findActiveContract($entityType, $entityId, $insuranceId); if ($contract === null) { throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'این بیمه برای این کلینیک/پزشک فعال نیست', 422); } return $contract; } /** * قانون پوشش یک بیمه برای tenant جاری (برای BillingCalculator). * اگر قرارداد فعالی نباشد، notCovered برمی‌گردد. */ public function coverageRule(string $entityType, int $entityId, ?int $insuranceId): CoverageRule { if ($insuranceId === null) { return CoverageRule::notCovered(); } $contract = $this->repo->findActiveContract($entityType, $entityId, $insuranceId); if ($contract === null) { return CoverageRule::notCovered(); } return new CoverageRule( coveragePercent: $contract->getCoveragePercent(), franchiseRials: $contract->getFranchiseRials(), ceilingRials: $contract->getAnnualCeilingRials(), covered: true, ); } /** * قانون پوشش یک خدمت خاص تحت بیمه‌ی tenant. * اگر override خدمت موجود باشد اعمال می‌شود؛ فیلدهای null از قرارداد ارث می‌برند. */ public function coverageRuleForService(string $entityType, int $entityId, ?int $insuranceId, int $serviceItemId): CoverageRule { if ($insuranceId === null) { return CoverageRule::notCovered(); } // پرچم سرویس gate نهایی است: اگر «این خدمت شامل بیمه می‌شود» غیرفعال باشد، هیچ پوششی محاسبه نمی‌شود. $service = $this->serviceItemRepo->find($serviceItemId); if ($service === null || !$service->isInsuranceCovered()) { return CoverageRule::notCovered(); } $contract = $this->repo->findActiveContract($entityType, $entityId, $insuranceId); if ($contract === null) { return CoverageRule::notCovered(); } $override = $this->coverageRepo->findOneFor($contract->getId(), $serviceItemId); if ($override !== null && !$override->isCovered()) { return CoverageRule::notCovered(); } return new CoverageRule( coveragePercent: $override?->getCoveragePercent() ?? $contract->getCoveragePercent(), franchiseRials: $override?->getFranchiseRials() ?? $contract->getFranchiseRials(), ceilingRials: $override?->getCeilingRials() ?? $contract->getAnnualCeilingRials(), covered: true, ); } /** @return array{covered: bool, percent: float|null, franchise: int|null, ceiling: int|null}|null */ public function getServiceCoverage(int $tenantInsuranceId, int $serviceItemId): ?array { $override = $this->coverageRepo->findOneFor($tenantInsuranceId, $serviceItemId); return $override?->toArray(); } public function setServiceCoverage( TenantInsurance $contract, int $serviceItemId, bool $covered, ?float $coveragePercent, ?int $franchiseRials, ?int $ceilingRials, ): void { $override = $this->coverageRepo->findOneFor($contract->getId(), $serviceItemId) ?? new \App\Insurance\Entity\TenantServiceCoverage($contract->getId(), $serviceItemId); $override->setCovered($covered) ->setCoveragePercent($coveragePercent) ->setFranchiseRials($franchiseRials) ->setCeilingRials($ceilingRials); $this->coverageRepo->save($override); } }