Files
sam-api/app/Services/TaxInvoiceService.php
hskwon 8ad4d7c0ce feat: Phase 3.8 바로빌 세금계산서 연동 API 구현
- 마이그레이션: barobill_settings, tax_invoices 테이블 생성
- 모델: BarobillSetting (인증서 암호화), TaxInvoice (상태/유형 상수)
- 서비스: BarobillService (API 연동), TaxInvoiceService (CRUD, 발행/취소)
- 컨트롤러: BarobillSettingController, TaxInvoiceController
- FormRequest: 6개 요청 검증 클래스
- Swagger: API 문서 완성 (BarobillSettingApi, TaxInvoiceApi)
2025-12-18 15:31:59 +09:00

298 lines
9.1 KiB
PHP

<?php
namespace App\Services;
use App\Models\Tenants\TaxInvoice;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
/**
* 세금계산서 관리 서비스
*/
class TaxInvoiceService extends Service
{
public function __construct(
private BarobillService $barobillService
) {}
// =========================================================================
// 목록 조회
// =========================================================================
/**
* 세금계산서 목록 조회
*/
public function list(array $params): LengthAwarePaginator
{
$tenantId = $this->tenantId();
$perPage = $params['per_page'] ?? 20;
$query = TaxInvoice::query()
->where('tenant_id', $tenantId)
->orderBy('issue_date', 'desc')
->orderBy('id', 'desc');
// 방향 (매출/매입)
if (! empty($params['direction'])) {
$query->where('direction', $params['direction']);
}
// 상태
if (! empty($params['status'])) {
$query->where('status', $params['status']);
}
// 세금계산서 유형
if (! empty($params['invoice_type'])) {
$query->where('invoice_type', $params['invoice_type']);
}
// 발행 유형
if (! empty($params['issue_type'])) {
$query->where('issue_type', $params['issue_type']);
}
// 기간 검색
if (! empty($params['issue_date_from'])) {
$query->whereDate('issue_date', '>=', $params['issue_date_from']);
}
if (! empty($params['issue_date_to'])) {
$query->whereDate('issue_date', '<=', $params['issue_date_to']);
}
// 거래처 검색 (공급자 또는 공급받는자)
if (! empty($params['corp_num'])) {
$query->where(function ($q) use ($params) {
$q->where('supplier_corp_num', $params['corp_num'])
->orWhere('buyer_corp_num', $params['corp_num']);
});
}
// 거래처명 검색
if (! empty($params['corp_name'])) {
$query->where(function ($q) use ($params) {
$q->where('supplier_corp_name', 'like', '%'.$params['corp_name'].'%')
->orWhere('buyer_corp_name', 'like', '%'.$params['corp_name'].'%');
});
}
// 국세청 승인번호 검색
if (! empty($params['nts_confirm_num'])) {
$query->where('nts_confirm_num', 'like', '%'.$params['nts_confirm_num'].'%');
}
return $query->paginate($perPage);
}
/**
* 세금계산서 상세 조회
*/
public function show(int $id): TaxInvoice
{
$tenantId = $this->tenantId();
return TaxInvoice::query()
->where('tenant_id', $tenantId)
->with(['creator', 'updater'])
->findOrFail($id);
}
// =========================================================================
// 세금계산서 생성/수정
// =========================================================================
/**
* 세금계산서 생성
*/
public function create(array $data): TaxInvoice
{
$tenantId = $this->tenantId();
$userId = $this->apiUserId();
// 합계금액 계산
$data['total_amount'] = ($data['supply_amount'] ?? 0) + ($data['tax_amount'] ?? 0);
$taxInvoice = TaxInvoice::create(array_merge($data, [
'tenant_id' => $tenantId,
'status' => TaxInvoice::STATUS_DRAFT,
'created_by' => $userId,
'updated_by' => $userId,
]));
return $taxInvoice->fresh();
}
/**
* 세금계산서 수정
*/
public function update(int $id, array $data): TaxInvoice
{
$tenantId = $this->tenantId();
$userId = $this->apiUserId();
$taxInvoice = TaxInvoice::query()
->where('tenant_id', $tenantId)
->findOrFail($id);
if (! $taxInvoice->canEdit()) {
throw new \Symfony\Component\HttpKernel\Exception\BadRequestHttpException(__('error.tax_invoice.cannot_edit'));
}
// 합계금액 계산
if (isset($data['supply_amount']) || isset($data['tax_amount'])) {
$supplyAmount = $data['supply_amount'] ?? $taxInvoice->supply_amount;
$taxAmount = $data['tax_amount'] ?? $taxInvoice->tax_amount;
$data['total_amount'] = $supplyAmount + $taxAmount;
}
$taxInvoice->fill(array_merge($data, ['updated_by' => $userId]));
$taxInvoice->save();
return $taxInvoice->fresh();
}
/**
* 세금계산서 삭제
*/
public function delete(int $id): bool
{
$tenantId = $this->tenantId();
$userId = $this->apiUserId();
$taxInvoice = TaxInvoice::query()
->where('tenant_id', $tenantId)
->findOrFail($id);
if (! $taxInvoice->canEdit()) {
throw new \Symfony\Component\HttpKernel\Exception\BadRequestHttpException(__('error.tax_invoice.cannot_delete'));
}
$taxInvoice->deleted_by = $userId;
$taxInvoice->save();
return $taxInvoice->delete();
}
// =========================================================================
// 발행/취소
// =========================================================================
/**
* 세금계산서 발행
*/
public function issue(int $id): TaxInvoice
{
$tenantId = $this->tenantId();
$taxInvoice = TaxInvoice::query()
->where('tenant_id', $tenantId)
->findOrFail($id);
if (! $taxInvoice->canEdit()) {
throw new \Symfony\Component\HttpKernel\Exception\BadRequestHttpException(__('error.tax_invoice.already_issued'));
}
return $this->barobillService->issueTaxInvoice($taxInvoice);
}
/**
* 세금계산서 취소
*/
public function cancel(int $id, string $reason): TaxInvoice
{
$tenantId = $this->tenantId();
$taxInvoice = TaxInvoice::query()
->where('tenant_id', $tenantId)
->findOrFail($id);
return $this->barobillService->cancelTaxInvoice($taxInvoice, $reason);
}
/**
* 국세청 전송 상태 조회
*/
public function checkStatus(int $id): TaxInvoice
{
$tenantId = $this->tenantId();
$taxInvoice = TaxInvoice::query()
->where('tenant_id', $tenantId)
->findOrFail($id);
return $this->barobillService->checkNtsSendStatus($taxInvoice);
}
// =========================================================================
// 통계
// =========================================================================
/**
* 세금계산서 요약 통계
*/
public function summary(array $params): array
{
$tenantId = $this->tenantId();
$query = TaxInvoice::query()
->where('tenant_id', $tenantId);
// 기간 필터
if (! empty($params['issue_date_from'])) {
$query->whereDate('issue_date', '>=', $params['issue_date_from']);
}
if (! empty($params['issue_date_to'])) {
$query->whereDate('issue_date', '<=', $params['issue_date_to']);
}
// 방향별 통계
$summary = $query->clone()
->select([
'direction',
DB::raw('COUNT(*) as count'),
DB::raw('SUM(supply_amount) as supply_amount'),
DB::raw('SUM(tax_amount) as tax_amount'),
DB::raw('SUM(total_amount) as total_amount'),
])
->groupBy('direction')
->get()
->keyBy('direction')
->toArray();
// 상태별 통계
$byStatus = $query->clone()
->select([
'status',
DB::raw('COUNT(*) as count'),
])
->groupBy('status')
->get()
->keyBy('status')
->toArray();
return [
'by_direction' => [
'sales' => $summary[TaxInvoice::DIRECTION_SALES] ?? [
'count' => 0,
'supply_amount' => 0,
'tax_amount' => 0,
'total_amount' => 0,
],
'purchases' => $summary[TaxInvoice::DIRECTION_PURCHASES] ?? [
'count' => 0,
'supply_amount' => 0,
'tax_amount' => 0,
'total_amount' => 0,
],
],
'by_status' => [
TaxInvoice::STATUS_DRAFT => $byStatus[TaxInvoice::STATUS_DRAFT]['count'] ?? 0,
TaxInvoice::STATUS_ISSUED => $byStatus[TaxInvoice::STATUS_ISSUED]['count'] ?? 0,
TaxInvoice::STATUS_SENT => $byStatus[TaxInvoice::STATUS_SENT]['count'] ?? 0,
TaxInvoice::STATUS_CANCELLED => $byStatus[TaxInvoice::STATUS_CANCELLED]['count'] ?? 0,
TaxInvoice::STATUS_FAILED => $byStatus[TaxInvoice::STATUS_FAILED]['count'] ?? 0,
],
];
}
}