feat: [회계] 매출/청구/입출금 관리 UI 개선

- 매출관리 SalesDetail, types 개선
- 청구관리 BillManagementClient 개선
- 입금/출금관리 상세 설정 개선

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-02-27 12:29:48 +09:00
parent d38f299c4b
commit a8b219e880
8 changed files with 36 additions and 9 deletions

View File

@@ -130,6 +130,11 @@ export function BillManagementClient({
});
if (result.success) {
// 삭제 후 현재 페이지가 빈 페이지가 되면 마지막 유효 페이지로 이동
if (result.data.length === 0 && result.pagination.lastPage > 0 && page > result.pagination.lastPage) {
loadData(result.pagination.lastPage);
return;
}
setData(result.data);
setPagination(result.pagination);
setCurrentPage(result.pagination.currentPage);

View File

@@ -17,7 +17,7 @@ import { toast } from 'sonner';
import { formatNumber } from '@/lib/utils/amount';
import { getBills, deleteBill, updateBillStatus } from './actions';
import { useDateRange } from '@/hooks';
import { createDeleteItemHandler, extractUniqueOptions } from '../shared';
import { extractUniqueOptions } from '../shared';
import {
FileText,
Plus,
@@ -244,7 +244,14 @@ export function BillManagement({ initialVendorId, initialBillType }: BillManagem
totalCount: pagination.total,
};
},
deleteItem: createDeleteItemHandler(deleteBill, setBillData, '어음이 삭제되었습니다.'),
deleteItem: async (id: string) => {
const result = await deleteBill(id);
if (result.success) {
// 서버에서 재조회 (pagination 메타데이터 포함)
await loadBills();
}
return { success: result.success, error: result.error };
},
},
// 테이블 컬럼

View File

@@ -67,10 +67,9 @@ export default function DepositDetailClientV2({
}, [depositId, initialMode]);
// ===== 저장/등록 핸들러 =====
// IntegratedDetailTemplate이 config.transformSubmitData를 이미 적용한 데이터를 전달함
const handleSubmit = useCallback(
async (formData: Record<string, unknown>): Promise<{ success: boolean; error?: string }> => {
const submitData = depositDetailConfig.transformSubmitData?.(formData) || formData;
async (submitData: Record<string, unknown>): Promise<{ success: boolean; error?: string }> => {
if (!submitData.vendorId) {
toast.error('거래처를 선택해주세요.');
return { success: false, error: '거래처를 선택해주세요.' };

View File

@@ -44,7 +44,7 @@ const fields: FieldDefinition[] = [
{
key: 'depositAmount',
label: '입금금액',
type: 'number',
type: 'currency',
placeholder: '입금금액을 입력해주세요',
disabled: (mode) => mode === 'view',
},
@@ -82,6 +82,7 @@ const fields: FieldDefinition[] = [
label: '입금 유형',
type: 'select',
required: true,
defaultValue: 'unset',
placeholder: '선택',
options: DEPOSIT_TYPE_SELECTOR_OPTIONS.map((opt) => ({
value: opt.value,

View File

@@ -159,6 +159,7 @@ export function SalesDetail({ mode, salesId }: SalesDetailProps) {
salesDate,
vendorId,
salesType,
items,
totalSupplyAmount: totals.supplyAmount,
totalVat: totals.vat,
totalAmount: totals.total,
@@ -188,7 +189,7 @@ export function SalesDetail({ mode, salesId }: SalesDetailProps) {
} finally {
setIsSaving(false);
}
}, [salesDate, vendorId, salesType, totals, taxInvoiceIssued, transactionStatementIssued, note, isNewMode, salesId]);
}, [salesDate, vendorId, salesType, items, totals, taxInvoiceIssued, transactionStatementIssued, note, isNewMode, salesId]);
// ===== 삭제 (IntegratedDetailTemplate 호환) =====
const handleDelete = useCallback(async (): Promise<{ success: boolean; error?: string }> => {

View File

@@ -320,13 +320,13 @@ export function SalesManagement({ initialData, initialPagination }: SalesManagem
// 커스텀 필터 함수 (filterConfig 기반 - ULP의 filters state에서 값 전달)
// 검색은 searchFilter에서 처리하므로 여기서는 필터만 처리
// NOTE: salesType 필터는 API에서 매출유형을 제공하지 않아 비활성 (모든 데이터가 'other')
customFilterFn: (items, fv) => {
if (!items || items.length === 0) return items;
const issuanceVal = fv.issuance as string;
let result = applyFilters(items, [
enumFilter('vendorName', fv.vendor as string),
enumFilter('salesType', fv.salesType as string),
]);
// 발행여부 필터 (특수 로직 - enumFilter로 대체 불가)

View File

@@ -261,6 +261,20 @@ export function transformFrontendToApi(data: Partial<SalesRecord>): Record<strin
if (data.taxInvoiceIssued !== undefined) apiData.tax_invoice_issued = data.taxInvoiceIssued;
if (data.transactionStatementIssued !== undefined) apiData.transaction_statement_issued = data.transactionStatementIssued;
// 품목 배열 변환 (수정 시 items 포함)
if (data.items !== undefined && data.items.length > 0) {
apiData.items = data.items.map((item, index) => ({
item_name: item.itemName,
quantity: item.quantity,
unit_price: item.unitPrice,
supply_amount: item.supplyAmount,
tax_amount: item.vat,
total_amount: item.supplyAmount + item.vat,
note: item.note || null,
sort_order: index + 1,
}));
}
return apiData;
}

View File

@@ -45,7 +45,7 @@ const fields: FieldDefinition[] = [
{
key: 'withdrawalAmount',
label: '출금금액',
type: 'number',
type: 'currency',
placeholder: '출금금액을 입력해주세요',
disabled: (mode) => mode === 'view',
},