diff --git a/app/Http/Controllers/Finance/PayableController.php b/app/Http/Controllers/Finance/PayableController.php
index c7d6fa3b..ac979710 100644
--- a/app/Http/Controllers/Finance/PayableController.php
+++ b/app/Http/Controllers/Finance/PayableController.php
@@ -3,9 +3,12 @@
namespace App\Http\Controllers\Finance;
use App\Http\Controllers\Controller;
+use App\Models\Barobill\HometaxInvoiceJournal;
+use App\Models\Finance\JournalEntryLine;
use App\Models\Finance\Payable;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
class PayableController extends Controller
{
@@ -180,4 +183,252 @@ public function destroy(int $id): JsonResponse
'message' => '미지급금이 삭제되었습니다.',
]);
}
+
+ /**
+ * 통합 잔액 조회: 홈택스 발생 + 일반전표 상계를 거래처별로 집계
+ */
+ public function integrated(Request $request): JsonResponse
+ {
+ $tenantId = session('selected_tenant_id', 1);
+ $startDate = $request->input('startDate', date('Y-01-01'));
+ $endDate = $request->input('endDate', date('Y-12-31'));
+ $account = $request->input('account', 'all');
+ $vendor = $request->input('vendor', '');
+
+ $accountCodes = $account === 'all' ? ['204', '205'] : [$account];
+
+ // 1. 홈택스 매입세금계산서 - 대변(미지급금 발생)
+ $hometaxQuery = HometaxInvoiceJournal::where('tenant_id', $tenantId)
+ ->whereIn('account_code', $accountCodes)
+ ->where('dc_type', 'credit')
+ ->where('credit_amount', '>', 0)
+ ->whereBetween('write_date', [$startDate, $endDate]);
+
+ if ($vendor) {
+ $hometaxQuery->where('trading_partner_name', 'like', "%{$vendor}%");
+ }
+
+ $hometaxGrouped = (clone $hometaxQuery)
+ ->select(
+ 'trading_partner_name',
+ 'account_code',
+ 'account_name',
+ DB::raw('SUM(credit_amount) as total_credit'),
+ DB::raw('COUNT(*) as cnt'),
+ DB::raw('MIN(write_date) as first_date'),
+ DB::raw('MAX(write_date) as last_date')
+ )
+ ->groupBy('trading_partner_name', 'account_code', 'account_name')
+ ->orderByDesc('total_credit')
+ ->get();
+
+ $hometaxDetails = (clone $hometaxQuery)
+ ->select('id', 'trading_partner_name', 'account_code', 'account_name', 'credit_amount', 'write_date', 'description', 'nts_confirm_num')
+ ->orderByDesc('write_date')
+ ->get();
+
+ // 2. 일반전표 - 계정 204/205 차변(상계) + 대변(전표를 통한 발생)
+ $journalQuery = JournalEntryLine::where('journal_entry_lines.tenant_id', $tenantId)
+ ->whereIn('journal_entry_lines.account_code', $accountCodes)
+ ->join('journal_entries', function ($join) {
+ $join->on('journal_entries.id', '=', 'journal_entry_lines.journal_entry_id')
+ ->whereNull('journal_entries.deleted_at');
+ })
+ ->whereBetween('journal_entries.entry_date', [$startDate, $endDate]);
+
+ if ($vendor) {
+ $journalQuery->where('journal_entry_lines.trading_partner_name', 'like', "%{$vendor}%");
+ }
+
+ $journalGrouped = (clone $journalQuery)
+ ->select(
+ 'journal_entry_lines.trading_partner_name',
+ 'journal_entry_lines.account_code',
+ 'journal_entry_lines.account_name',
+ 'journal_entry_lines.dc_type',
+ DB::raw('SUM(journal_entry_lines.debit_amount) as total_debit'),
+ DB::raw('SUM(journal_entry_lines.credit_amount) as total_credit'),
+ DB::raw('COUNT(*) as cnt')
+ )
+ ->groupBy('journal_entry_lines.trading_partner_name', 'journal_entry_lines.account_code', 'journal_entry_lines.account_name', 'journal_entry_lines.dc_type')
+ ->orderByDesc('total_debit')
+ ->get();
+
+ $journalDetails = (clone $journalQuery)
+ ->select(
+ 'journal_entry_lines.id',
+ 'journal_entry_lines.trading_partner_name',
+ 'journal_entry_lines.account_code',
+ 'journal_entry_lines.account_name',
+ 'journal_entry_lines.dc_type',
+ 'journal_entry_lines.debit_amount',
+ 'journal_entry_lines.credit_amount',
+ 'journal_entry_lines.description',
+ 'journal_entries.entry_date',
+ 'journal_entries.entry_no'
+ )
+ ->orderByDesc('journal_entries.entry_date')
+ ->get();
+
+ // 3. 거래처별 잔액 계산
+ $vendorMap = [];
+
+ // 홈택스 대변 = 발생
+ foreach ($hometaxGrouped as $row) {
+ $key = $row->trading_partner_name.'|'.$row->account_code;
+ if (! isset($vendorMap[$key])) {
+ $vendorMap[$key] = [
+ 'vendorName' => $row->trading_partner_name,
+ 'accountCode' => $row->account_code,
+ 'accountName' => $row->account_name,
+ 'occurred' => 0,
+ 'offset' => 0,
+ 'hometaxCount' => 0,
+ 'journalDebitCount' => 0,
+ 'journalCreditCount' => 0,
+ ];
+ }
+ $vendorMap[$key]['occurred'] += $row->total_credit;
+ $vendorMap[$key]['hometaxCount'] += $row->cnt;
+ }
+
+ // 일반전표 차변 = 상계, 대변 = 발생
+ foreach ($journalGrouped as $row) {
+ $key = $row->trading_partner_name.'|'.$row->account_code;
+ if (! isset($vendorMap[$key])) {
+ $vendorMap[$key] = [
+ 'vendorName' => $row->trading_partner_name,
+ 'accountCode' => $row->account_code,
+ 'accountName' => $row->account_name,
+ 'occurred' => 0,
+ 'offset' => 0,
+ 'hometaxCount' => 0,
+ 'journalDebitCount' => 0,
+ 'journalCreditCount' => 0,
+ ];
+ }
+ if ($row->dc_type === 'debit') {
+ $vendorMap[$key]['offset'] += $row->total_debit;
+ $vendorMap[$key]['journalDebitCount'] += $row->cnt;
+ } else {
+ $vendorMap[$key]['occurred'] += $row->total_credit;
+ $vendorMap[$key]['journalCreditCount'] += $row->cnt;
+ }
+ }
+
+ // 잔액 계산 및 정렬
+ $byVendor = collect($vendorMap)->map(function ($item) {
+ $item['balance'] = $item['occurred'] - $item['offset'];
+
+ return $item;
+ })->sortByDesc('balance')->values()->all();
+
+ // 4. 요약 통계
+ $totalOccurred = array_sum(array_column($byVendor, 'occurred'));
+ $totalOffset = array_sum(array_column($byVendor, 'offset'));
+
+ return response()->json([
+ 'success' => true,
+ 'data' => [
+ 'summary' => [
+ 'totalOccurred' => $totalOccurred,
+ 'totalOffset' => $totalOffset,
+ 'totalBalance' => $totalOccurred - $totalOffset,
+ 'hometaxCount' => $hometaxDetails->count(),
+ 'journalCount' => $journalDetails->count(),
+ 'vendorCount' => count($byVendor),
+ ],
+ 'byVendor' => $byVendor,
+ 'hometaxDetails' => $hometaxDetails,
+ 'journalDetails' => $journalDetails,
+ ],
+ ]);
+ }
+
+ /**
+ * 홈택스 매입세금계산서 미지급금/미지급비용 대변 내역
+ */
+ public function hometaxPayables(Request $request): JsonResponse
+ {
+ $tenantId = session('selected_tenant_id', 1);
+ $startDate = $request->input('startDate', date('Y-01-01'));
+ $endDate = $request->input('endDate', date('Y-12-31'));
+ $account = $request->input('account', 'all');
+
+ $accountCodes = $account === 'all' ? ['204', '205'] : [$account];
+
+ $items = HometaxInvoiceJournal::where('tenant_id', $tenantId)
+ ->whereIn('account_code', $accountCodes)
+ ->where('dc_type', 'credit')
+ ->where('credit_amount', '>', 0)
+ ->whereBetween('write_date', [$startDate, $endDate])
+ ->select('id', 'trading_partner_name', 'account_code', 'account_name', 'credit_amount', 'write_date', 'description', 'nts_confirm_num', 'supply_amount', 'tax_amount', 'total_amount')
+ ->orderByDesc('write_date')
+ ->get();
+
+ $totalCredit = $items->sum('credit_amount');
+
+ return response()->json([
+ 'success' => true,
+ 'data' => $items,
+ 'summary' => [
+ 'totalCredit' => $totalCredit,
+ 'count' => $items->count(),
+ ],
+ ]);
+ }
+
+ /**
+ * 일반전표 미지급금/미지급비용 내역 (차변=상계, 대변=발생)
+ */
+ public function journalPayables(Request $request): JsonResponse
+ {
+ $tenantId = session('selected_tenant_id', 1);
+ $startDate = $request->input('startDate', date('Y-01-01'));
+ $endDate = $request->input('endDate', date('Y-12-31'));
+ $account = $request->input('account', 'all');
+ $dcType = $request->input('dcType', 'all');
+
+ $accountCodes = $account === 'all' ? ['204', '205'] : [$account];
+
+ $query = JournalEntryLine::where('journal_entry_lines.tenant_id', $tenantId)
+ ->whereIn('journal_entry_lines.account_code', $accountCodes)
+ ->join('journal_entries', function ($join) {
+ $join->on('journal_entries.id', '=', 'journal_entry_lines.journal_entry_id')
+ ->whereNull('journal_entries.deleted_at');
+ })
+ ->whereBetween('journal_entries.entry_date', [$startDate, $endDate]);
+
+ if ($dcType !== 'all') {
+ $query->where('journal_entry_lines.dc_type', $dcType);
+ }
+
+ $items = $query->select(
+ 'journal_entry_lines.id',
+ 'journal_entry_lines.trading_partner_name',
+ 'journal_entry_lines.account_code',
+ 'journal_entry_lines.account_name',
+ 'journal_entry_lines.dc_type',
+ 'journal_entry_lines.debit_amount',
+ 'journal_entry_lines.credit_amount',
+ 'journal_entry_lines.description',
+ 'journal_entries.entry_date',
+ 'journal_entries.entry_no'
+ )
+ ->orderByDesc('journal_entries.entry_date')
+ ->get();
+
+ $totalDebit = $items->sum('debit_amount');
+ $totalCredit = $items->sum('credit_amount');
+
+ return response()->json([
+ 'success' => true,
+ 'data' => $items,
+ 'summary' => [
+ 'totalDebit' => $totalDebit,
+ 'totalCredit' => $totalCredit,
+ 'count' => $items->count(),
+ ],
+ ]);
+ }
}
diff --git a/resources/views/finance/payables.blade.php b/resources/views/finance/payables.blade.php
index 9803acab..b4bb36cd 100644
--- a/resources/views/finance/payables.blade.php
+++ b/resources/views/finance/payables.blade.php
@@ -18,7 +18,7 @@
@verbatim
diff --git a/routes/web.php b/routes/web.php
index 43ec461a..3a8700df 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -1197,6 +1197,9 @@
// 미지급금 관리 API
Route::prefix('payables')->name('payables.')->group(function () {
Route::get('/list', [\App\Http\Controllers\Finance\PayableController::class, 'index'])->name('list');
+ Route::get('/integrated', [\App\Http\Controllers\Finance\PayableController::class, 'integrated'])->name('integrated');
+ Route::get('/hometax', [\App\Http\Controllers\Finance\PayableController::class, 'hometaxPayables'])->name('hometax');
+ Route::get('/journals', [\App\Http\Controllers\Finance\PayableController::class, 'journalPayables'])->name('journals');
Route::post('/store', [\App\Http\Controllers\Finance\PayableController::class, 'store'])->name('store');
Route::put('/{id}', [\App\Http\Controllers\Finance\PayableController::class, 'update'])->name('update');
Route::post('/{id}/pay', [\App\Http\Controllers\Finance\PayableController::class, 'pay'])->name('pay');