feat:부가세 관리 구분 6개로 확장 (전자/종이세금계산서 분리)

- 매출(세금계산서) → 매출(전자세금계산서) + 매출(종이세금계산서) 분리
- 매입(세금계산서) → 매입(전자세금계산서) + 매입(종이세금계산서) 분리
- 매입(카드) → 매입(신용카드) 명칭 변경
- 요약 테이블 6행으로 확장, 필터 드롭다운 업데이트
- 컨트롤러 stats에 hometaxSales/manualSales/manualPurchase 분리 반환

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
김보곤
2026-02-09 09:46:21 +09:00
parent 4ed902e846
commit b204e73ada
2 changed files with 64 additions and 23 deletions

View File

@@ -195,12 +195,12 @@ public function index(Request $request): JsonResponse
->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');
@@ -208,23 +208,42 @@ public function index(Request $request): JsonResponse
// 홈택스 면세 계산서 (매입 + 매출 모두)
$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');
// 수동입력 매출 종이세금계산서 (과세+영세)
$manualSalesTaxable = $manualRecords->where('type', 'sales')->whereIn('taxType', ['taxable', 'zero_rated']);
$manualSalesSupply = $manualSalesTaxable->sum('supplyAmount');
$manualSalesVat = $manualSalesTaxable->sum('vatAmount');
// 수동입력 매입 종이세금계산서 (과세+영세)
$manualPurchaseTaxable = $manualRecords->where('type', 'purchase')->whereIn('taxType', ['taxable', 'zero_rated']);
$manualPurchaseSupply = $manualPurchaseTaxable->sum('supplyAmount');
$manualPurchaseVat = $manualPurchaseTaxable->sum('vatAmount');
// 수동입력 면세 계산서
$manualExemptSalesSupply = $manualRecords->where('type', 'sales')->where('taxType', 'exempt')->sum('supplyAmount');
$manualExemptPurchaseSupply = $manualRecords->where('type', 'purchase')->where('taxType', 'exempt')->sum('supplyAmount');
// 면세 계산서 합계 (홈택스 + 수동)
$exemptSupply = $exemptSalesSupply + $exemptPurchaseSupply + $manualExemptSalesSupply + $manualExemptPurchaseSupply;
$stats = [
'salesSupply' => $hometaxSalesSupply + $manualSalesSupply,
'salesVat' => $hometaxSalesVat + $manualSalesVat,
'purchaseSupply' => $hometaxPurchaseSupply + $cardPurchaseSupply + $manualPurchaseSupply,
'purchaseVat' => $hometaxPurchaseVat + $cardPurchaseVat + $manualPurchaseVat,
'hometaxSalesSupply' => $hometaxSalesSupply,
'hometaxSalesVat' => $hometaxSalesVat,
'manualSalesSupply' => $manualSalesSupply,
'manualSalesVat' => $manualSalesVat,
'hometaxPurchaseSupply' => $hometaxPurchaseSupply,
'hometaxPurchaseVat' => $hometaxPurchaseVat,
'exemptSupply' => $exemptSupply, // 면세 계산서 공급가액
'manualPurchaseSupply' => $manualPurchaseSupply,
'manualPurchaseVat' => $manualPurchaseVat,
'exemptSupply' => $exemptSupply,
'cardPurchaseSupply' => $cardPurchaseSupply,
'cardPurchaseVat' => $cardPurchaseVat,
'total' => $allRecords->count(),

View File

@@ -50,7 +50,7 @@
function VatManagement() {
const [vatRecords, setVatRecords] = useState([]);
const [stats, setStats] = useState({ salesSupply: 0, salesVat: 0, purchaseSupply: 0, purchaseVat: 0, hometaxPurchaseSupply: 0, hometaxPurchaseVat: 0, exemptSupply: 0, cardPurchaseSupply: 0, cardPurchaseVat: 0, total: 0 });
const [stats, setStats] = useState({ salesSupply: 0, salesVat: 0, purchaseSupply: 0, purchaseVat: 0, hometaxSalesSupply: 0, hometaxSalesVat: 0, manualSalesSupply: 0, manualSalesVat: 0, hometaxPurchaseSupply: 0, hometaxPurchaseVat: 0, manualPurchaseSupply: 0, manualPurchaseVat: 0, exemptSupply: 0, cardPurchaseSupply: 0, cardPurchaseVat: 0, total: 0 });
const [periods, setPeriods] = useState([]);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
@@ -157,8 +157,10 @@ function VatManagement() {
if (!matchesSearch || !matchesTaxType) return false;
if (filterType === 'all') return true;
if (filterType === 'hometax_sales') return item.type === 'sales' && item.taxType !== 'exempt';
if (filterType === 'hometax_purchase') return item.source === 'hometax' && item.type === 'purchase' && item.taxType !== 'exempt';
if (filterType === 'electronic_sales') return item.source === 'hometax' && item.type === 'sales' && item.taxType !== 'exempt';
if (filterType === 'paper_sales') return item.source === 'manual' && item.type === 'sales' && item.taxType !== 'exempt';
if (filterType === 'electronic_purchase') return item.source === 'hometax' && item.type === 'purchase' && item.taxType !== 'exempt';
if (filterType === 'paper_purchase') return item.source === 'manual' && item.type === 'purchase' && item.taxType !== 'exempt';
if (filterType === 'exempt_purchase') return item.taxType === 'exempt';
if (filterType === 'purchase_card') return item.source === 'card';
return true;
@@ -168,8 +170,14 @@ function VatManagement() {
const purchaseVat = stats.purchaseVat || 0;
const salesSupply = stats.salesSupply || 0;
const purchaseSupply = stats.purchaseSupply || 0;
const hometaxSalesSupply = stats.hometaxSalesSupply || 0;
const hometaxSalesVat = stats.hometaxSalesVat || 0;
const manualSalesSupply = stats.manualSalesSupply || 0;
const manualSalesVat = stats.manualSalesVat || 0;
const hometaxPurchaseSupply = stats.hometaxPurchaseSupply || 0;
const hometaxPurchaseVat = stats.hometaxPurchaseVat || 0;
const manualPurchaseSupply = stats.manualPurchaseSupply || 0;
const manualPurchaseVat = stats.manualPurchaseVat || 0;
const exemptSupply = stats.exemptSupply || 0;
const cardPurchaseSupply = stats.cardPurchaseSupply || 0;
const cardPurchaseVat = stats.cardPurchaseVat || 0;
@@ -246,11 +254,11 @@ function VatManagement() {
};
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 === 'hometax' && type === 'sales') return '매출(전자)';
if (source === 'hometax' && type === 'purchase') return '매입(전자)';
if (source === 'card' || isCard) return '매입(신용카드)';
if (source === 'manual') {
return type === 'sales' ? '매출(수동)' : '매입(수동)';
return type === 'sales' ? '매출(종이)' : '매입(종이)';
}
const labels = { 'sales': '매출', 'purchase': '매입' };
return labels[type] || type;
@@ -288,6 +296,8 @@ function VatManagement() {
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';
if (source === 'manual' && type === 'sales') return 'bg-emerald-50 text-emerald-600 border border-emerald-200';
if (source === 'manual' && type === 'purchase') return 'bg-pink-50 text-pink-600 border border-pink-200';
const styles = {
'sales': 'bg-emerald-50 text-emerald-600',
'purchase': 'bg-pink-50 text-pink-600'
@@ -364,22 +374,32 @@ function VatManagement() {
</thead>
<tbody>
<tr className="border-b border-gray-100">
<td className="px-6 py-3 text-sm">매출(세금계산서)</td>
<td className="px-6 py-3 text-sm text-right">{formatCurrency(salesSupply)}</td>
<td className="px-6 py-3 text-sm text-right text-emerald-600 font-medium">{formatCurrency(salesVat)}</td>
<td className="px-6 py-3 text-sm">매출(전자세금계산서)</td>
<td className="px-6 py-3 text-sm text-right">{formatCurrency(hometaxSalesSupply)}</td>
<td className="px-6 py-3 text-sm text-right text-emerald-600 font-medium">{formatCurrency(hometaxSalesVat)}</td>
</tr>
<tr className="border-b border-gray-100 bg-emerald-50/30">
<td className="px-6 py-3 text-sm">매출(종이세금계산서)</td>
<td className="px-6 py-3 text-sm text-right">{formatCurrency(manualSalesSupply)}</td>
<td className="px-6 py-3 text-sm text-right text-emerald-600 font-medium">{formatCurrency(manualSalesVat)}</td>
</tr>
<tr className="border-b border-gray-100">
<td className="px-6 py-3 text-sm">매입(세금계산서)</td>
<td className="px-6 py-3 text-sm">매입(전자세금계산서)</td>
<td className="px-6 py-3 text-sm text-right">{formatCurrency(hometaxPurchaseSupply)}</td>
<td className="px-6 py-3 text-sm text-right text-pink-600 font-medium">({formatCurrency(hometaxPurchaseVat)})</td>
</tr>
<tr className="border-b border-gray-100 bg-pink-50/30">
<td className="px-6 py-3 text-sm">매입(종이세금계산서)</td>
<td className="px-6 py-3 text-sm text-right">{formatCurrency(manualPurchaseSupply)}</td>
<td className="px-6 py-3 text-sm text-right text-pink-600 font-medium">({formatCurrency(manualPurchaseVat)})</td>
</tr>
<tr className="border-b border-gray-100 bg-gray-50/50">
<td className="px-6 py-3 text-sm text-gray-600">매입(계산서)</td>
<td className="px-6 py-3 text-sm text-right text-gray-600">{formatCurrency(exemptSupply)}</td>
<td className="px-6 py-3 text-sm text-right text-gray-400">-</td>
</tr>
<tr className="border-b border-gray-100 bg-purple-50/50">
<td className="px-6 py-3 text-sm text-purple-700">매입(카드)</td>
<td className="px-6 py-3 text-sm text-purple-700">매입(신용카드)</td>
<td className="px-6 py-3 text-sm text-right text-purple-600">{formatCurrency(cardPurchaseSupply)}</td>
<td className="px-6 py-3 text-sm text-right text-purple-600 font-medium">({formatCurrency(cardPurchaseVat)})</td>
</tr>
@@ -400,10 +420,12 @@ function VatManagement() {
</div>
<select value={filterType} onChange={(e) => setFilterType(e.target.value)} className="px-3 py-2 border border-gray-300 rounded-lg">
<option value="all">전체 유형</option>
<option value="hometax_sales">매출(세금계산서)</option>
<option value="hometax_purchase">(세금계산서)</option>
<option value="electronic_sales">매출(전자세금계산서)</option>
<option value="paper_sales">(종이세금계산서)</option>
<option value="electronic_purchase">매입(전자세금계산서)</option>
<option value="paper_purchase">매입(종이세금계산서)</option>
<option value="exempt_purchase">매입(계산서)</option>
<option value="purchase_card">매입(카드)</option>
<option value="purchase_card">매입(신용카드)</option>
</select>
<select value={filterTaxType} onChange={(e) => setFilterTaxType(e.target.value)} className="px-3 py-2 border border-gray-300 rounded-lg">
<option value="all">전체 세금구분</option>