feat: Phase 3.8 바로빌 세금계산서 연동 API 구현
- 마이그레이션: barobill_settings, tax_invoices 테이블 생성 - 모델: BarobillSetting (인증서 암호화), TaxInvoice (상태/유형 상수) - 서비스: BarobillService (API 연동), TaxInvoiceService (CRUD, 발행/취소) - 컨트롤러: BarobillSettingController, TaxInvoiceController - FormRequest: 6개 요청 검증 클래스 - Swagger: API 문서 완성 (BarobillSettingApi, TaxInvoiceApi)
This commit is contained in:
297
app/Services/TaxInvoiceService.php
Normal file
297
app/Services/TaxInvoiceService.php
Normal file
@@ -0,0 +1,297 @@
|
||||
<?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,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user