feat: [회계] 매출/청구/입출금 관리 UI 개선
- 매출관리 SalesDetail, types 개선 - 청구관리 BillManagementClient 개선 - 입금/출금관리 상세 설정 개선 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 };
|
||||
},
|
||||
},
|
||||
|
||||
// 테이블 컬럼
|
||||
|
||||
@@ -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: '거래처를 선택해주세요.' };
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }> => {
|
||||
|
||||
@@ -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로 대체 불가)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ const fields: FieldDefinition[] = [
|
||||
{
|
||||
key: 'withdrawalAmount',
|
||||
label: '출금금액',
|
||||
type: 'number',
|
||||
type: 'currency',
|
||||
placeholder: '출금금액을 입력해주세요',
|
||||
disabled: (mode) => mode === 'view',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user