diff --git a/app/Http/Controllers/Finance/VatRecordController.php b/app/Http/Controllers/Finance/VatRecordController.php index bfdd2c3b..4e8c2565 100644 --- a/app/Http/Controllers/Finance/VatRecordController.php +++ b/app/Http/Controllers/Finance/VatRecordController.php @@ -4,7 +4,10 @@ use App\Http\Controllers\Controller; use App\Models\Finance\VatRecord; -use App\Models\Finance\CardTransaction; +use App\Models\Barobill\CardTransaction as BarobillCardTransaction; +use App\Models\Barobill\CardTransactionSplit; +use App\Models\Barobill\CardTransactionHide; +use App\Models\Barobill\HometaxInvoice; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -13,38 +16,157 @@ class VatRecordController extends Controller public function index(Request $request): JsonResponse { $tenantId = session('selected_tenant_id', 1); + $period = $request->input('period'); - $query = VatRecord::forTenant($tenantId); + // Step 1: 기간 → 날짜 범위 변환 + [$startDate, $endDate] = $period ? $this->periodToDateRange($period) : [null, null]; + $startDateYmd = $startDate ? str_replace('-', '', $startDate) : null; + $endDateYmd = $endDate ? str_replace('-', '', $endDate) : null; - if ($search = $request->input('search')) { - $query->where(function ($q) use ($search) { - $q->where('partner_name', 'like', "%{$search}%"); + $taxTypeMap = [ + 1 => 'taxable', + 2 => 'zero_rated', + 3 => 'exempt', + ]; + + // Step 2: 홈택스 매출 조회 + $hometaxSalesRecords = collect(); + $hometaxPurchaseRecords = collect(); + $cardRecords = collect(); + + if ($startDate && $endDate) { + // 홈택스 매출 + $hometaxSales = HometaxInvoice::where('tenant_id', $tenantId) + ->sales() + ->period($startDate, $endDate) + ->get(); + + $hometaxSalesRecords = $hometaxSales->map(function ($inv) use ($period, $taxTypeMap) { + return [ + 'id' => 'hometax_' . $inv->id, + 'period' => $period, + 'type' => 'sales', + 'taxType' => $taxTypeMap[$inv->tax_type] ?? 'taxable', + 'partnerName' => $inv->invoicee_corp_name, + 'invoiceNo' => $inv->nts_confirm_num ?? '', + 'invoiceDate' => $inv->write_date ? \Carbon\Carbon::parse($inv->write_date)->format('Y-m-d') : null, + 'supplyAmount' => (int) $inv->supply_amount, + 'vatAmount' => (int) $inv->tax_amount, + 'totalAmount' => (int) $inv->total_amount, + 'status' => 'filed', + 'memo' => null, + 'isCardTransaction' => false, + 'isHometax' => true, + 'source' => 'hometax', + ]; }); - } - if ($period = $request->input('period')) { - $query->where('period', $period); - } + // Step 3: 홈택스 매입 조회 + $hometaxPurchases = HometaxInvoice::where('tenant_id', $tenantId) + ->purchase() + ->period($startDate, $endDate) + ->get(); - if ($type = $request->input('type')) { - if ($type !== 'all') { - $query->where('type', $type); + $hometaxPurchaseRecords = $hometaxPurchases->map(function ($inv) use ($period, $taxTypeMap) { + return [ + 'id' => 'hometax_' . $inv->id, + 'period' => $period, + 'type' => 'purchase', + 'taxType' => $taxTypeMap[$inv->tax_type] ?? 'taxable', + 'partnerName' => $inv->invoicer_corp_name, + 'invoiceNo' => $inv->nts_confirm_num ?? '', + 'invoiceDate' => $inv->write_date ? \Carbon\Carbon::parse($inv->write_date)->format('Y-m-d') : null, + 'supplyAmount' => (int) $inv->supply_amount, + 'vatAmount' => (int) $inv->tax_amount, + 'totalAmount' => (int) $inv->total_amount, + 'status' => 'filed', + 'memo' => null, + 'isCardTransaction' => false, + 'isHometax' => true, + 'source' => 'hometax', + ]; + }); + + // Step 4: 카드 공제분 조회 + if ($startDateYmd && $endDateYmd) { + $hiddenKeys = CardTransactionHide::getHiddenKeys($tenantId, $startDateYmd, $endDateYmd); + + $cardTransactions = BarobillCardTransaction::where('tenant_id', $tenantId) + ->whereBetween('use_date', [$startDateYmd, $endDateYmd]) + ->orderBy('use_date', 'desc') + ->get(); + + $splitsByKey = CardTransactionSplit::getByDateRange($tenantId, $startDateYmd, $endDateYmd); + + foreach ($cardTransactions as $card) { + // 숨김 처리된 거래는 skip + if ($hiddenKeys->contains($card->unique_key)) { + continue; + } + + $splits = $splitsByKey->get($card->unique_key); + + if ($splits && $splits->count() > 0) { + // 분개가 있으면: deductible 분개만 포함 + foreach ($splits as $split) { + if ($split->deduction_type === 'deductible') { + $cardRecords->push([ + 'id' => 'card_split_' . $split->id, + 'period' => $period, + 'type' => 'purchase', + 'taxType' => 'taxable', + 'partnerName' => $card->merchant_name, + 'invoiceNo' => $card->approval_num ?? '', + 'invoiceDate' => $card->use_date ? \Carbon\Carbon::createFromFormat('Ymd', $card->use_date)->format('Y-m-d') : null, + 'supplyAmount' => (int) $split->split_supply_amount, + 'vatAmount' => (int) $split->split_tax, + 'totalAmount' => (int) $split->split_amount, + 'status' => 'filed', + 'memo' => $split->account_name ?? null, + 'isCardTransaction' => true, + 'isHometax' => false, + 'source' => 'card', + ]); + } + } + } else { + // 분개가 없으면: deduction_type='deductible'인 경우만 포함 + if ($card->deduction_type === 'deductible') { + $effectiveSupply = $card->modified_supply_amount + ?? ($card->approval_amount - $card->tax); + $effectiveTax = $card->modified_tax ?? $card->tax; + + $cardRecords->push([ + 'id' => 'card_' . $card->id, + 'period' => $period, + 'type' => 'purchase', + 'taxType' => 'taxable', + 'partnerName' => $card->merchant_name, + 'invoiceNo' => $card->approval_num ?? '', + 'invoiceDate' => $card->use_date ? \Carbon\Carbon::createFromFormat('Ymd', $card->use_date)->format('Y-m-d') : null, + 'supplyAmount' => (int) $effectiveSupply, + 'vatAmount' => (int) $effectiveTax, + 'totalAmount' => (int) ($effectiveSupply + $effectiveTax), + 'status' => 'filed', + 'memo' => $card->memo, + 'isCardTransaction' => true, + 'isHometax' => false, + 'source' => 'card', + ]); + } + } + } } } - if ($taxType = $request->input('tax_type')) { - if ($taxType !== 'all') { - $query->where('tax_type', $taxType); - } + // Step 5: 수동 입력 (vat_records) - 기존 유지 + $manualQuery = VatRecord::forTenant($tenantId); + + if ($period) { + $manualQuery->where('period', $period); } - if ($status = $request->input('status')) { - if ($status !== 'all') { - $query->where('status', $status); - } - } - - $records = $query->orderBy('invoice_date', 'desc') + $manualRecords = $manualQuery->orderBy('invoice_date', 'desc') ->get() ->map(function ($record) { return [ @@ -61,67 +183,39 @@ public function index(Request $request): JsonResponse 'status' => $record->status, 'memo' => $record->memo, 'isCardTransaction' => false, + 'isHometax' => false, + 'source' => 'manual', ]; }); - // 카드 공제분 매입 반영 - $cardRecords = collect(); - $cardPurchaseSupply = 0; - $cardPurchaseVat = 0; + // Step 6: 통합 및 통계 + $allRecords = $hometaxSalesRecords + ->concat($hometaxPurchaseRecords) + ->concat($cardRecords) + ->concat($manualRecords) + ->values(); - if ($period = $request->input('period')) { - [$startDate, $endDate] = $this->periodToDateRange($period); - - if ($startDate && $endDate) { - $cardQuery = CardTransaction::forTenant($tenantId) - ->where('deduction_type', 'deductible') - ->whereBetween('transaction_date', [$startDate, $endDate]); - - $cardTransactions = $cardQuery->orderBy('transaction_date', 'desc')->get(); - - $cardRecords = $cardTransactions->map(function ($card) use ($period) { - $supply = (int) round($card->amount / 1.1); - $vat = $card->amount - $supply; - return [ - 'id' => 'card_' . $card->id, - 'period' => $period, - 'type' => 'purchase', - 'taxType' => 'taxable', - 'partnerName' => $card->merchant, - 'invoiceNo' => $card->approval_no ?? '', - 'invoiceDate' => $card->transaction_date?->format('Y-m-d'), - 'supplyAmount' => $supply, - 'vatAmount' => $vat, - 'totalAmount' => $card->amount, - 'status' => 'filed', - 'memo' => $card->memo, - 'isCardTransaction' => true, - ]; - }); - - $cardPurchaseSupply = $cardRecords->sum('supplyAmount'); - $cardPurchaseVat = $cardRecords->sum('vatAmount'); - } - } - - // 통합 records - $allRecords = $records->concat($cardRecords)->values(); - - // 해당 기간 통계 - $periodQuery = VatRecord::forTenant($tenantId); - if ($period = $request->input('period')) { - $periodQuery->where('period', $period); - } - $periodRecords = $periodQuery->get(); + $hometaxSalesSupply = $hometaxSalesRecords->sum('supplyAmount'); + $hometaxSalesVat = $hometaxSalesRecords->sum('vatAmount'); + $hometaxPurchaseSupply = $hometaxPurchaseRecords->sum('supplyAmount'); + $hometaxPurchaseVat = $hometaxPurchaseRecords->sum('vatAmount'); + $cardPurchaseSupply = $cardRecords->sum('supplyAmount'); + $cardPurchaseVat = $cardRecords->sum('vatAmount'); + $manualSalesSupply = $manualRecords->where('type', 'sales')->sum('supplyAmount'); + $manualSalesVat = $manualRecords->where('type', 'sales')->sum('vatAmount'); + $manualPurchaseSupply = $manualRecords->where('type', 'purchase')->sum('supplyAmount'); + $manualPurchaseVat = $manualRecords->where('type', 'purchase')->sum('vatAmount'); $stats = [ - 'salesSupply' => $periodRecords->where('type', 'sales')->sum('supply_amount'), - 'salesVat' => $periodRecords->where('type', 'sales')->sum('vat_amount'), - 'purchaseSupply' => $periodRecords->where('type', 'purchase')->sum('supply_amount') + $cardPurchaseSupply, - 'purchaseVat' => $periodRecords->where('type', 'purchase')->sum('vat_amount') + $cardPurchaseVat, + 'salesSupply' => $hometaxSalesSupply + $manualSalesSupply, + 'salesVat' => $hometaxSalesVat + $manualSalesVat, + 'purchaseSupply' => $hometaxPurchaseSupply + $cardPurchaseSupply + $manualPurchaseSupply, + 'purchaseVat' => $hometaxPurchaseVat + $cardPurchaseVat + $manualPurchaseVat, + 'hometaxPurchaseSupply' => $hometaxPurchaseSupply, + 'hometaxPurchaseVat' => $hometaxPurchaseVat, 'cardPurchaseSupply' => $cardPurchaseSupply, 'cardPurchaseVat' => $cardPurchaseVat, - 'total' => $periodRecords->count(), + 'total' => $allRecords->count(), ]; // 사용 중인 기간 목록 diff --git a/resources/views/finance/vat.blade.php b/resources/views/finance/vat.blade.php index d58a695f..117801fa 100644 --- a/resources/views/finance/vat.blade.php +++ b/resources/views/finance/vat.blade.php @@ -49,7 +49,7 @@ function VatManagement() { const [vatRecords, setVatRecords] = useState([]); - const [stats, setStats] = useState({ salesSupply: 0, salesVat: 0, purchaseSupply: 0, purchaseVat: 0, cardPurchaseSupply: 0, cardPurchaseVat: 0, total: 0 }); + const [stats, setStats] = useState({ salesSupply: 0, salesVat: 0, purchaseSupply: 0, purchaseVat: 0, hometaxPurchaseSupply: 0, hometaxPurchaseVat: 0, cardPurchaseSupply: 0, cardPurchaseVat: 0, total: 0 }); const [periods, setPeriods] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); @@ -152,26 +152,31 @@ function VatManagement() { const filteredRecords = vatRecords.filter(item => { const matchesSearch = item.partnerName.toLowerCase().includes(searchTerm.toLowerCase()); - const matchesType = filterType === 'all' || item.type === filterType || (filterType === 'purchase_card' && item.isCardTransaction); const matchesTaxType = filterTaxType === 'all' || item.taxType === filterTaxType; const matchesStatus = filterStatus === 'all' || item.status === filterStatus; - if (filterType === 'purchase_card') return item.isCardTransaction && matchesSearch && matchesTaxType && matchesStatus; - if (filterType === 'purchase' && !item.isCardTransaction) return item.type === 'purchase' && matchesSearch && matchesTaxType && matchesStatus; - if (filterType !== 'all' && filterType !== 'purchase_card') return item.type === filterType && !item.isCardTransaction && matchesSearch && matchesTaxType && matchesStatus; - return matchesSearch && matchesTaxType && matchesStatus; + if (!matchesSearch || !matchesTaxType || !matchesStatus) return false; + + if (filterType === 'all') return true; + if (filterType === 'hometax_sales') return item.source === 'hometax' && item.type === 'sales'; + if (filterType === 'hometax_purchase') return item.source === 'hometax' && item.type === 'purchase'; + if (filterType === 'purchase_card') return item.source === 'card'; + if (filterType === 'manual') return item.source === 'manual'; + return true; }); const salesVat = stats.salesVat || 0; const purchaseVat = stats.purchaseVat || 0; const salesSupply = stats.salesSupply || 0; const purchaseSupply = stats.purchaseSupply || 0; + const hometaxPurchaseSupply = stats.hometaxPurchaseSupply || 0; + const hometaxPurchaseVat = stats.hometaxPurchaseVat || 0; const cardPurchaseSupply = stats.cardPurchaseSupply || 0; const cardPurchaseVat = stats.cardPurchaseVat || 0; const netVat = salesVat - purchaseVat; const handleAdd = () => { setModalMode('add'); setFormData({ ...initialFormState, period: filterPeriod }); setShowModal(true); }; const handleEdit = (item) => { - if (item.isCardTransaction) return; + if (item.isCardTransaction || item.isHometax) return; setModalMode('edit'); setEditingItem(item); setFormData({ @@ -233,14 +238,19 @@ function VatManagement() { const handleDownload = () => { const rows = [['부가세 관리', getPeriodLabel(filterPeriod)], [], ['구분', '세금구분', '거래처', '작성일자', '공급가액', '부가세', '합계', '상태'], - ...filteredRecords.map(item => [getTypeLabel(item.type, item.isCardTransaction), getTaxTypeLabel(item.taxType), item.partnerName, item.invoiceDate, item.supplyAmount, item.vatAmount, item.totalAmount, getStatusLabel(item.status)])]; + ...filteredRecords.map(item => [getTypeLabel(item.type, item.isCardTransaction, item.source), getTaxTypeLabel(item.taxType), item.partnerName, item.invoiceDate, item.supplyAmount, item.vatAmount, item.totalAmount, getStatusLabel(item.status)])]; const csvContent = rows.map(row => row.join(',')).join('\n'); const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = `부가세관리_${filterPeriod}.csv`; link.click(); }; - const getTypeLabel = (type, isCard = false) => { - if (isCard) return '매입(카드)'; + const getTypeLabel = (type, isCard = false, source = 'manual') => { + if (source === 'hometax' && type === 'sales') return '매출'; + if (source === 'hometax' && type === 'purchase') return '매입'; + if (source === 'card' || isCard) return '매입(카드)'; + if (source === 'manual') { + return type === 'sales' ? '매출(수동)' : '매입(수동)'; + } const labels = { 'sales': '매출', 'purchase': '매입' }; return labels[type] || type; }; @@ -273,11 +283,13 @@ function VatManagement() { return styles[status] || 'bg-gray-100 text-gray-700'; }; - const getTypeStyle = (type, isCard = false) => { - if (isCard) return 'bg-purple-100 text-purple-700'; + const getTypeStyle = (type, isCard = false, source = 'manual') => { + if (source === 'card' || isCard) return 'bg-purple-100 text-purple-700'; + if (source === 'hometax' && type === 'sales') return 'bg-emerald-100 text-emerald-700'; + if (source === 'hometax' && type === 'purchase') return 'bg-pink-100 text-pink-700'; const styles = { - 'sales': 'bg-emerald-100 text-emerald-700', - 'purchase': 'bg-pink-100 text-pink-700' + 'sales': 'bg-emerald-50 text-emerald-600', + 'purchase': 'bg-pink-50 text-pink-600' }; return styles[type] || 'bg-gray-100 text-gray-700'; }; @@ -298,7 +310,7 @@ function VatManagement() {