- 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>
230 lines
8.1 KiB
PHP
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;
|
|
}
|
|
}
|