feat: insurance & medical billing system (6 phases)

Multi-tenant insurance contracts, service coverage, versioned tariffs,
invoice calculation, and insurance claims with debt reporting.

- TenantInsurance: per-tenant insurance contracts (coverage/franchise/ceiling,
  versioning, soft-deactivate) + active guard
- ServiceItem.insuranceCovered + TenantServiceCoverage per-service overrides
- Tariff: versioned yearly tariffs with fallback to ServiceItem price
- Billing domain: Money/ShareBreakdown VOs, BillingCalculator (unit-tested),
  Invoice/InvoiceItem aggregate, InvoiceService.createFromSession
- Claim/ClaimItem with state machine (pending->submitted->approved/rejected->paid),
  ClaimService, insurance-debt report
- ClaimSubmitterInterface + ManualClaimSubmitter (future insurance API ready)
- Admin UI: insurance-pricing page, claims page, service tariff modal,
  service insurance toggle; routes + sidebar entries
- Architecture doc + billing/insurance/clinic-services API docs

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-06-23 15:05:24 +03:30
co-authored by Claude Opus 4.8
parent 5b1dfe9b40
commit 89191eee57
54 changed files with 4233 additions and 10 deletions
+12
View File
@@ -36,6 +36,12 @@ class ServiceItem
#[ORM\Column(type: 'boolean')]
private bool $active = true;
#[ORM\Column(name: 'insurance_covered', type: 'boolean')]
private bool $insuranceCovered = false;
#[ORM\Column(name: 'insurance_price_rials', type: 'integer', nullable: true)]
private ?int $insurancePriceRials = null;
#[ORM\Column(name: 'created_at', type: 'integer')]
private int $createdAt;
@@ -59,6 +65,8 @@ class ServiceItem
public function getName(): string { return $this->name; }
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 getCreatedAt(): int { return $this->createdAt; }
public function getUpdatedAt(): int { return $this->updatedAt; }
@@ -66,6 +74,8 @@ class ServiceItem
public function setName(string $name): self { $this->name = $name; $this->updatedAt = time(); return $this; }
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 toArray(): array
{
@@ -77,6 +87,8 @@ class ServiceItem
'name' => $this->name,
'price_rials' => $this->priceRials,
'active' => $this->active,
'insurance_covered' => $this->insuranceCovered,
'insurance_price_rials' => $this->insurancePriceRials,
'created_at' => $this->createdAt,
'updated_at' => $this->updatedAt,
];