feat: [receivables] 미수금 이월잔액 반영
- summary() 메서드: 거래처별 이월잔액(priorBalance) 계산 추가 - ledger() 메서드: 원장 누적잔액이 이월잔액부터 시작하도록 변경 - 프론트엔드: LedgerTab 이월잔액 통계카드 추가 (조건부 표시) - 프론트엔드: SummaryTab 이월잔액 컬럼 추가, 라벨 당기발생/당기회수로 변경 - CSV 다운로드에 이월잔액 포함
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
use App\Models\Finance\Receivable;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ReceivableController extends Controller
|
||||
{
|
||||
@@ -194,6 +195,53 @@ public function ledger(Request $request): JsonResponse
|
||||
$partner = $request->input('partner');
|
||||
$source = $request->input('source', 'all');
|
||||
|
||||
// 분개 완료된 매출세금계산서 ID
|
||||
$journaledInvoiceIds = [];
|
||||
if ($source !== 'journal') {
|
||||
$journaledInvoiceIds = HometaxInvoiceJournal::where('tenant_id', $tenantId)
|
||||
->where('invoice_type', 'sales')
|
||||
->distinct()
|
||||
->pluck('hometax_invoice_id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
// 이월 잔액 계산 (startDate 이전)
|
||||
$priorBalance = 0;
|
||||
if ($startDate) {
|
||||
if ($source !== 'journal') {
|
||||
$priorHtQuery = HometaxInvoiceJournal::where('tenant_id', $tenantId)
|
||||
->where('account_code', '108')
|
||||
->where('invoice_type', 'sales')
|
||||
->where('write_date', '<', $startDate);
|
||||
if ($partner) {
|
||||
$priorHtQuery->where('trading_partner_name', 'like', "%{$partner}%");
|
||||
}
|
||||
$priorHtData = $priorHtQuery->selectRaw('SUM(debit_amount) as td, SUM(credit_amount) as tc')->first();
|
||||
$priorBalance += ($priorHtData->td ?? 0) - ($priorHtData->tc ?? 0);
|
||||
|
||||
$priorUnjQuery = HometaxInvoice::where('tenant_id', $tenantId)
|
||||
->where('invoice_type', 'sales')
|
||||
->whereNotIn('id', $journaledInvoiceIds)
|
||||
->where('write_date', '<', $startDate);
|
||||
if ($partner) {
|
||||
$priorUnjQuery->where('invoicee_corp_name', 'like', "%{$partner}%");
|
||||
}
|
||||
$priorBalance += (int) $priorUnjQuery->sum('total_amount');
|
||||
}
|
||||
if ($source !== 'hometax') {
|
||||
$priorJnlQuery = JournalEntryLine::where('journal_entry_lines.tenant_id', $tenantId)
|
||||
->where('journal_entry_lines.account_code', '108')
|
||||
->join('journal_entries', 'journal_entries.id', '=', 'journal_entry_lines.journal_entry_id')
|
||||
->whereNull('journal_entries.deleted_at')
|
||||
->where('journal_entries.entry_date', '<', $startDate);
|
||||
if ($partner) {
|
||||
$priorJnlQuery->where('journal_entry_lines.trading_partner_name', 'like', "%{$partner}%");
|
||||
}
|
||||
$priorJnlData = $priorJnlQuery->selectRaw('SUM(journal_entry_lines.debit_amount) as td, SUM(journal_entry_lines.credit_amount) as tc')->first();
|
||||
$priorBalance += ($priorJnlData->td ?? 0) - ($priorJnlData->tc ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
$items = collect();
|
||||
|
||||
// 1) 홈택스 매출세금계산서에서 외상매출금 조회
|
||||
@@ -213,12 +261,6 @@ public function ledger(Request $request): JsonResponse
|
||||
$hometaxQuery->where('trading_partner_name', 'like', "%{$partner}%");
|
||||
}
|
||||
|
||||
$journaledInvoiceIds = HometaxInvoiceJournal::where('tenant_id', $tenantId)
|
||||
->where('invoice_type', 'sales')
|
||||
->distinct()
|
||||
->pluck('hometax_invoice_id')
|
||||
->toArray();
|
||||
|
||||
$hometaxItems = $hometaxQuery->get()->map(fn ($j) => [
|
||||
'date' => $j->write_date?->format('Y-m-d'),
|
||||
'source' => 'hometax',
|
||||
@@ -293,9 +335,9 @@ public function ledger(Request $request): JsonResponse
|
||||
$items = $items->merge($journalItems);
|
||||
}
|
||||
|
||||
// 날짜순 정렬 + 누적잔액 계산
|
||||
// 날짜순 정렬 + 누적잔액 계산 (이월잔액부터 시작)
|
||||
$sorted = $items->sortBy('date')->values();
|
||||
$balance = 0;
|
||||
$balance = $priorBalance;
|
||||
$sorted = $sorted->map(function ($item) use (&$balance) {
|
||||
$balance += $item['debitAmount'] - $item['creditAmount'];
|
||||
$item['balance'] = $balance;
|
||||
@@ -311,16 +353,17 @@ public function ledger(Request $request): JsonResponse
|
||||
'success' => true,
|
||||
'data' => $sorted->values(),
|
||||
'stats' => [
|
||||
'priorBalance' => $priorBalance,
|
||||
'totalDebit' => $totalDebit,
|
||||
'totalCredit' => $totalCredit,
|
||||
'balance' => $totalDebit - $totalCredit,
|
||||
'balance' => $priorBalance + $totalDebit - $totalCredit,
|
||||
'partnerCount' => $partnerCount,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 거래처별 외상매출금 요약
|
||||
* 거래처별 외상매출금 요약 (이월잔액 포함)
|
||||
*/
|
||||
public function summary(Request $request): JsonResponse
|
||||
{
|
||||
@@ -328,6 +371,79 @@ public function summary(Request $request): JsonResponse
|
||||
$startDate = $request->input('startDate');
|
||||
$endDate = $request->input('endDate');
|
||||
|
||||
// 분개 완료된 매출세금계산서 ID (전 기간)
|
||||
$journaledInvoiceIds = HometaxInvoiceJournal::where('tenant_id', $tenantId)
|
||||
->where('invoice_type', 'sales')
|
||||
->distinct()
|
||||
->pluck('hometax_invoice_id')
|
||||
->toArray();
|
||||
|
||||
// 0. 이월 잔액 계산 (startDate 이전의 누적 잔액)
|
||||
$priorBalanceMap = [];
|
||||
|
||||
if ($startDate) {
|
||||
// 이월 - 홈택스 분개 완료건 (account 108, sales)
|
||||
HometaxInvoiceJournal::where('tenant_id', $tenantId)
|
||||
->where('account_code', '108')
|
||||
->where('invoice_type', 'sales')
|
||||
->where('write_date', '<', $startDate)
|
||||
->select(
|
||||
'trading_partner_name',
|
||||
DB::raw('SUM(debit_amount) as total_debit'),
|
||||
DB::raw('SUM(credit_amount) as total_credit')
|
||||
)
|
||||
->groupBy('trading_partner_name')
|
||||
->get()
|
||||
->each(function ($row) use (&$priorBalanceMap) {
|
||||
$key = $row->trading_partner_name;
|
||||
if (! isset($priorBalanceMap[$key])) {
|
||||
$priorBalanceMap[$key] = ['debit' => 0, 'credit' => 0];
|
||||
}
|
||||
$priorBalanceMap[$key]['debit'] += $row->total_debit;
|
||||
$priorBalanceMap[$key]['credit'] += $row->total_credit;
|
||||
});
|
||||
|
||||
// 이월 - 미분개 매출세금계산서 (차변 발생)
|
||||
HometaxInvoice::where('tenant_id', $tenantId)
|
||||
->where('invoice_type', 'sales')
|
||||
->whereNotIn('id', $journaledInvoiceIds)
|
||||
->where('write_date', '<', $startDate)
|
||||
->select('invoicee_corp_name', DB::raw('SUM(total_amount) as total_amount'))
|
||||
->groupBy('invoicee_corp_name')
|
||||
->get()
|
||||
->each(function ($row) use (&$priorBalanceMap) {
|
||||
$key = $row->invoicee_corp_name;
|
||||
if (! isset($priorBalanceMap[$key])) {
|
||||
$priorBalanceMap[$key] = ['debit' => 0, 'credit' => 0];
|
||||
}
|
||||
$priorBalanceMap[$key]['debit'] += $row->total_amount;
|
||||
});
|
||||
|
||||
// 이월 - 일반전표 (account 108)
|
||||
JournalEntryLine::where('journal_entry_lines.tenant_id', $tenantId)
|
||||
->where('journal_entry_lines.account_code', '108')
|
||||
->join('journal_entries', function ($join) {
|
||||
$join->on('journal_entries.id', '=', 'journal_entry_lines.journal_entry_id')
|
||||
->whereNull('journal_entries.deleted_at');
|
||||
})
|
||||
->where('journal_entries.entry_date', '<', $startDate)
|
||||
->select(
|
||||
'journal_entry_lines.trading_partner_name',
|
||||
DB::raw('SUM(journal_entry_lines.debit_amount) as total_debit'),
|
||||
DB::raw('SUM(journal_entry_lines.credit_amount) as total_credit')
|
||||
)
|
||||
->groupBy('journal_entry_lines.trading_partner_name')
|
||||
->get()
|
||||
->each(function ($row) use (&$priorBalanceMap) {
|
||||
$key = $row->trading_partner_name;
|
||||
if (! isset($priorBalanceMap[$key])) {
|
||||
$priorBalanceMap[$key] = ['debit' => 0, 'credit' => 0];
|
||||
}
|
||||
$priorBalanceMap[$key]['debit'] += $row->total_debit;
|
||||
$priorBalanceMap[$key]['credit'] += $row->total_credit;
|
||||
});
|
||||
}
|
||||
|
||||
$items = collect();
|
||||
|
||||
// 1) 홈택스 매출세금계산서에서 외상매출금
|
||||
@@ -343,12 +459,6 @@ public function summary(Request $request): JsonResponse
|
||||
$hometaxQuery->where('write_date', '<=', $endDate);
|
||||
}
|
||||
|
||||
$journaledInvoiceIds = HometaxInvoiceJournal::where('tenant_id', $tenantId)
|
||||
->where('invoice_type', 'sales')
|
||||
->distinct()
|
||||
->pluck('hometax_invoice_id')
|
||||
->toArray();
|
||||
|
||||
$hometaxItems = $hometaxQuery->get()->map(fn ($j) => [
|
||||
'tradingPartnerName' => $j->trading_partner_name,
|
||||
'debitAmount' => $j->dc_type === 'debit' ? (int) $j->debit_amount : 0,
|
||||
@@ -399,20 +509,42 @@ public function summary(Request $request): JsonResponse
|
||||
]);
|
||||
$items = $items->merge($journalItems);
|
||||
|
||||
// 거래처별 그룹핑
|
||||
$grouped = $items->groupBy('tradingPartnerName')->map(function ($group, $partnerName) {
|
||||
// 거래처별 그룹핑 (이월 잔액 포함)
|
||||
$grouped = $items->groupBy('tradingPartnerName')->map(function ($group, $partnerName) use ($priorBalanceMap) {
|
||||
$totalDebit = $group->sum('debitAmount');
|
||||
$totalCredit = $group->sum('creditAmount');
|
||||
$prior = $priorBalanceMap[$partnerName] ?? null;
|
||||
$priorBalance = $prior ? ($prior['debit'] - $prior['credit']) : 0;
|
||||
|
||||
return [
|
||||
'tradingPartnerName' => $partnerName ?: '(미지정)',
|
||||
'priorBalance' => $priorBalance,
|
||||
'totalDebit' => $totalDebit,
|
||||
'totalCredit' => $totalCredit,
|
||||
'balance' => $totalDebit - $totalCredit,
|
||||
'balance' => $priorBalance + $totalDebit - $totalCredit,
|
||||
'lastTransactionDate' => $group->pluck('date')->filter()->sort()->last(),
|
||||
'transactionCount' => $group->count(),
|
||||
];
|
||||
})->sortByDesc('balance')->values();
|
||||
});
|
||||
|
||||
// 이월 잔액만 있고 당기 거래가 없는 거래처도 포함
|
||||
$existingPartners = $grouped->keys()->toArray();
|
||||
foreach ($priorBalanceMap as $partnerName => $prior) {
|
||||
$priorBalance = $prior['debit'] - $prior['credit'];
|
||||
if ($priorBalance != 0 && ! in_array($partnerName, $existingPartners)) {
|
||||
$grouped[$partnerName] = [
|
||||
'tradingPartnerName' => $partnerName ?: '(미지정)',
|
||||
'priorBalance' => $priorBalance,
|
||||
'totalDebit' => 0,
|
||||
'totalCredit' => 0,
|
||||
'balance' => $priorBalance,
|
||||
'lastTransactionDate' => null,
|
||||
'transactionCount' => 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$grouped = $grouped->sortByDesc('balance')->values();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
|
||||
Reference in New Issue
Block a user