Files
sam-manage/app/Services/Barobill/HometaxSyncService.php
김보곤 ac66e36294 feat:홈택스 세금계산서 로컬 저장 및 동기화 기능 구현
- HometaxInvoice 모델 생성 (로컬 DB 조회/저장)
- HometaxSyncService 서비스 생성 (API 데이터 동기화)
- HometaxController에 로컬 조회/동기화 메서드 추가
- 라우트 추가: local-sales, local-purchases, sync, update-memo, toggle-checked
- UI: 데이터소스 선택 (로컬 DB/바로빌 API), 동기화 버튼 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 17:13:18 +09:00

230 lines
8.1 KiB
PHP

<?php
namespace App\Services\Barobill;
use App\Models\Barobill\HometaxInvoice;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
/**
* 홈택스 세금계산서 동기화 서비스
*/
class HometaxSyncService
{
/**
* API 응답 데이터를 로컬 DB에 동기화
*
* @param array $invoices API에서 받은 세금계산서 목록
* @param int $tenantId 테넌트 ID
* @param string $invoiceType 'sales' 또는 'purchase'
* @return array 동기화 결과 ['inserted' => int, 'updated' => int, 'failed' => int]
*/
public function syncInvoices(array $invoices, int $tenantId, string $invoiceType): array
{
$result = [
'inserted' => 0,
'updated' => 0,
'failed' => 0,
'total' => count($invoices),
];
if (empty($invoices)) {
return $result;
}
DB::beginTransaction();
try {
foreach ($invoices as $apiData) {
// 국세청승인번호가 없으면 스킵
if (empty($apiData['ntsConfirmNum'])) {
$result['failed']++;
continue;
}
$modelData = HometaxInvoice::fromApiData($apiData, $tenantId, $invoiceType);
// upsert (있으면 업데이트, 없으면 삽입)
$existing = HometaxInvoice::where('tenant_id', $tenantId)
->where('nts_confirm_num', $modelData['nts_confirm_num'])
->where('invoice_type', $invoiceType)
->first();
if ($existing) {
// 기존 데이터 업데이트 (자체 관리 필드는 유지)
$existing->update([
'write_date' => $modelData['write_date'],
'issue_date' => $modelData['issue_date'],
'invoicer_corp_num' => $modelData['invoicer_corp_num'],
'invoicer_corp_name' => $modelData['invoicer_corp_name'],
'invoicer_ceo_name' => $modelData['invoicer_ceo_name'],
'invoicee_corp_num' => $modelData['invoicee_corp_num'],
'invoicee_corp_name' => $modelData['invoicee_corp_name'],
'invoicee_ceo_name' => $modelData['invoicee_ceo_name'],
'supply_amount' => $modelData['supply_amount'],
'tax_amount' => $modelData['tax_amount'],
'total_amount' => $modelData['total_amount'],
'tax_type' => $modelData['tax_type'],
'purpose_type' => $modelData['purpose_type'],
'item_name' => $modelData['item_name'],
'remark' => $modelData['remark'],
'synced_at' => now(),
]);
$result['updated']++;
} else {
// 새 데이터 삽입
HometaxInvoice::create($modelData);
$result['inserted']++;
}
}
DB::commit();
Log::info('[HometaxSync] 동기화 완료', [
'tenant_id' => $tenantId,
'invoice_type' => $invoiceType,
'result' => $result,
]);
} catch (\Throwable $e) {
DB::rollBack();
Log::error('[HometaxSync] 동기화 실패', [
'tenant_id' => $tenantId,
'invoice_type' => $invoiceType,
'error' => $e->getMessage(),
]);
throw $e;
}
return $result;
}
/**
* 로컬 DB에서 세금계산서 목록 조회
*
* @param int $tenantId 테넌트 ID
* @param string $invoiceType 'sales' 또는 'purchase'
* @param string $startDate 시작일 (Y-m-d)
* @param string $endDate 종료일 (Y-m-d)
* @param string $dateType 날짜 타입 ('write', 'issue', 'send')
* @param string|null $searchCorp 거래처 검색어
* @return array
*/
public function getLocalInvoices(
int $tenantId,
string $invoiceType,
string $startDate,
string $endDate,
string $dateType = 'write',
?string $searchCorp = null
): array {
$query = HometaxInvoice::where('tenant_id', $tenantId)
->where('invoice_type', $invoiceType)
->period($startDate, $endDate, $dateType);
if (!empty($searchCorp)) {
$query->searchCorp($searchCorp, $invoiceType);
}
$invoices = $query->orderByDesc('write_date')->get();
// API 응답 형식에 맞게 변환
$formattedInvoices = $invoices->map(function ($inv) {
return [
'id' => $inv->id,
'ntsConfirmNum' => $inv->nts_confirm_num,
'writeDate' => $inv->write_date?->format('Ymd'),
'writeDateFormatted' => $inv->write_date?->format('Y-m-d'),
'issueDT' => $inv->issue_date?->format('Ymd'),
'issueDateFormatted' => $inv->issue_date?->format('Y-m-d'),
'invoicerCorpNum' => $inv->invoicer_corp_num,
'invoicerCorpName' => $inv->invoicer_corp_name,
'invoicerCEOName' => $inv->invoicer_ceo_name,
'invoiceeCorpNum' => $inv->invoicee_corp_num,
'invoiceeCorpName' => $inv->invoicee_corp_name,
'invoiceeCEOName' => $inv->invoicee_ceo_name,
'supplyAmount' => $inv->supply_amount,
'supplyAmountFormatted' => $inv->formatted_supply_amount,
'taxAmount' => $inv->tax_amount,
'taxAmountFormatted' => $inv->formatted_tax_amount,
'totalAmount' => $inv->total_amount,
'totalAmountFormatted' => $inv->formatted_total_amount,
'taxType' => $inv->tax_type,
'taxTypeName' => $inv->tax_type_name,
'purposeType' => $inv->purpose_type,
'purposeTypeName' => $inv->purpose_type_name,
'issueTypeName' => $inv->issue_type_name,
'itemName' => $inv->item_name,
'remark' => $inv->remark,
'memo' => $inv->memo,
'category' => $inv->category,
'isChecked' => $inv->is_checked,
'syncedAt' => $inv->synced_at?->format('Y-m-d H:i:s'),
];
})->toArray();
// 요약 계산
$summary = [
'totalAmount' => $invoices->sum('supply_amount'),
'totalTax' => $invoices->sum('tax_amount'),
'totalSum' => $invoices->sum('total_amount'),
'count' => $invoices->count(),
];
return [
'invoices' => $formattedInvoices,
'summary' => $summary,
];
}
/**
* 마지막 동기화 시간 조회
*/
public function getLastSyncTime(int $tenantId, string $invoiceType): ?string
{
$lastSync = HometaxInvoice::where('tenant_id', $tenantId)
->where('invoice_type', $invoiceType)
->orderByDesc('synced_at')
->value('synced_at');
return $lastSync?->format('Y-m-d H:i:s');
}
/**
* 메모 업데이트
*/
public function updateMemo(int $id, int $tenantId, ?string $memo): bool
{
return HometaxInvoice::where('id', $id)
->where('tenant_id', $tenantId)
->update(['memo' => $memo]) > 0;
}
/**
* 확인 여부 토글
*/
public function toggleChecked(int $id, int $tenantId): bool
{
$invoice = HometaxInvoice::where('id', $id)
->where('tenant_id', $tenantId)
->first();
if (!$invoice) {
return false;
}
$invoice->is_checked = !$invoice->is_checked;
return $invoice->save();
}
/**
* 분류 태그 업데이트
*/
public function updateCategory(int $id, int $tenantId, ?string $category): bool
{
return HometaxInvoice::where('id', $id)
->where('tenant_id', $tenantId)
->update(['category' => $category]) > 0;
}
}