feat: [현황판/악성채권] 카드별 sub_label(대표 거래처명 + 건수) 추가

- BadDebtService: summary에 카드별(전체/추심중/법적조치/회수완료) sub_labels 추가
- StatusBoardService: 악성채권·신규거래처·결재 카드에 sub_label 추가
  - 악성채권: 최다 금액 거래처명
  - 신규거래처: 최근 등록 업체명
  - 결재: 최근 결재 제목

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-03-09 16:32:58 +09:00
parent 60c4256bd0
commit 56c60ec3df
2 changed files with 111 additions and 12 deletions

View File

@@ -110,6 +110,9 @@ public function summary(array $params = []): array
->distinct('client_id')
->count('client_id');
// per-card sub_label: 각 상태별 최다 금액 거래처명 + 건수
$subLabels = $this->buildPerCardSubLabels($query);
return [
'total_amount' => (float) $totalAmount,
'collecting_amount' => (float) $collectingAmount,
@@ -117,9 +120,56 @@ public function summary(array $params = []): array
'recovered_amount' => (float) $recoveredAmount,
'bad_debt_amount' => (float) $badDebtAmount,
'client_count' => $clientCount,
'sub_labels' => $subLabels,
];
}
/**
* 카드별 sub_label 생성 (최다 금액 거래처명 + 건수)
*/
private function buildPerCardSubLabels($baseQuery): array
{
$result = [];
$statusScopes = [
'dc1' => null, // 전체 (누적)
'dc2' => 'collecting', // 추심중
'dc3' => 'legalAction', // 법적조치
'dc4' => 'recovered', // 회수완료
];
foreach ($statusScopes as $cardId => $scope) {
$q = clone $baseQuery;
if ($scope) {
$q = $q->$scope();
}
$clientCount = (clone $q)->distinct('client_id')->count('client_id');
if ($clientCount <= 0) {
$result[$cardId] = null;
continue;
}
$topClient = (clone $q)
->join('clients', 'bad_debts.client_id', '=', 'clients.id')
->selectRaw('clients.name, SUM(bad_debts.debt_amount) as total_amount')
->groupBy('clients.id', 'clients.name')
->orderByDesc('total_amount')
->first();
if ($topClient) {
$result[$cardId] = $clientCount > 1
? $topClient->name.' 외 '.($clientCount - 1).'건'
: $topClient->name;
} else {
$result[$cardId] = null;
}
}
return $result;
}
/**
* 악성채권 상세 조회
*/

View File

@@ -67,16 +67,35 @@ private function getOrdersStatus(int $tenantId, Carbon $today): array
*/
private function getBadDebtStatus(int $tenantId): array
{
$count = BadDebt::query()
$query = BadDebt::query()
->where('tenant_id', $tenantId)
->where('status', BadDebt::STATUS_COLLECTING) // 추심 진행 중
->where('is_active', true) // 활성 채권만 (목록 페이지와 일치)
->count();
->where('status', BadDebt::STATUS_COLLECTING)
->where('is_active', true);
$count = (clone $query)->count();
// 최다 금액 거래처명 조회
$subLabel = null;
if ($count > 0) {
$topClient = (clone $query)
->join('clients', 'bad_debts.client_id', '=', 'clients.id')
->selectRaw('clients.name, SUM(bad_debts.debt_amount) as total_amount')
->groupBy('clients.id', 'clients.name')
->orderByDesc('total_amount')
->first();
if ($topClient) {
$subLabel = $count > 1
? $topClient->name.' 외 '.($count - 1).'건'
: $topClient->name;
}
}
return [
'id' => 'bad_debts',
'label' => __('message.status_board.bad_debts'),
'count' => $count,
'sub_label' => $subLabel,
'path' => '/accounting/bad-debt-collection',
'isHighlighted' => false,
];
@@ -152,15 +171,31 @@ private function getTaxDeadlineStatus(int $tenantId, Carbon $today): array
*/
private function getNewClientStatus(int $tenantId, Carbon $today): array
{
$count = Client::query()
$query = Client::query()
->where('tenant_id', $tenantId)
->where('created_at', '>=', $today->copy()->subDays(7))
->count();
->where('created_at', '>=', $today->copy()->subDays(7));
$count = (clone $query)->count();
// 가장 최근 등록 업체명 조회
$subLabel = null;
if ($count > 0) {
$latestClient = (clone $query)
->orderByDesc('created_at')
->first();
if ($latestClient) {
$subLabel = $count > 1
? $latestClient->name.' 외 '.($count - 1).'건'
: $latestClient->name;
}
}
return [
'id' => 'new_clients',
'label' => __('message.status_board.new_clients'),
'count' => $count,
'sub_label' => $subLabel,
'path' => '/accounting/vendors',
'isHighlighted' => false,
];
@@ -211,19 +246,33 @@ private function getPurchaseStatus(int $tenantId): array
*/
private function getApprovalStatus(int $tenantId, int $userId): array
{
$count = ApprovalStep::query()
->whereHas('approval', function ($query) use ($tenantId) {
$query->where('tenant_id', $tenantId)
$query = ApprovalStep::query()
->whereHas('approval', function ($q) use ($tenantId) {
$q->where('tenant_id', $tenantId)
->where('status', 'pending');
})
->where('approver_id', $userId)
->where('status', 'pending')
->count();
->where('status', 'pending');
$count = (clone $query)->count();
// 최근 결재 유형 조회
$subLabel = null;
if ($count > 0) {
$latestStep = (clone $query)->with('approval')->latest()->first();
if ($latestStep && $latestStep->approval) {
$typeLabel = $latestStep->approval->title ?? '결재';
$subLabel = $count > 1
? $typeLabel.' 외 '.($count - 1).'건'
: $typeLabel;
}
}
return [
'id' => 'approvals',
'label' => __('message.status_board.approvals'),
'count' => $count,
'sub_label' => $subLabel,
'path' => '/approval/inbox',
'isHighlighted' => $count > 0,
];