Files
sam-manage/app/Http/Controllers/Finance/VatRecordController.php

343 lines
14 KiB
PHP
Raw Normal View History

<?php
namespace App\Http\Controllers\Finance;
use App\Http\Controllers\Controller;
use App\Models\Finance\VatRecord;
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;
class VatRecordController extends Controller
{
public function index(Request $request): JsonResponse
{
$tenantId = session('selected_tenant_id', 1);
$period = $request->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->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',
]);
}
}
}
}
}
// 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();
$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' => $hometaxSalesSupply + $manualSalesSupply,
'salesVat' => $hometaxSalesVat + $manualSalesVat,
'purchaseSupply' => $hometaxPurchaseSupply + $cardPurchaseSupply + $manualPurchaseSupply,
'purchaseVat' => $hometaxPurchaseVat + $cardPurchaseVat + $manualPurchaseVat,
'hometaxPurchaseSupply' => $hometaxPurchaseSupply,
'hometaxPurchaseVat' => $hometaxPurchaseVat,
'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];
}
}