feat:부가세관리 외부 데이터 소스 연동 (홈택스/바로빌 카드)

홈택스 매출/매입 세금계산서 + 바로빌 카드 공제분 자동 조회 기능 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
김보곤
2026-02-05 19:44:13 +09:00
parent e49af062bc
commit 600ba52dc9
2 changed files with 215 additions and 101 deletions

View File

@@ -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(),
];
// 사용 중인 기간 목록