feat: [payables] 미지급금관리 전표 삭제 기능 추가
- 일반전표 상세 행에 삭제 버튼(휴지통 아이콘) 추가
- DELETE /finance/payables/journal-entry/{id} API 추가
- journal_entry_id 필드를 프론트에 전달하도록 쿼리 수정
- 삭제 후 데이터 자동 새로고침
This commit is contained in:
@@ -313,6 +313,7 @@ public function integrated(Request $request): JsonResponse
|
||||
$journalDetails = (clone $journalQuery)
|
||||
->select(
|
||||
'journal_entry_lines.id',
|
||||
'journal_entry_lines.journal_entry_id',
|
||||
'journal_entry_lines.trading_partner_name',
|
||||
'journal_entry_lines.account_code',
|
||||
'journal_entry_lines.account_name',
|
||||
@@ -505,4 +506,21 @@ public function journalPayables(Request $request): JsonResponse
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 미지급금 관련 전표 강제 삭제 (soft delete)
|
||||
*/
|
||||
public function deleteJournalEntry(int $id): JsonResponse
|
||||
{
|
||||
$tenantId = session('selected_tenant_id', 1);
|
||||
|
||||
$entry = JournalEntry::where('tenant_id', $tenantId)->findOrFail($id);
|
||||
|
||||
$entry->delete();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => "전표 {$entry->entry_no}이(가) 삭제되었습니다.",
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
const TrendingUp = createIcon('trending-up');
|
||||
const TrendingDown = createIcon('trending-down');
|
||||
const Layers = createIcon('layers');
|
||||
const Trash2 = createIcon('trash-2');
|
||||
|
||||
const formatCurrency = (num) => num ? Number(num).toLocaleString() : '0';
|
||||
const formatInputCurrency = (value) => {
|
||||
@@ -81,6 +82,25 @@ function IntegratedTab({ startDate, endDate, account, vendorSearch }) {
|
||||
|
||||
useEffect(() => { fetchData(); }, [fetchData]);
|
||||
|
||||
const handleDeleteJournal = async (journalEntryId, entryNo) => {
|
||||
if (!confirm(`전표 ${entryNo}을(를) 삭제하시겠습니까?`)) return;
|
||||
try {
|
||||
const res = await fetch(`/finance/payables/journal-entry/${journalEntryId}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content },
|
||||
});
|
||||
const json = await res.json();
|
||||
if (json.success) {
|
||||
alert(json.message);
|
||||
fetchData();
|
||||
} else {
|
||||
alert('삭제 실패: ' + (json.message || ''));
|
||||
}
|
||||
} catch (err) {
|
||||
alert('삭제 오류: ' + err.message);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) return <LoadingSpinner />;
|
||||
if (!data) return <EmptyState message="데이터를 불러올 수 없습니다." />;
|
||||
|
||||
@@ -233,9 +253,17 @@ function IntegratedTab({ startDate, endDate, account, vendorSearch }) {
|
||||
{j.dc_type === 'debit' ? '차변(상계)' : '대변(발생)'}
|
||||
</span>
|
||||
</div>
|
||||
<span className={`font-medium ${j.dc_type === 'debit' ? 'text-emerald-600' : 'text-blue-600'}`}>
|
||||
{formatCurrency(j.dc_type === 'debit' ? j.debit_amount : j.credit_amount)}원
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`font-medium ${j.dc_type === 'debit' ? 'text-emerald-600' : 'text-blue-600'}`}>
|
||||
{formatCurrency(j.dc_type === 'debit' ? j.debit_amount : j.credit_amount)}원
|
||||
</span>
|
||||
{j.journal_entry_id && (
|
||||
<button onClick={(e) => { e.stopPropagation(); handleDeleteJournal(j.journal_entry_id, j.entry_no); }}
|
||||
className="p-1 text-gray-300 hover:text-red-500 hover:bg-red-50 rounded transition-colors" title="전표 삭제">
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1383,6 +1383,7 @@
|
||||
Route::put('/{id}', [\App\Http\Controllers\Finance\PayableController::class, 'update'])->name('update');
|
||||
Route::post('/{id}/pay', [\App\Http\Controllers\Finance\PayableController::class, 'pay'])->name('pay');
|
||||
Route::delete('/{id}', [\App\Http\Controllers\Finance\PayableController::class, 'destroy'])->name('destroy');
|
||||
Route::delete('/journal-entry/{id}', [\App\Http\Controllers\Finance\PayableController::class, 'deleteJournalEntry'])->name('delete-journal');
|
||||
});
|
||||
|
||||
// 기타
|
||||
|
||||
Reference in New Issue
Block a user