feat: [receivables] 외상매출금 원장/거래처별 요약 연동
- 홈택스 분개(hometax_invoice_journals)와 일반전표(journal_entry_lines)에서 계정코드 108(외상매출금) 데이터 집계 - ledger() API: 날짜순 정렬, 누적잔액 계산, 출처/거래처/기간 필터 - summary() API: 거래처별 발생액/회수액/잔액 요약 - UI 3탭 구조로 개편: 외상매출금 원장 / 거래처별 요약 / 수동관리(기존) - 거래처별 요약에서 행 클릭 시 해당 거래처 원장으로 이동
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
namespace App\Http\Controllers\Finance;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Barobill\HometaxInvoiceJournal;
|
||||
use App\Models\Finance\JournalEntryLine;
|
||||
use App\Models\Finance\Receivable;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -18,7 +20,7 @@ public function index(Request $request): JsonResponse
|
||||
if ($search = $request->input('search')) {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('customer_name', 'like', "%{$search}%")
|
||||
->orWhere('invoice_no', 'like', "%{$search}%");
|
||||
->orWhere('invoice_no', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -179,4 +181,179 @@ public function destroy(int $id): JsonResponse
|
||||
'message' => '미수금이 삭제되었습니다.',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 외상매출금 원장 (홈택스 분개 + 일반전표에서 계정코드 108 집계)
|
||||
*/
|
||||
public function ledger(Request $request): JsonResponse
|
||||
{
|
||||
$tenantId = session('selected_tenant_id', 1);
|
||||
$startDate = $request->input('startDate');
|
||||
$endDate = $request->input('endDate');
|
||||
$partner = $request->input('partner');
|
||||
$source = $request->input('source', 'all');
|
||||
|
||||
$items = collect();
|
||||
|
||||
// 1) 홈택스 분개에서 외상매출금(108) 조회
|
||||
if ($source !== 'journal') {
|
||||
$hometaxQuery = HometaxInvoiceJournal::where('tenant_id', $tenantId)
|
||||
->where('account_code', '108')
|
||||
->where('invoice_type', 'sales');
|
||||
|
||||
if ($startDate) {
|
||||
$hometaxQuery->where('write_date', '>=', $startDate);
|
||||
}
|
||||
if ($endDate) {
|
||||
$hometaxQuery->where('write_date', '<=', $endDate);
|
||||
}
|
||||
if ($partner) {
|
||||
$hometaxQuery->where('trading_partner_name', 'like', "%{$partner}%");
|
||||
}
|
||||
|
||||
$hometaxItems = $hometaxQuery->get()->map(fn ($j) => [
|
||||
'date' => $j->write_date?->format('Y-m-d'),
|
||||
'source' => 'hometax',
|
||||
'sourceLabel' => '홈택스 매출',
|
||||
'refNo' => $j->nts_confirm_num,
|
||||
'tradingPartnerName' => $j->trading_partner_name,
|
||||
'description' => $j->description ?: '매출세금계산서',
|
||||
'debitAmount' => $j->dc_type === 'debit' ? (int) $j->debit_amount : 0,
|
||||
'creditAmount' => $j->dc_type === 'credit' ? (int) $j->credit_amount : 0,
|
||||
]);
|
||||
|
||||
$items = $items->merge($hometaxItems);
|
||||
}
|
||||
|
||||
// 2) 일반전표에서 외상매출금(108) 조회
|
||||
if ($source !== 'hometax') {
|
||||
$journalQuery = 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')
|
||||
->select('journal_entry_lines.*', 'journal_entries.entry_date', 'journal_entries.entry_no');
|
||||
|
||||
if ($startDate) {
|
||||
$journalQuery->where('journal_entries.entry_date', '>=', $startDate);
|
||||
}
|
||||
if ($endDate) {
|
||||
$journalQuery->where('journal_entries.entry_date', '<=', $endDate);
|
||||
}
|
||||
if ($partner) {
|
||||
$journalQuery->where('journal_entry_lines.trading_partner_name', 'like', "%{$partner}%");
|
||||
}
|
||||
|
||||
$journalItems = $journalQuery->get()->map(fn ($l) => [
|
||||
'date' => $l->entry_date instanceof \Carbon\Carbon ? $l->entry_date->format('Y-m-d') : $l->entry_date,
|
||||
'source' => 'journal',
|
||||
'sourceLabel' => '일반전표 '.$l->entry_no,
|
||||
'refNo' => $l->entry_no,
|
||||
'tradingPartnerName' => $l->trading_partner_name,
|
||||
'description' => $l->description ?: '일반전표',
|
||||
'debitAmount' => (int) $l->debit_amount,
|
||||
'creditAmount' => (int) $l->credit_amount,
|
||||
]);
|
||||
|
||||
$items = $items->merge($journalItems);
|
||||
}
|
||||
|
||||
// 날짜순 정렬 + 누적잔액 계산
|
||||
$sorted = $items->sortBy('date')->values();
|
||||
$balance = 0;
|
||||
$sorted = $sorted->map(function ($item) use (&$balance) {
|
||||
$balance += $item['debitAmount'] - $item['creditAmount'];
|
||||
$item['balance'] = $balance;
|
||||
|
||||
return $item;
|
||||
});
|
||||
|
||||
$totalDebit = $sorted->sum('debitAmount');
|
||||
$totalCredit = $sorted->sum('creditAmount');
|
||||
$partnerCount = $sorted->pluck('tradingPartnerName')->filter()->unique()->count();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $sorted->values(),
|
||||
'stats' => [
|
||||
'totalDebit' => $totalDebit,
|
||||
'totalCredit' => $totalCredit,
|
||||
'balance' => $totalDebit - $totalCredit,
|
||||
'partnerCount' => $partnerCount,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 거래처별 외상매출금 요약
|
||||
*/
|
||||
public function summary(Request $request): JsonResponse
|
||||
{
|
||||
$tenantId = session('selected_tenant_id', 1);
|
||||
$startDate = $request->input('startDate');
|
||||
$endDate = $request->input('endDate');
|
||||
|
||||
$items = collect();
|
||||
|
||||
// 1) 홈택스 분개에서 외상매출금(108)
|
||||
$hometaxQuery = HometaxInvoiceJournal::where('tenant_id', $tenantId)
|
||||
->where('account_code', '108')
|
||||
->where('invoice_type', 'sales');
|
||||
|
||||
if ($startDate) {
|
||||
$hometaxQuery->where('write_date', '>=', $startDate);
|
||||
}
|
||||
if ($endDate) {
|
||||
$hometaxQuery->where('write_date', '<=', $endDate);
|
||||
}
|
||||
|
||||
$hometaxItems = $hometaxQuery->get()->map(fn ($j) => [
|
||||
'tradingPartnerName' => $j->trading_partner_name,
|
||||
'debitAmount' => $j->dc_type === 'debit' ? (int) $j->debit_amount : 0,
|
||||
'creditAmount' => $j->dc_type === 'credit' ? (int) $j->credit_amount : 0,
|
||||
'date' => $j->write_date?->format('Y-m-d'),
|
||||
]);
|
||||
$items = $items->merge($hometaxItems);
|
||||
|
||||
// 2) 일반전표에서 외상매출금(108)
|
||||
$journalQuery = 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')
|
||||
->select('journal_entry_lines.*', 'journal_entries.entry_date');
|
||||
|
||||
if ($startDate) {
|
||||
$journalQuery->where('journal_entries.entry_date', '>=', $startDate);
|
||||
}
|
||||
if ($endDate) {
|
||||
$journalQuery->where('journal_entries.entry_date', '<=', $endDate);
|
||||
}
|
||||
|
||||
$journalItems = $journalQuery->get()->map(fn ($l) => [
|
||||
'tradingPartnerName' => $l->trading_partner_name,
|
||||
'debitAmount' => (int) $l->debit_amount,
|
||||
'creditAmount' => (int) $l->credit_amount,
|
||||
'date' => $l->entry_date instanceof \Carbon\Carbon ? $l->entry_date->format('Y-m-d') : $l->entry_date,
|
||||
]);
|
||||
$items = $items->merge($journalItems);
|
||||
|
||||
// 거래처별 그룹핑
|
||||
$grouped = $items->groupBy('tradingPartnerName')->map(function ($group, $partnerName) {
|
||||
$totalDebit = $group->sum('debitAmount');
|
||||
$totalCredit = $group->sum('creditAmount');
|
||||
|
||||
return [
|
||||
'tradingPartnerName' => $partnerName ?: '(미지정)',
|
||||
'totalDebit' => $totalDebit,
|
||||
'totalCredit' => $totalCredit,
|
||||
'balance' => $totalDebit - $totalCredit,
|
||||
'lastTransactionDate' => $group->pluck('date')->filter()->sort()->last(),
|
||||
'transactionCount' => $group->count(),
|
||||
];
|
||||
})->sortByDesc('balance')->values();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $grouped,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user