298 lines
9.1 KiB
PHP
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,
|
||
|
|
],
|
||
|
|
];
|
||
|
|
}
|
||
|
|
}
|