feat: add payments summary endpoint and UI redesign for MyPaymentsPage
- Implemented a new API endpoint `/api/v1/my/billing/payments/summary` to provide a financial summary of payments with filters for national code, status, and date range. - Updated the InvoiceRepository to aggregate totals for paid and unsettled invoices. - Created a new hook `usePaymentsSummary` to fetch summary data in the frontend. - Redesigned the MyPaymentsPage to align with the ClaimsPage structure, incorporating a design system, summary statistics, and improved filtering options. - Added tests for the new payments summary endpoint to ensure correct functionality and filtering behavior.
This commit is contained in:
@@ -168,18 +168,50 @@ class BillingController extends BaseController
|
||||
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
|
||||
}
|
||||
|
||||
$filters = [
|
||||
$page = max(1, (int) $request->query->get('page', 1));
|
||||
$limit = min(100, max(1, (int) $request->query->get('limit', 20)));
|
||||
|
||||
$result = $this->invoiceService->tenantInvoiceList(
|
||||
$entityType,
|
||||
$entityId,
|
||||
$this->paymentFilters($request),
|
||||
$page,
|
||||
$limit,
|
||||
);
|
||||
|
||||
return $this->paginated($result['items'], $result['total'], $page, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* خلاصهی مالی همان مجموعهی فیلترشدهی listPayments — برای کارتهای آمار.
|
||||
* پاسخ: { total_rials, paid_rials, unsettled_rials, invoices_count }.
|
||||
*/
|
||||
#[Route('/api/v1/my/billing/payments/summary', methods: ['GET'])]
|
||||
public function paymentsSummary(Request $request, #[CurrentUser] User $user): JsonResponse
|
||||
{
|
||||
[$entityType, $entityId] = $this->resolveEntity($user);
|
||||
if ($entityId === null) {
|
||||
return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
|
||||
}
|
||||
|
||||
return $this->success(
|
||||
$this->invoiceService->tenantInvoiceSummary($entityType, $entityId, $this->paymentFilters($request)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* فیلترهای مشترک لیست پرداختها و خلاصهی آن؛ یک منبع تا دو نما واگرا نشوند.
|
||||
*
|
||||
* @return array{national_code:?string,status:?string,from:?string,to:?string}
|
||||
*/
|
||||
private function paymentFilters(Request $request): array
|
||||
{
|
||||
return [
|
||||
'national_code' => $request->query->get('national_code') ?: null,
|
||||
'status' => $request->query->get('status') ?: null,
|
||||
'from' => $request->query->get('from') ?: null,
|
||||
'to' => $request->query->get('to') ?: null,
|
||||
];
|
||||
$page = max(1, (int) $request->query->get('page', 1));
|
||||
$limit = min(100, max(1, (int) $request->query->get('limit', 20)));
|
||||
|
||||
$result = $this->invoiceService->tenantInvoiceList($entityType, $entityId, $filters, $page, $limit);
|
||||
|
||||
return $this->paginated($result['items'], $result['total'], $page, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -67,6 +67,36 @@ class InvoiceRepository extends ServiceEntityRepository
|
||||
->getSingleScalarResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregate totals over the same filtered set as {@see tenantInvoices}, so the
|
||||
* summary cards always agree with the table below them.
|
||||
*
|
||||
* @param array{national_code?:?string,status?:?string,from?:?int,to?:?int} $filters
|
||||
* @return array{total_rials:int,paid_rials:int,unsettled_rials:int,invoices_count:int}
|
||||
*/
|
||||
public function tenantInvoiceSummary(string $entityType, int $entityId, array $filters): array
|
||||
{
|
||||
$row = $this->tenantInvoicesQuery($entityType, $entityId, $filters)
|
||||
->select(
|
||||
'COALESCE(SUM(i.patientRials), 0) AS total_rials',
|
||||
'COALESCE(SUM(CASE WHEN i.status = :paidStatus THEN i.patientRials ELSE 0 END), 0) AS paid_rials',
|
||||
'COUNT(i.id) AS invoices_count',
|
||||
)
|
||||
->setParameter('paidStatus', Invoice::STATUS_PAID)
|
||||
->getQuery()
|
||||
->getSingleResult();
|
||||
|
||||
$total = (int) $row['total_rials'];
|
||||
$paid = (int) $row['paid_rials'];
|
||||
|
||||
return [
|
||||
'total_rials' => $total,
|
||||
'paid_rials' => $paid,
|
||||
'unsettled_rials' => $total - $paid,
|
||||
'invoices_count' => (int) $row['invoices_count'],
|
||||
];
|
||||
}
|
||||
|
||||
private function tenantInvoicesQuery(string $entityType, int $entityId, array $filters): QueryBuilder
|
||||
{
|
||||
$qb = $this->createQueryBuilder('i')
|
||||
|
||||
@@ -109,6 +109,18 @@ class InvoiceService
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Financial summary of the same filtered invoice set as {@see tenantInvoiceList},
|
||||
* used by the payments page stat cards.
|
||||
*
|
||||
* @param array{national_code?:?string,status?:?string,from?:?int,to?:?int} $filters
|
||||
* @return array{total_rials:int,paid_rials:int,unsettled_rials:int,invoices_count:int}
|
||||
*/
|
||||
public function tenantInvoiceSummary(string $entityType, int $entityId, array $filters): array
|
||||
{
|
||||
return $this->invoiceRepo->tenantInvoiceSummary($entityType, $entityId, $filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* A patient's recorded invoices, shaped for the detail table: number, issue
|
||||
* time, a single service title (first item, "+ more" when several), total,
|
||||
|
||||
Reference in New Issue
Block a user