- VendorLedgerController: 거래처별 원장 조회 API - VendorLedgerService: 원장 조회 비즈니스 로직 - Swagger 문서화 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
307 lines
12 KiB
PHP
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(),
|
|
];
|
|
}
|
|
}
|