Files
sam-api/app/Services/VendorLedgerService.php
kent dced7b7fd3 feat: I-2 거래처 원장 API 구현
- VendorLedgerController: 거래처별 원장 조회 API
- VendorLedgerService: 원장 조회 비즈니스 로직
- Swagger 문서화

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-26 15:46:31 +09:00

307 lines
12 KiB
PHP

<?php
namespace App\Services;
use App\Models\Orders\Client;
use App\Models\Tenants\Bill;
use App\Models\Tenants\Deposit;
use App\Models\Tenants\Sale;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
class VendorLedgerService extends Service
{
/**
* 거래처원장 목록 조회 (거래처별 매출/수금 집계)
*/
public function index(array $params): LengthAwarePaginator
{
$tenantId = $this->tenantId();
$startDate = $params['start_date'] ?? null;
$endDate = $params['end_date'] ?? null;
$search = $params['search'] ?? null;
$perPage = $params['per_page'] ?? 20;
// 거래처 목록 조회
$query = Client::query()
->where('tenant_id', $tenantId)
->where('is_active', true);
// 거래처명 검색
if (! empty($search)) {
$query->where('name', 'like', "%{$search}%");
}
// 거래처 ID 목록
$clientsQuery = clone $query;
$clients = $clientsQuery->select('id', 'name', 'sales_payment_day')->get();
// 매출 집계 서브쿼리
$salesSubquery = Sale::query()
->select('client_id', DB::raw('SUM(total_amount) as total_sales'))
->where('tenant_id', $tenantId)
->when($startDate, fn ($q) => $q->where('sale_date', '>=', $startDate))
->when($endDate, fn ($q) => $q->where('sale_date', '<=', $endDate))
->groupBy('client_id');
// 수금 집계 서브쿼리
$depositsSubquery = Deposit::query()
->select('client_id', DB::raw('SUM(amount) as total_collection'))
->where('tenant_id', $tenantId)
->when($startDate, fn ($q) => $q->where('deposit_date', '>=', $startDate))
->when($endDate, fn ($q) => $q->where('deposit_date', '<=', $endDate))
->groupBy('client_id');
// 이월잔액 서브쿼리 (기간 시작 전 매출 - 수금)
$carryoverSalesSubquery = null;
$carryoverDepositsSubquery = null;
if ($startDate) {
$carryoverSalesSubquery = Sale::query()
->select('client_id', DB::raw('COALESCE(SUM(total_amount), 0) as carryover_sales'))
->where('tenant_id', $tenantId)
->where('sale_date', '<', $startDate)
->groupBy('client_id');
$carryoverDepositsSubquery = Deposit::query()
->select('client_id', DB::raw('COALESCE(SUM(amount), 0) as carryover_deposits'))
->where('tenant_id', $tenantId)
->where('deposit_date', '<', $startDate)
->groupBy('client_id');
}
// 메인 쿼리
$mainQuery = Client::query()
->select([
'clients.id',
'clients.name',
'clients.sales_payment_day',
DB::raw('COALESCE(sales_agg.total_sales, 0) as period_sales'),
DB::raw('COALESCE(deposits_agg.total_collection, 0) as period_collection'),
])
->where('clients.tenant_id', $tenantId)
->where('clients.is_active', true)
->leftJoinSub($salesSubquery, 'sales_agg', 'clients.id', '=', 'sales_agg.client_id')
->leftJoinSub($depositsSubquery, 'deposits_agg', 'clients.id', '=', 'deposits_agg.client_id');
if ($carryoverSalesSubquery && $carryoverDepositsSubquery) {
$mainQuery->addSelect([
DB::raw('COALESCE(carryover_sales_agg.carryover_sales, 0) as carryover_sales'),
DB::raw('COALESCE(carryover_deposits_agg.carryover_deposits, 0) as carryover_deposits'),
])
->leftJoinSub($carryoverSalesSubquery, 'carryover_sales_agg', 'clients.id', '=', 'carryover_sales_agg.client_id')
->leftJoinSub($carryoverDepositsSubquery, 'carryover_deposits_agg', 'clients.id', '=', 'carryover_deposits_agg.client_id');
} else {
$mainQuery->addSelect([
DB::raw('0 as carryover_sales'),
DB::raw('0 as carryover_deposits'),
]);
}
// 검색
if (! empty($search)) {
$mainQuery->where('clients.name', 'like', "%{$search}%");
}
// 정렬
$sortBy = $params['sort_by'] ?? 'name';
$sortDir = $params['sort_dir'] ?? 'asc';
$mainQuery->orderBy("clients.{$sortBy}", $sortDir);
return $mainQuery->paginate($perPage);
}
/**
* 거래처원장 요약 통계
*/
public function summary(array $params): array
{
$tenantId = $this->tenantId();
$startDate = $params['start_date'] ?? null;
$endDate = $params['end_date'] ?? null;
// 기간 내 매출 합계
$totalSales = Sale::query()
->where('tenant_id', $tenantId)
->when($startDate, fn ($q) => $q->where('sale_date', '>=', $startDate))
->when($endDate, fn ($q) => $q->where('sale_date', '<=', $endDate))
->sum('total_amount');
// 기간 내 수금 합계
$totalCollection = Deposit::query()
->where('tenant_id', $tenantId)
->when($startDate, fn ($q) => $q->where('deposit_date', '>=', $startDate))
->when($endDate, fn ($q) => $q->where('deposit_date', '<=', $endDate))
->sum('amount');
// 이월잔액 (기간 시작 전 매출 - 수금)
$carryoverBalance = 0;
if ($startDate) {
$carryoverSales = Sale::query()
->where('tenant_id', $tenantId)
->where('sale_date', '<', $startDate)
->sum('total_amount');
$carryoverDeposits = Deposit::query()
->where('tenant_id', $tenantId)
->where('deposit_date', '<', $startDate)
->sum('amount');
$carryoverBalance = (float) $carryoverSales - (float) $carryoverDeposits;
}
// 현재 잔액 = 이월잔액 + 매출 - 수금
$balance = $carryoverBalance + (float) $totalSales - (float) $totalCollection;
return [
'carryover_balance' => $carryoverBalance,
'total_sales' => (float) $totalSales,
'total_collection' => (float) $totalCollection,
'balance' => $balance,
];
}
/**
* 거래처원장 상세 (거래처별 거래 내역)
*/
public function show(int $clientId, array $params): array
{
$tenantId = $this->tenantId();
$startDate = $params['start_date'] ?? null;
$endDate = $params['end_date'] ?? null;
// 거래처 정보
$client = Client::query()
->where('tenant_id', $tenantId)
->findOrFail($clientId);
// 이월잔액 계산
$carryoverBalance = 0;
if ($startDate) {
$carryoverSales = Sale::query()
->where('tenant_id', $tenantId)
->where('client_id', $clientId)
->where('sale_date', '<', $startDate)
->sum('total_amount');
$carryoverDeposits = Deposit::query()
->where('tenant_id', $tenantId)
->where('client_id', $clientId)
->where('deposit_date', '<', $startDate)
->sum('amount');
$carryoverBalance = (float) $carryoverSales - (float) $carryoverDeposits;
}
// 거래 내역 조회 (매출 + 수금)
$transactions = collect();
// 매출 내역
$sales = Sale::query()
->where('tenant_id', $tenantId)
->where('client_id', $clientId)
->when($startDate, fn ($q) => $q->where('sale_date', '>=', $startDate))
->when($endDate, fn ($q) => $q->where('sale_date', '<=', $endDate))
->orderBy('sale_date')
->get()
->map(fn ($sale) => [
'id' => $sale->id,
'date' => $sale->sale_date->format('Y-m-d'),
'type' => 'sales',
'description' => $sale->description ?? '매출',
'sales_amount' => (float) $sale->total_amount,
'collection_amount' => 0,
'reference_id' => $sale->id,
'reference_type' => 'sale',
'has_action' => false,
'is_highlighted' => ! $sale->tax_invoice_issued,
]);
// 수금 내역
$deposits = Deposit::query()
->where('tenant_id', $tenantId)
->where('client_id', $clientId)
->when($startDate, fn ($q) => $q->where('deposit_date', '>=', $startDate))
->when($endDate, fn ($q) => $q->where('deposit_date', '<=', $endDate))
->orderBy('deposit_date')
->get()
->map(fn ($deposit) => [
'id' => $deposit->id,
'date' => $deposit->deposit_date->format('Y-m-d'),
'type' => 'collection',
'description' => $deposit->description ?? '입금',
'sales_amount' => 0,
'collection_amount' => (float) $deposit->amount,
'reference_id' => $deposit->id,
'reference_type' => 'deposit',
'has_action' => false,
'is_highlighted' => false,
'is_parenthesis' => $deposit->payment_method === 'check',
]);
// 어음 내역
$bills = Bill::query()
->where('tenant_id', $tenantId)
->where('client_id', $clientId)
->when($startDate, fn ($q) => $q->where('issue_date', '>=', $startDate))
->when($endDate, fn ($q) => $q->where('issue_date', '<=', $endDate))
->orderBy('issue_date')
->get()
->map(fn ($bill) => [
'id' => $bill->id,
'date' => $bill->issue_date,
'type' => 'note',
'description' => "수취 어음 (만기 {$bill->maturity_date})",
'sales_amount' => 0,
'collection_amount' => (float) $bill->amount,
'reference_id' => $bill->id,
'reference_type' => 'bill',
'has_action' => true,
'is_highlighted' => false,
'is_parenthesis' => true,
'note_info' => $bill->maturity_date,
]);
// 전체 거래 내역 합치기 및 정렬
$allTransactions = $sales->merge($deposits)->merge($bills)
->sortBy('date')
->values();
// 잔액 계산
$runningBalance = $carryoverBalance;
$transactions = $allTransactions->map(function ($item) use (&$runningBalance) {
$runningBalance = $runningBalance + $item['sales_amount'] - $item['collection_amount'];
$item['balance'] = $runningBalance;
return $item;
});
// 합계 계산
$totalSales = $transactions->sum('sales_amount');
$totalCollection = $transactions->sum('collection_amount');
return [
'client' => [
'id' => $client->id,
'name' => $client->name,
'business_number' => $client->business_no,
'representative_name' => $client->contact_person,
'phone' => $client->phone,
'mobile' => $client->mobile,
'fax' => $client->fax,
'email' => $client->email,
'address' => $client->address,
],
'period' => [
'start_date' => $startDate,
'end_date' => $endDate,
],
'carryover_balance' => $carryoverBalance,
'total_sales' => $totalSales,
'total_collection' => $totalCollection,
'balance' => $runningBalance,
'transactions' => $transactions->toArray(),
];
}
}