diff --git a/app/Http/Controllers/Finance/JournalEntryController.php b/app/Http/Controllers/Finance/JournalEntryController.php index 313dd826..4c0ac257 100644 --- a/app/Http/Controllers/Finance/JournalEntryController.php +++ b/app/Http/Controllers/Finance/JournalEntryController.php @@ -164,52 +164,68 @@ public function store(Request $request): JsonResponse ], 422); } - try { - $entry = DB::transaction(function () use ($tenantId, $request, $lines, $totalDebit, $totalCredit) { - $entryNo = JournalEntry::generateEntryNo($tenantId, $request->entry_date); + $maxRetries = 3; + $lastError = null; - $entry = JournalEntry::create([ - 'tenant_id' => $tenantId, - 'entry_no' => $entryNo, - 'entry_date' => $request->entry_date, - 'description' => $request->description, - 'total_debit' => $totalDebit, - 'total_credit' => $totalCredit, - 'status' => 'draft', - 'created_by_name' => auth()->user()?->name ?? '시스템', - 'attachment_note' => $request->attachment_note, - ]); + for ($attempt = 0; $attempt < $maxRetries; $attempt++) { + try { + $entry = DB::transaction(function () use ($tenantId, $request, $lines, $totalDebit, $totalCredit) { + $entryNo = JournalEntry::generateEntryNo($tenantId, $request->entry_date); - foreach ($lines as $i => $line) { - JournalEntryLine::create([ + $entry = JournalEntry::create([ 'tenant_id' => $tenantId, - 'journal_entry_id' => $entry->id, - 'line_no' => $i + 1, - 'dc_type' => $line['dc_type'], - 'account_code' => $line['account_code'], - 'account_name' => $line['account_name'], - 'trading_partner_id' => $line['trading_partner_id'] ?? null, - 'trading_partner_name' => $line['trading_partner_name'] ?? null, - 'debit_amount' => $line['debit_amount'], - 'credit_amount' => $line['credit_amount'], - 'description' => $line['description'] ?? null, + 'entry_no' => $entryNo, + 'entry_date' => $request->entry_date, + 'description' => $request->description, + 'total_debit' => $totalDebit, + 'total_credit' => $totalCredit, + 'status' => 'draft', + 'created_by_name' => auth()->user()?->name ?? '시스템', + 'attachment_note' => $request->attachment_note, ]); + + foreach ($lines as $i => $line) { + JournalEntryLine::create([ + 'tenant_id' => $tenantId, + 'journal_entry_id' => $entry->id, + 'line_no' => $i + 1, + 'dc_type' => $line['dc_type'], + 'account_code' => $line['account_code'], + 'account_name' => $line['account_name'], + 'trading_partner_id' => $line['trading_partner_id'] ?? null, + 'trading_partner_name' => $line['trading_partner_name'] ?? null, + 'debit_amount' => $line['debit_amount'], + 'credit_amount' => $line['credit_amount'], + 'description' => $line['description'] ?? null, + ]); + } + + return $entry; + }); + + return response()->json([ + 'success' => true, + 'message' => '전표가 저장되었습니다.', + 'data' => ['id' => $entry->id, 'entry_no' => $entry->entry_no], + ]); + } catch (\Illuminate\Database\QueryException $e) { + $lastError = $e; + if ($e->errorInfo[1] === 1062) { + continue; } - - return $entry; - }); - - return response()->json([ - 'success' => true, - 'message' => '전표가 저장되었습니다.', - 'data' => ['id' => $entry->id, 'entry_no' => $entry->entry_no], - ]); - } catch (\Throwable $e) { - return response()->json([ - 'success' => false, - 'message' => '전표 저장 실패: ' . $e->getMessage(), - ], 500); + break; + } catch (\Throwable $e) { + $lastError = $e; + break; + } } + + Log::error('전표 저장 실패', ['error' => $lastError->getMessage()]); + + return response()->json([ + 'success' => false, + 'message' => '전표 저장 실패: ' . $lastError->getMessage(), + ], 500); } /** @@ -544,54 +560,69 @@ public function storeFromBank(Request $request): JsonResponse ], 422); } - try { - $entry = DB::transaction(function () use ($tenantId, $request, $lines, $totalDebit, $totalCredit) { - $entryNo = JournalEntry::generateEntryNo($tenantId, $request->entry_date); + $maxRetries = 3; + $lastError = null; - $entry = JournalEntry::create([ - 'tenant_id' => $tenantId, - 'entry_no' => $entryNo, - 'entry_date' => $request->entry_date, - 'description' => $request->description, - 'total_debit' => $totalDebit, - 'total_credit' => $totalCredit, - 'status' => 'draft', - 'source_type' => 'bank_transaction', - 'source_key' => $request->source_key, - 'created_by_name' => auth()->user()?->name ?? '시스템', - ]); + for ($attempt = 0; $attempt < $maxRetries; $attempt++) { + try { + $entry = DB::transaction(function () use ($tenantId, $request, $lines, $totalDebit, $totalCredit) { + $entryNo = JournalEntry::generateEntryNo($tenantId, $request->entry_date); - foreach ($lines as $i => $line) { - JournalEntryLine::create([ + $entry = JournalEntry::create([ 'tenant_id' => $tenantId, - 'journal_entry_id' => $entry->id, - 'line_no' => $i + 1, - 'dc_type' => $line['dc_type'], - 'account_code' => $line['account_code'], - 'account_name' => $line['account_name'], - 'trading_partner_id' => $line['trading_partner_id'] ?? null, - 'trading_partner_name' => $line['trading_partner_name'] ?? null, - 'debit_amount' => $line['debit_amount'], - 'credit_amount' => $line['credit_amount'], - 'description' => $line['description'] ?? null, + 'entry_no' => $entryNo, + 'entry_date' => $request->entry_date, + 'description' => $request->description, + 'total_debit' => $totalDebit, + 'total_credit' => $totalCredit, + 'status' => 'draft', + 'source_type' => 'bank_transaction', + 'source_key' => $request->source_key, + 'created_by_name' => auth()->user()?->name ?? '시스템', ]); + + foreach ($lines as $i => $line) { + JournalEntryLine::create([ + 'tenant_id' => $tenantId, + 'journal_entry_id' => $entry->id, + 'line_no' => $i + 1, + 'dc_type' => $line['dc_type'], + 'account_code' => $line['account_code'], + 'account_name' => $line['account_name'], + 'trading_partner_id' => $line['trading_partner_id'] ?? null, + 'trading_partner_name' => $line['trading_partner_name'] ?? null, + 'debit_amount' => $line['debit_amount'], + 'credit_amount' => $line['credit_amount'], + 'description' => $line['description'] ?? null, + ]); + } + + return $entry; + }); + + return response()->json([ + 'success' => true, + 'message' => '분개가 저장되었습니다.', + 'data' => ['id' => $entry->id, 'entry_no' => $entry->entry_no], + ]); + } catch (\Illuminate\Database\QueryException $e) { + $lastError = $e; + if ($e->errorInfo[1] === 1062) { + continue; } - - return $entry; - }); - - return response()->json([ - 'success' => true, - 'message' => '분개가 저장되었습니다.', - 'data' => ['id' => $entry->id, 'entry_no' => $entry->entry_no], - ]); - } catch (\Throwable $e) { - Log::error('은행거래 분개 저장 오류: ' . $e->getMessage()); - return response()->json([ - 'success' => false, - 'message' => '분개 저장 실패: ' . $e->getMessage(), - ], 500); + break; + } catch (\Throwable $e) { + $lastError = $e; + break; + } } + + Log::error('은행거래 분개 저장 오류: ' . $lastError->getMessage()); + + return response()->json([ + 'success' => false, + 'message' => '분개 저장 실패: ' . $lastError->getMessage(), + ], 500); } /** diff --git a/resources/views/finance/journal-entries.blade.php b/resources/views/finance/journal-entries.blade.php index df975b4e..dea393d6 100644 --- a/resources/views/finance/journal-entries.blade.php +++ b/resources/views/finance/journal-entries.blade.php @@ -1003,9 +1003,20 @@ className="px-2.5 py-1 text-xs font-medium bg-amber-100 text-amber-700 rounded-f const method = isEditMode ? 'PUT' : 'POST'; const res = await fetch(url, { - method, headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': CSRF_TOKEN }, + method, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': CSRF_TOKEN }, body: JSON.stringify(payload), }); + + if (!res.ok) { + const text = await res.text(); + try { + const errData = JSON.parse(text); + const msg = errData.message || (errData.errors ? Object.values(errData.errors).flat().join(', ') : `저장 실패 (${res.status})`); + notify(msg, 'error'); + } catch { notify(`저장 실패: 서버 오류 (${res.status})`, 'error'); } + return; + } + const data = await res.json(); if (data.success) { @@ -1015,7 +1026,8 @@ className="px-2.5 py-1 text-xs font-medium bg-amber-100 text-amber-700 rounded-f notify(data.message || '저장 실패', 'error'); } } catch (err) { - notify('저장 중 오류가 발생했습니다.', 'error'); + console.error('전표 저장 오류:', err); + notify('저장 중 오류가 발생했습니다: ' + err.message, 'error'); } finally { setSaving(false); } @@ -1383,9 +1395,20 @@ className={`px-6 py-2 text-sm font-medium rounded-lg flex items-center gap-1 tra const res = await fetch('/finance/journal-entries/store-from-bank', { method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': CSRF_TOKEN }, + headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': CSRF_TOKEN }, body: JSON.stringify(payload), }); + + if (!res.ok) { + const text = await res.text(); + try { + const errData = JSON.parse(text); + const msg = errData.message || (errData.errors ? Object.values(errData.errors).flat().join(', ') : `저장 실패 (${res.status})`); + notify(msg, 'error'); + } catch { notify(`저장 실패: 서버 오류 (${res.status})`, 'error'); } + return; + } + const data = await res.json(); if (data.success) { @@ -1396,7 +1419,7 @@ className={`px-6 py-2 text-sm font-medium rounded-lg flex items-center gap-1 tra } } catch (err) { console.error('분개 저장 오류:', err); - notify('분개 저장 중 오류가 발생했습니다.', 'error'); + notify('분개 저장 중 오류가 발생했습니다: ' + err.message, 'error'); } finally { setSaving(false); }