tenantId(); $year = $params['year'] ?? date('Y'); $search = $params['search'] ?? null; // 거래처 목록 조회 $clientsQuery = Client::where('tenant_id', $tenantId) ->where('is_active', true); if ($search) { $clientsQuery->where('name', 'like', "%{$search}%"); } $clients = $clientsQuery->orderBy('name')->get(); $result = []; foreach ($clients as $client) { // 월별 데이터 수집 $salesByMonth = $this->getSalesByMonth($tenantId, $client->id, $year); $depositsByMonth = $this->getDepositsByMonth($tenantId, $client->id, $year); $billsByMonth = $this->getBillsByMonth($tenantId, $client->id, $year); // 미수금 계산 (매출 - 입금 - 어음) $receivablesByMonth = $this->calculateReceivables($salesByMonth, $depositsByMonth, $billsByMonth); // 카테고리별 데이터 생성 $categories = [ [ 'category' => 'sales', 'amounts' => $this->formatMonthlyAmounts($salesByMonth), ], [ 'category' => 'deposit', 'amounts' => $this->formatMonthlyAmounts($depositsByMonth), ], [ 'category' => 'bill', 'amounts' => $this->formatMonthlyAmounts($billsByMonth), ], [ 'category' => 'receivable', 'amounts' => $this->formatMonthlyAmounts($receivablesByMonth), ], [ 'category' => 'memo', 'amounts' => $this->getEmptyMonthlyAmounts(), ], ]; // 미수금이 있는 월 확인 (연체 표시용) $overdueMonths = []; foreach ($receivablesByMonth as $month => $amount) { if ($amount > 0) { $overdueMonths[] = $month; } } $result[] = [ 'id' => (string) $client->id, 'vendor_id' => $client->id, 'vendor_name' => $client->name, 'is_overdue' => count($overdueMonths) > 0, 'overdue_months' => $overdueMonths, 'categories' => $categories, ]; } // 미수금이 있는 거래처만 필터링 (선택적) if (! empty($params['has_receivable'])) { $result = array_filter($result, function ($item) { $receivableCat = collect($item['categories'])->firstWhere('category', 'receivable'); return $receivableCat && $receivableCat['amounts']['total'] > 0; }); $result = array_values($result); } return $result; } /** * 요약 통계 조회 */ public function summary(array $params): array { $tenantId = $this->tenantId(); $year = $params['year'] ?? date('Y'); $startDate = "{$year}-01-01"; $endDate = "{$year}-12-31"; // 총 매출 $totalSales = Sale::where('tenant_id', $tenantId) ->whereNotNull('client_id') ->whereBetween('sale_date', [$startDate, $endDate]) ->sum('total_amount'); // 총 입금 $totalDeposits = Deposit::where('tenant_id', $tenantId) ->whereNotNull('client_id') ->whereBetween('deposit_date', [$startDate, $endDate]) ->sum('amount'); // 총 어음 $totalBills = Bill::where('tenant_id', $tenantId) ->whereNotNull('client_id') ->where('bill_type', 'received') ->whereBetween('issue_date', [$startDate, $endDate]) ->sum('amount'); // 총 미수금 $totalReceivables = $totalSales - $totalDeposits - $totalBills; if ($totalReceivables < 0) { $totalReceivables = 0; } // 거래처 수 $vendorCount = Client::where('tenant_id', $tenantId) ->where('is_active', true) ->whereHas('orders') ->count(); // 연체 거래처 수 (미수금이 있는 거래처) $overdueVendorCount = DB::table('sales') ->where('tenant_id', $tenantId) ->whereNotNull('client_id') ->whereBetween('sale_date', [$startDate, $endDate]) ->select('client_id') ->groupBy('client_id') ->havingRaw('SUM(total_amount) > ( SELECT COALESCE(SUM(d.amount), 0) FROM deposits d WHERE d.tenant_id = ? AND d.client_id = sales.client_id AND d.deposit_date BETWEEN ? AND ? ) + ( SELECT COALESCE(SUM(b.amount), 0) FROM bills b WHERE b.tenant_id = ? AND b.client_id = sales.client_id AND b.bill_type = "received" AND b.issue_date BETWEEN ? AND ? )', [$tenantId, $startDate, $endDate, $tenantId, $startDate, $endDate]) ->count(); return [ 'total_sales' => (float) $totalSales, 'total_deposits' => (float) $totalDeposits, 'total_bills' => (float) $totalBills, 'total_receivables' => (float) $totalReceivables, 'vendor_count' => $vendorCount, 'overdue_vendor_count' => $overdueVendorCount, ]; } /** * 월별 매출 조회 */ private function getSalesByMonth(int $tenantId, int $clientId, string $year): array { $result = []; $sales = Sale::where('tenant_id', $tenantId) ->where('client_id', $clientId) ->whereYear('sale_date', $year) ->select( DB::raw('MONTH(sale_date) as month'), DB::raw('SUM(total_amount) as total') ) ->groupBy(DB::raw('MONTH(sale_date)')) ->get(); foreach ($sales as $sale) { $result[(int) $sale->month] = (float) $sale->total; } return $result; } /** * 월별 입금 조회 */ private function getDepositsByMonth(int $tenantId, int $clientId, string $year): array { $result = []; $deposits = Deposit::where('tenant_id', $tenantId) ->where('client_id', $clientId) ->whereYear('deposit_date', $year) ->select( DB::raw('MONTH(deposit_date) as month'), DB::raw('SUM(amount) as total') ) ->groupBy(DB::raw('MONTH(deposit_date)')) ->get(); foreach ($deposits as $deposit) { $result[(int) $deposit->month] = (float) $deposit->total; } return $result; } /** * 월별 어음 조회 */ private function getBillsByMonth(int $tenantId, int $clientId, string $year): array { $result = []; $bills = Bill::where('tenant_id', $tenantId) ->where('client_id', $clientId) ->where('bill_type', 'received') ->whereYear('issue_date', $year) ->select( DB::raw('MONTH(issue_date) as month'), DB::raw('SUM(amount) as total') ) ->groupBy(DB::raw('MONTH(issue_date)')) ->get(); foreach ($bills as $bill) { $result[(int) $bill->month] = (float) $bill->total; } return $result; } /** * 미수금 계산 */ private function calculateReceivables(array $sales, array $deposits, array $bills): array { $result = []; for ($month = 1; $month <= 12; $month++) { $salesAmount = $sales[$month] ?? 0; $depositAmount = $deposits[$month] ?? 0; $billAmount = $bills[$month] ?? 0; $receivable = $salesAmount - $depositAmount - $billAmount; $result[$month] = max(0, $receivable); } return $result; } /** * 월별 금액을 프론트엔드 형식으로 변환 */ private function formatMonthlyAmounts(array $monthlyData): array { $amounts = []; $total = 0; for ($month = 1; $month <= 12; $month++) { $key = "month{$month}"; $amount = $monthlyData[$month] ?? 0; $amounts[$key] = $amount; $total += $amount; } $amounts['total'] = $total; return $amounts; } /** * 빈 월별 금액 데이터 */ private function getEmptyMonthlyAmounts(): array { $amounts = []; for ($month = 1; $month <= 12; $month++) { $amounts["month{$month}"] = 0; } $amounts['total'] = 0; return $amounts; } }