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:
hamed
2026-07-29 13:28:59 +03:30
parent 11b4dcdd34
commit 4f4bce9fe2
31 changed files with 1497 additions and 137 deletions
+41 -1
View File
@@ -169,11 +169,41 @@ class ClaimRepository extends ServiceEntityRepository
'total_approved_rials' => (int) $r['total_approved_rials'],
'total_paid_rials' => (int) $r['total_paid_rials'],
'overall_status' => count($statuses) === 1 ? reset($statuses) : 'mixed',
'insurances' => self::parseInsurances((string) ($r['insurances'] ?? '')),
'last_activity_at' => (int) $r['last_activity_at'],
];
}, $rows);
}
/**
* ردیف‌های GROUP_CONCAT بیمه‌های یک بیمار → آرایهٔ ساخت‌یافته. یک بیمار می‌تواند
* مطالبه زیر چند بیمه داشته باشد، پس ستون «بیمه» یک لیست است نه یک مقدار.
*
* @return list<array{insurance_id: int, insurance_name: string|null, kind: string|null}>
*/
private static function parseInsurances(string $concatenated): array
{
if ($concatenated === '') {
return [];
}
$rows = [];
foreach (explode('~', $concatenated) as $chunk) {
[$id, $name, $kind] = array_pad(explode('|', $chunk), 3, null);
if ($id === null || $id === '') {
continue;
}
$rows[] = [
'insurance_id' => (int) $id,
'insurance_name' => $name === '' ? null : $name,
'kind' => $kind === '' ? null : $kind,
];
}
return $rows;
}
public function countPatientsWithClaims(string $entityType, int $entityId, array $filters): int
{
[$where, $params] = $this->patientAggregateFilters($filters);
@@ -222,12 +252,14 @@ class ClaimRepository extends ServiceEntityRepository
WHERE i3.id IN (SELECT DISTINCT cm3.invoice_id FROM claim_map cm3 WHERE cm3.record_id = pr.id)
), 0) AS total_patient_rials,
GROUP_CONCAT(DISTINCT c.status) AS statuses,
GROUP_CONCAT(DISTINCT CONCAT_WS('|', c.insurance_id, COALESCE(ins.name, ''), c.insurance_kind) SEPARATOR '~') AS insurances,
MAX(c.updated_at) AS last_activity_at
FROM claim_map cm
JOIN claims c ON c.id = cm.claim_id
JOIN invoices inv ON inv.id = cm.invoice_id
JOIN patient_records pr ON pr.id = cm.record_id
JOIN users u ON u.id = pr.user_id
LEFT JOIN insurances ins ON ins.id = c.insurance_id
{$where}
GROUP BY pr.id, u.uuid, pr.uuid, u.real_name, u.mobile_number, u.national_code
SQL;
@@ -250,6 +282,10 @@ class ClaimRepository extends ServiceEntityRepository
$conditions[] = 'c.insurance_id = :insId';
$params['insId'] = (int) $filters['insurance_id'];
}
if (!empty($filters['kind'])) {
$conditions[] = 'c.insurance_kind = :kind';
$params['kind'] = (string) $filters['kind'];
}
if (!empty($filters['from'])) {
$conditions[] = 'c.created_at >= :from';
$params['from'] = (int) $filters['from'];
@@ -270,7 +306,10 @@ class ClaimRepository extends ServiceEntityRepository
: 'c.status <> \'paid\'';
}
if (!empty($filters['search'])) {
$conditions[] = '(u.real_name LIKE :search OR u.mobile_number LIKE :search OR u.national_code LIKE :search)';
// نام بیمه هم جستجو می‌شود: کاربر «آسیا» را می‌نویسد و انتظار دارد بیماران
// همان بیمه بیایند، نه فقط بیماری که اسمش آسیاست.
$conditions[] = '(u.real_name LIKE :search OR u.mobile_number LIKE :search'
. ' OR u.national_code LIKE :search OR ins.name LIKE :search)';
$params['search'] = '%' . trim((string) $filters['search']) . '%';
}
@@ -328,6 +367,7 @@ class ClaimRepository extends ServiceEntityRepository
LEFT JOIN patient_sessions ps ON ps.id = inv.patient_session_id
LEFT JOIN appointments a ON a.id = ps.appointment_id
LEFT JOIN doctors d ON d.id = a.doctor_id
LEFT JOIN insurances ins ON ins.id = c.insurance_id
{$where}
ORDER BY c.created_at DESC, c.id DESC
SQL;