From 56c60ec3dfd8c23d8b1b7c51811d7980374aeb0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EB=B3=91=EC=B2=A0?= Date: Mon, 9 Mar 2026 16:32:58 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[=ED=98=84=ED=99=A9=ED=8C=90/=EC=95=85?= =?UTF-8?q?=EC=84=B1=EC=B1=84=EA=B6=8C]=20=EC=B9=B4=EB=93=9C=EB=B3=84=20su?= =?UTF-8?q?b=5Flabel(=EB=8C=80=ED=91=9C=20=EA=B1=B0=EB=9E=98=EC=B2=98?= =?UTF-8?q?=EB=AA=85=20+=20=EA=B1=B4=EC=88=98)=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BadDebtService: summary에 카드별(전체/추심중/법적조치/회수완료) sub_labels 추가 - StatusBoardService: 악성채권·신규거래처·결재 카드에 sub_label 추가 - 악성채권: 최다 금액 거래처명 - 신규거래처: 최근 등록 업체명 - 결재: 최근 결재 제목 Co-Authored-By: Claude Opus 4.6 --- app/Services/BadDebtService.php | 50 ++++++++++++++++++++ app/Services/StatusBoardService.php | 73 ++++++++++++++++++++++++----- 2 files changed, 111 insertions(+), 12 deletions(-) diff --git a/app/Services/BadDebtService.php b/app/Services/BadDebtService.php index 9b04565..063155c 100644 --- a/app/Services/BadDebtService.php +++ b/app/Services/BadDebtService.php @@ -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; + } + /** * 악성채권 상세 조회 */ diff --git a/app/Services/StatusBoardService.php b/app/Services/StatusBoardService.php index b38dca5..4a26290 100644 --- a/app/Services/StatusBoardService.php +++ b/app/Services/StatusBoardService.php @@ -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, ];