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>
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
use App\Models\Barobill\BarobillConfig;
|
||||
use App\Models\Barobill\BarobillMember;
|
||||
use App\Models\Tenants\Tenant;
|
||||
use App\Services\Barobill\HometaxSyncService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
@@ -1174,4 +1175,230 @@ private function xmlToObject(\SimpleXMLElement $xml): object
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 로컬 DB에서 매출 세금계산서 조회
|
||||
*/
|
||||
public function localSales(Request $request, HometaxSyncService $syncService): JsonResponse
|
||||
{
|
||||
try {
|
||||
$tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
|
||||
|
||||
$startDate = $request->input('startDate');
|
||||
$endDate = $request->input('endDate');
|
||||
|
||||
// YYYYMMDD 형식을 Y-m-d로 변환
|
||||
if (strlen($startDate) === 8) {
|
||||
$startDate = substr($startDate, 0, 4) . '-' . substr($startDate, 4, 2) . '-' . substr($startDate, 6, 2);
|
||||
}
|
||||
if (strlen($endDate) === 8) {
|
||||
$endDate = substr($endDate, 0, 4) . '-' . substr($endDate, 4, 2) . '-' . substr($endDate, 6, 2);
|
||||
}
|
||||
|
||||
$dateType = $request->input('dateType', 1) == 1 ? 'write' : 'issue';
|
||||
$searchCorp = $request->input('searchCorp');
|
||||
|
||||
$data = $syncService->getLocalInvoices(
|
||||
$tenantId,
|
||||
'sales',
|
||||
$startDate,
|
||||
$endDate,
|
||||
$dateType,
|
||||
$searchCorp
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
'lastSyncAt' => $syncService->getLastSyncTime($tenantId, 'sales'),
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('로컬 매출 조회 오류: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '조회 오류: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 로컬 DB에서 매입 세금계산서 조회
|
||||
*/
|
||||
public function localPurchases(Request $request, HometaxSyncService $syncService): JsonResponse
|
||||
{
|
||||
try {
|
||||
$tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
|
||||
|
||||
$startDate = $request->input('startDate');
|
||||
$endDate = $request->input('endDate');
|
||||
|
||||
// YYYYMMDD 형식을 Y-m-d로 변환
|
||||
if (strlen($startDate) === 8) {
|
||||
$startDate = substr($startDate, 0, 4) . '-' . substr($startDate, 4, 2) . '-' . substr($startDate, 6, 2);
|
||||
}
|
||||
if (strlen($endDate) === 8) {
|
||||
$endDate = substr($endDate, 0, 4) . '-' . substr($endDate, 4, 2) . '-' . substr($endDate, 6, 2);
|
||||
}
|
||||
|
||||
$dateType = $request->input('dateType', 1) == 1 ? 'write' : 'issue';
|
||||
$searchCorp = $request->input('searchCorp');
|
||||
|
||||
$data = $syncService->getLocalInvoices(
|
||||
$tenantId,
|
||||
'purchase',
|
||||
$startDate,
|
||||
$endDate,
|
||||
$dateType,
|
||||
$searchCorp
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
'lastSyncAt' => $syncService->getLastSyncTime($tenantId, 'purchase'),
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('로컬 매입 조회 오류: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '조회 오류: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 바로빌 API에서 데이터를 가져와 로컬 DB에 동기화
|
||||
*/
|
||||
public function sync(Request $request, HometaxSyncService $syncService): JsonResponse
|
||||
{
|
||||
try {
|
||||
$tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
|
||||
$type = $request->input('type', 'all'); // 'sales', 'purchase', 'all'
|
||||
$startDate = $request->input('startDate', date('Ymd', strtotime('-1 month')));
|
||||
$endDate = $request->input('endDate', date('Ymd'));
|
||||
$dateType = (int)$request->input('dateType', 1);
|
||||
|
||||
$results = [];
|
||||
|
||||
// 매출 동기화
|
||||
if ($type === 'all' || $type === 'sales') {
|
||||
$salesRequest = new Request([
|
||||
'startDate' => $startDate,
|
||||
'endDate' => $endDate,
|
||||
'dateType' => $dateType,
|
||||
'limit' => 500,
|
||||
]);
|
||||
|
||||
$salesResponse = $this->sales($salesRequest);
|
||||
$salesData = json_decode($salesResponse->getContent(), true);
|
||||
|
||||
if ($salesData['success'] && !empty($salesData['data']['invoices'])) {
|
||||
$results['sales'] = $syncService->syncInvoices(
|
||||
$salesData['data']['invoices'],
|
||||
$tenantId,
|
||||
'sales'
|
||||
);
|
||||
} else {
|
||||
$results['sales'] = [
|
||||
'inserted' => 0,
|
||||
'updated' => 0,
|
||||
'failed' => 0,
|
||||
'total' => 0,
|
||||
'error' => $salesData['error'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 매입 동기화
|
||||
if ($type === 'all' || $type === 'purchase') {
|
||||
$purchaseRequest = new Request([
|
||||
'startDate' => $startDate,
|
||||
'endDate' => $endDate,
|
||||
'dateType' => $dateType,
|
||||
'limit' => 500,
|
||||
]);
|
||||
|
||||
$purchaseResponse = $this->purchases($purchaseRequest);
|
||||
$purchaseData = json_decode($purchaseResponse->getContent(), true);
|
||||
|
||||
if ($purchaseData['success'] && !empty($purchaseData['data']['invoices'])) {
|
||||
$results['purchase'] = $syncService->syncInvoices(
|
||||
$purchaseData['data']['invoices'],
|
||||
$tenantId,
|
||||
'purchase'
|
||||
);
|
||||
} else {
|
||||
$results['purchase'] = [
|
||||
'inserted' => 0,
|
||||
'updated' => 0,
|
||||
'failed' => 0,
|
||||
'total' => 0,
|
||||
'error' => $purchaseData['error'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 총 결과 계산
|
||||
$totalInserted = ($results['sales']['inserted'] ?? 0) + ($results['purchase']['inserted'] ?? 0);
|
||||
$totalUpdated = ($results['sales']['updated'] ?? 0) + ($results['purchase']['updated'] ?? 0);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => "동기화 완료: {$totalInserted}건 추가, {$totalUpdated}건 갱신",
|
||||
'data' => $results,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('홈택스 동기화 오류: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '동기화 오류: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 세금계산서 메모 업데이트
|
||||
*/
|
||||
public function updateMemo(Request $request, HometaxSyncService $syncService): JsonResponse
|
||||
{
|
||||
try {
|
||||
$tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
|
||||
$id = $request->input('id');
|
||||
$memo = $request->input('memo');
|
||||
|
||||
$success = $syncService->updateMemo($id, $tenantId, $memo);
|
||||
|
||||
return response()->json([
|
||||
'success' => $success,
|
||||
'message' => $success ? '메모가 저장되었습니다.' : '저장에 실패했습니다.',
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '오류: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 세금계산서 확인 여부 토글
|
||||
*/
|
||||
public function toggleChecked(Request $request, HometaxSyncService $syncService): JsonResponse
|
||||
{
|
||||
try {
|
||||
$tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
|
||||
$id = $request->input('id');
|
||||
|
||||
$success = $syncService->toggleChecked($id, $tenantId);
|
||||
|
||||
return response()->json([
|
||||
'success' => $success,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '오류: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user