feat: [calendar,vat] 캘린더 CRUD 및 부가세 상세 조회 API 추가

- CalendarController/Service: 일정 등록/수정/삭제 API 추가
- VatController/Service: getDetail() 상세 조회 (요약, 참조테이블, 미발행 목록, 신고기간 옵션)
- 라우트: POST/PUT/DELETE /calendar/schedules, GET /vat/detail 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-03-04 20:33:04 +09:00
parent 2f3ec13b24
commit 74a60e06bc
5 changed files with 275 additions and 0 deletions

View File

@@ -237,6 +237,139 @@ private function getPeriodLabel(int $year, string $periodType, int $period): str
};
}
/**
* 부가세 상세 조회 (모달용)
*
* @param string|null $periodType 기간 타입 (quarter|half|year)
* @param int|null $year 연도
* @param int|null $period 기간 번호
* @return array
*/
public function getDetail(?string $periodType = 'quarter', ?int $year = null, ?int $period = null): array
{
$tenantId = $this->tenantId();
$now = Carbon::now();
$year = $year ?? $now->year;
$periodType = $periodType ?? 'quarter';
$period = $period ?? $this->getCurrentPeriod($periodType, $now);
[$startDate, $endDate] = $this->getPeriodDateRange($year, $periodType, $period);
$periodLabel = $this->getPeriodLabel($year, $periodType, $period);
$validStatuses = [TaxInvoice::STATUS_ISSUED, TaxInvoice::STATUS_SENT];
// 매출 공급가액 + 세액
$salesData = TaxInvoice::where('tenant_id', $tenantId)
->where('direction', TaxInvoice::DIRECTION_SALES)
->whereIn('status', $validStatuses)
->whereBetween('issue_date', [$startDate, $endDate])
->selectRaw('COALESCE(SUM(supply_amount), 0) as supply_amount, COALESCE(SUM(tax_amount), 0) as tax_amount')
->first();
// 매입 공급가액 + 세액
$purchasesData = TaxInvoice::where('tenant_id', $tenantId)
->where('direction', TaxInvoice::DIRECTION_PURCHASES)
->whereIn('status', $validStatuses)
->whereBetween('issue_date', [$startDate, $endDate])
->selectRaw('COALESCE(SUM(supply_amount), 0) as supply_amount, COALESCE(SUM(tax_amount), 0) as tax_amount')
->first();
$salesSupplyAmount = (int) ($salesData->supply_amount ?? 0);
$salesTaxAmount = (int) ($salesData->tax_amount ?? 0);
$purchasesSupplyAmount = (int) ($purchasesData->supply_amount ?? 0);
$purchasesTaxAmount = (int) ($purchasesData->tax_amount ?? 0);
$estimatedPayment = $salesTaxAmount - $purchasesTaxAmount;
// 신고기간 옵션 생성
$periodOptions = $this->generatePeriodOptions($year, $periodType, $period);
// 부가세 요약 테이블 (direction + invoice_type 별 GROUP BY)
$referenceTable = TaxInvoice::where('tenant_id', $tenantId)
->whereIn('status', $validStatuses)
->whereBetween('issue_date', [$startDate, $endDate])
->selectRaw("
direction,
invoice_type,
COALESCE(SUM(supply_amount), 0) as supply_amount,
COALESCE(SUM(tax_amount), 0) as tax_amount
")
->groupBy('direction', 'invoice_type')
->get()
->map(fn ($row) => [
'direction' => $row->direction,
'direction_label' => $row->direction === TaxInvoice::DIRECTION_SALES ? '매출' : '매입',
'invoice_type' => $row->invoice_type,
'invoice_type_label' => match ($row->invoice_type) {
TaxInvoice::TYPE_TAX_INVOICE => '전자세금계산서',
TaxInvoice::TYPE_INVOICE => '계산서',
TaxInvoice::TYPE_MODIFIED_TAX_INVOICE => '수정세금계산서',
default => $row->invoice_type,
},
'supply_amount' => (int) $row->supply_amount,
'tax_amount' => (int) $row->tax_amount,
])
->toArray();
// 미발행/미수취 세금계산서 목록 (status=draft)
$unissuedInvoices = TaxInvoice::where('tenant_id', $tenantId)
->where('status', TaxInvoice::STATUS_DRAFT)
->orderBy('issue_date', 'desc')
->limit(100)
->get()
->map(fn ($invoice) => [
'id' => $invoice->id,
'direction' => $invoice->direction,
'direction_label' => $invoice->direction === TaxInvoice::DIRECTION_SALES ? '매출' : '매입',
'issue_date' => $invoice->issue_date,
'vendor_name' => $invoice->direction === TaxInvoice::DIRECTION_SALES
? ($invoice->buyer_corp_name ?? '-')
: ($invoice->supplier_corp_name ?? '-'),
'tax_amount' => (int) $invoice->tax_amount,
'status' => $invoice->direction === TaxInvoice::DIRECTION_SALES ? '미발행' : '미수취',
])
->toArray();
return [
'period_label' => $periodLabel,
'period_options' => $periodOptions,
'summary' => [
'sales_supply_amount' => $salesSupplyAmount,
'sales_tax_amount' => $salesTaxAmount,
'purchases_supply_amount' => $purchasesSupplyAmount,
'purchases_tax_amount' => $purchasesTaxAmount,
'estimated_payment' => (int) abs($estimatedPayment),
'is_refund' => $estimatedPayment < 0,
],
'reference_table' => $referenceTable,
'unissued_invoices' => $unissuedInvoices,
];
}
/**
* 신고기간 드롭다운 옵션 생성
* 현재 기간 포함 최근 8개 기간
*/
private function generatePeriodOptions(int $currentYear, string $periodType, int $currentPeriod): array
{
$options = [];
$year = $currentYear;
$period = $currentPeriod;
for ($i = 0; $i < 8; $i++) {
$label = $this->getPeriodLabel($year, $periodType, $period);
$value = "{$year}-{$periodType}-{$period}";
$options[] = ['value' => $value, 'label' => $label];
// 이전 기간으로 이동
$prev = $this->getPreviousPeriod($year, $periodType, $period);
$year = $prev['year'];
$period = $prev['period'];
}
return $options;
}
/**
* 이전 기간 계산
*