feat(claims): add tracking number and status history for claims

- Introduced `tracking_number` field in the `claims` table to store the insurance tracking number.
- Created `claim_status_logs` table to maintain a history of status changes for claims, including who made the change and when.
- Implemented `ClaimStatusLog` entity and repository for managing status log entries.
- Updated `ClaimService` to log transitions and handle tracking numbers during claim submissions.
- Added new API endpoint for fetching claims by patient, including detailed claim history and status logs.
- Enhanced frontend with a new `ClaimPatientDetailPage` to display claims and their status history.
- Added tests to ensure correct aggregation of claims and proper handling of status transitions.
This commit is contained in:
hamed
2026-07-18 23:38:02 +03:30
parent b3a5cda808
commit 20bdc49e89
15 changed files with 1412 additions and 309 deletions
+34 -4
View File
@@ -6,7 +6,10 @@ use App\Billing\Contract\ClaimSubmitterInterface;
use App\Billing\Entity\Claim;
use App\Billing\Entity\ClaimItem;
use App\Billing\Entity\Invoice;
use App\Billing\Entity\ClaimStatusLog;
use App\Billing\Repository\ClaimRepository;
use App\Billing\Repository\ClaimStatusLogRepository;
use App\Auth\Entity\User;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Exception\AppException;
@@ -15,6 +18,7 @@ class ClaimService
public function __construct(
private readonly ClaimRepository $claimRepo,
private readonly ClaimSubmitterInterface $submitter,
private readonly ClaimStatusLogRepository $statusLogRepo,
) {}
/**
@@ -59,6 +63,12 @@ class ClaimService
}
$this->claimRepo->getEntityManager()->flush();
// بعد از flush تا id مطالبه موجود باشد.
foreach ($claims as $claim) {
$this->logTransition($claim, null, $claim->getStatus(), 'ایجاد مطالبه', null);
}
$this->claimRepo->getEntityManager()->flush();
return $claims;
}
@@ -81,7 +91,7 @@ class ClaimService
return $hasShare ? $claim : null;
}
public function transition(Claim $claim, string $target, array $opts = []): void
public function transition(Claim $claim, string $target, array $opts = [], ?User $actor = null): void
{
if (!$claim->canTransitionTo($target)) {
throw new AppException(
@@ -91,23 +101,43 @@ class ClaimService
);
}
if ($target === Claim::STATUS_REJECTED && trim((string) ($opts['reason'] ?? '')) === '') {
throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'دلیل رد الزامی است', 422, 'reason');
}
$from = $claim->getStatus();
match ($target) {
Claim::STATUS_SUBMITTED => $this->doSubmit($claim),
Claim::STATUS_SUBMITTED => $this->doSubmit($claim, $opts['tracking_number'] ?? null),
Claim::STATUS_APPROVED => $claim->approve($opts['approved_rials'] ?? null),
Claim::STATUS_REJECTED => $claim->reject($opts['reason'] ?? ''),
Claim::STATUS_PAID => $claim->pay($opts['paid_rials'] ?? null),
default => throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'وضعیت نامعتبر', 422),
};
$this->claimRepo->save($claim);
$this->claimRepo->save($claim, false);
$this->logTransition($claim, $from, $target, $opts['note'] ?? $opts['reason'] ?? null, $actor);
$this->claimRepo->getEntityManager()->flush();
}
private function doSubmit(Claim $claim): void
private function logTransition(Claim $claim, ?string $from, string $to, ?string $note, ?User $actor): void
{
$log = (new ClaimStatusLog((int) $claim->getId(), $from, $to))
->setNote($note)
->setActor($actor?->getId(), $actor?->getRealName() ?? $actor?->getMobileNumber());
$this->statusLogRepo->save($log, false);
}
private function doSubmit(Claim $claim, ?string $trackingNumber): void
{
$result = $this->submitter->submit($claim);
if (!$result->success) {
throw new AppException(ErrorCodes::ERR_VALIDATION_001, $result->errorMessage ?? 'ارسال مطالبه ناموفق بود', 422);
}
$claim->submit();
if ($trackingNumber !== null) {
$claim->setTrackingNumber((string) $trackingNumber);
}
}
}