input('period'); // Step 1: 기간 → 날짜 범위 변환 [$startDate, $endDate] = $period ? $this->periodToDateRange($period) : [null, null]; $startDateYmd = $startDate ? str_replace('-', '', $startDate) : null; $endDateYmd = $endDate ? str_replace('-', '', $endDate) : null; $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', ]; }); // Step 3: 홈택스 매입 조회 $hometaxPurchases = HometaxInvoice::where('tenant_id', $tenantId) ->purchase() ->period($startDate, $endDate) ->get(); $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[$card->unique_key] ?? null; if ($splits && count($splits) > 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', ]); } } } } } // Step 5: 수동 입력 (vat_records) - 기존 유지 $manualQuery = VatRecord::forTenant($tenantId); if ($period) { $manualQuery->where('period', $period); } $manualRecords = $manualQuery->orderBy('invoice_date', 'desc') ->get() ->map(function ($record) { return [ 'id' => $record->id, 'period' => $record->period, 'type' => $record->type, 'taxType' => $record->tax_type ?? 'taxable', 'partnerName' => $record->partner_name, 'invoiceNo' => $record->invoice_no, 'invoiceDate' => $record->invoice_date?->format('Y-m-d'), 'supplyAmount' => $record->supply_amount, 'vatAmount' => $record->vat_amount, 'totalAmount' => $record->total_amount, 'status' => $record->status, 'memo' => $record->memo, 'isCardTransaction' => false, 'isHometax' => false, 'source' => 'manual', ]; }); // Step 6: 통합 및 통계 $allRecords = $hometaxSalesRecords ->concat($hometaxPurchaseRecords) ->concat($cardRecords) ->concat($manualRecords) ->values(); // 홈택스 매출 (과세 + 영세만, 면세 제외) $hometaxSalesTaxable = $hometaxSalesRecords->whereIn('taxType', ['taxable', 'zero_rated']); $hometaxSalesSupply = $hometaxSalesTaxable->sum('supplyAmount'); $hometaxSalesVat = $hometaxSalesTaxable->sum('vatAmount'); // 홈택스 매입 세금계산서 (과세 + 영세만, 면세 제외) $hometaxPurchaseTaxable = $hometaxPurchaseRecords->whereIn('taxType', ['taxable', 'zero_rated']); $hometaxPurchaseSupply = $hometaxPurchaseTaxable->sum('supplyAmount'); $hometaxPurchaseVat = $hometaxPurchaseTaxable->sum('vatAmount'); // 홈택스 면세 계산서 (매입 + 매출 모두) $exemptSalesSupply = $hometaxSalesRecords->where('taxType', 'exempt')->sum('supplyAmount'); $exemptPurchaseSupply = $hometaxPurchaseRecords->where('taxType', 'exempt')->sum('supplyAmount'); $exemptSupply = $exemptSalesSupply + $exemptPurchaseSupply; $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' => $hometaxSalesSupply + $manualSalesSupply, 'salesVat' => $hometaxSalesVat + $manualSalesVat, 'purchaseSupply' => $hometaxPurchaseSupply + $cardPurchaseSupply + $manualPurchaseSupply, 'purchaseVat' => $hometaxPurchaseVat + $cardPurchaseVat + $manualPurchaseVat, 'hometaxPurchaseSupply' => $hometaxPurchaseSupply, 'hometaxPurchaseVat' => $hometaxPurchaseVat, 'exemptSupply' => $exemptSupply, // 면세 계산서 공급가액 'cardPurchaseSupply' => $cardPurchaseSupply, 'cardPurchaseVat' => $cardPurchaseVat, 'total' => $allRecords->count(), ]; // 사용 중인 기간 목록 $periods = VatRecord::forTenant($tenantId) ->select('period') ->distinct() ->orderBy('period', 'desc') ->pluck('period') ->toArray(); return response()->json([ 'success' => true, 'data' => $allRecords, 'stats' => $stats, 'periods' => $periods, ]); } public function store(Request $request): JsonResponse { $request->validate([ 'partnerName' => 'required|string|max:100', 'period' => 'required|string|max:20', 'type' => 'required|in:sales,purchase', 'taxType' => 'nullable|in:taxable,zero_rated,exempt', 'supplyAmount' => 'required|integer|min:0', ]); $tenantId = session('selected_tenant_id', 1); $record = VatRecord::create([ 'tenant_id' => $tenantId, 'period' => $request->input('period'), 'type' => $request->input('type', 'sales'), 'tax_type' => $request->input('taxType', 'taxable'), 'partner_name' => $request->input('partnerName'), 'invoice_no' => $request->input('invoiceNo'), 'invoice_date' => $request->input('invoiceDate'), 'supply_amount' => $request->input('supplyAmount', 0), 'vat_amount' => $request->input('vatAmount', 0), 'total_amount' => $request->input('totalAmount', 0), 'status' => $request->input('status', 'pending'), 'memo' => $request->input('memo'), ]); return response()->json([ 'success' => true, 'message' => '세금계산서가 등록되었습니다.', 'data' => ['id' => $record->id], ]); } public function update(Request $request, int $id): JsonResponse { $tenantId = session('selected_tenant_id', 1); $record = VatRecord::forTenant($tenantId)->findOrFail($id); $request->validate([ 'partnerName' => 'required|string|max:100', 'period' => 'required|string|max:20', 'type' => 'required|in:sales,purchase', 'taxType' => 'nullable|in:taxable,zero_rated,exempt', 'supplyAmount' => 'required|integer|min:0', ]); $record->update([ 'period' => $request->input('period'), 'type' => $request->input('type'), 'tax_type' => $request->input('taxType', $record->tax_type), 'partner_name' => $request->input('partnerName'), 'invoice_no' => $request->input('invoiceNo'), 'invoice_date' => $request->input('invoiceDate'), 'supply_amount' => $request->input('supplyAmount', 0), 'vat_amount' => $request->input('vatAmount', 0), 'total_amount' => $request->input('totalAmount', 0), 'status' => $request->input('status'), 'memo' => $request->input('memo'), ]); return response()->json([ 'success' => true, 'message' => '세금계산서가 수정되었습니다.', ]); } public function destroy(int $id): JsonResponse { $tenantId = session('selected_tenant_id', 1); $record = VatRecord::forTenant($tenantId)->findOrFail($id); $record->delete(); return response()->json([ 'success' => true, 'message' => '세금계산서가 삭제되었습니다.', ]); } /** * 부가세 신고기간을 날짜 범위로 변환 * YYYY-1P: 1기 예정 (0101~0331) * YYYY-1C: 1기 확정 (0401~0630) * YYYY-2P: 2기 예정 (0701~0930) * YYYY-2C: 2기 확정 (1001~1231) */ private function periodToDateRange(string $period): array { $parts = explode('-', $period); if (count($parts) !== 2) { return [null, null]; } $year = $parts[0]; $code = $parts[1]; $ranges = [ '1P' => ["{$year}-01-01", "{$year}-03-31"], '1C' => ["{$year}-04-01", "{$year}-06-30"], '2P' => ["{$year}-07-01", "{$year}-09-30"], '2C' => ["{$year}-10-01", "{$year}-12-31"], ]; return $ranges[$code] ?? [null, null]; } }