feat(insurance): redesign insurance management page to match Figma

Rebuild the /admin/insurance-pricing contracts UI to the Figma "مدیریت بیمه"
design and inject the coverage/franchise/ceiling fields the design omitted.

Backend:
- Add contract-level `kind` column to TenantInsurance (basic|supplementary),
  defaulting to the catalog type; migration Version20260715093358.
- POST/PATCH /billing/tenant-insurances now accept effective_from,
  effective_to, kind; PATCH also toggles is_active without clobbering the
  user-set effective_to (unlike DELETE/deactivate).
- List returns the latest version of every insurance (active + inactive) via
  TenantInsuranceRepository::findLatestByTenant, for the فعال/غیرفعال toggle.

Frontend:
- New InsuranceModal (ui/Modal + SearchableSelect + PersianDateInput) with the
  seven fields; submit "ثبت بیمه".
- TenantInsuranceContracts rebuilt: header + search box, desktop table
  (ردیف/نام/کد/نوع/وضعیت/عملیات) and mobile cards, status toggle -> PATCH.
- utils: isoToUnix/unixToIso helpers for contract dates.

Tests: TenantInsuranceContractApiTest (create/edit/toggle/list, 5 cases),
InsuranceModal + TenantInsuranceContracts vitest suites, docs/api updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-07-15 13:23:38 +03:30
co-authored by Claude Opus 4.8
parent e94f832c8a
commit d7cb9ea5a3
12 changed files with 741 additions and 167 deletions
+8
View File
@@ -47,6 +47,10 @@ class TenantInsurance
#[ORM\Column(name: 'annual_ceiling_rials', type: 'integer', nullable: true)]
private ?int $annualCeilingRials = null;
/** Contract-level insurance kind ('basic'|'supplementary'); overrides the catalog type when set. */
#[ORM\Column(name: 'kind', type: 'string', length: 20, nullable: true)]
private ?string $kind = null;
#[ORM\Column(name: 'effective_from', type: 'integer')]
private int $effectiveFrom;
@@ -81,6 +85,7 @@ class TenantInsurance
public function getCoveragePercent(): float { return (float) $this->coveragePercent; }
public function getFranchiseRials(): int { return $this->franchiseRials; }
public function getAnnualCeilingRials(): ?int { return $this->annualCeilingRials; }
public function getKind(): ?string { return $this->kind; }
public function getEffectiveFrom(): int { return $this->effectiveFrom; }
public function getEffectiveTo(): ?int { return $this->effectiveTo; }
@@ -88,6 +93,8 @@ class TenantInsurance
public function setCoveragePercent(float $v): self { $this->coveragePercent = (string) $v; $this->updatedAt = time(); return $this; }
public function setFranchiseRials(int $v): self { $this->franchiseRials = $v; $this->updatedAt = time(); return $this; }
public function setAnnualCeilingRials(?int $v): self { $this->annualCeilingRials = $v; $this->updatedAt = time(); return $this; }
public function setKind(?string $v): self { $this->kind = $v; $this->updatedAt = time(); return $this; }
public function setEffectiveFrom(int $v): self { $this->effectiveFrom = $v; $this->updatedAt = time(); return $this; }
public function setEffectiveTo(?int $v): self { $this->effectiveTo = $v; $this->updatedAt = time(); return $this; }
public function toArray(): array
@@ -102,6 +109,7 @@ class TenantInsurance
'coverage_percent' => (float) $this->coveragePercent,
'franchise_rials' => $this->franchiseRials,
'annual_ceiling_rials' => $this->annualCeilingRials,
'kind' => $this->kind,
'effective_from' => $this->effectiveFrom,
'effective_to' => $this->effectiveTo,
];