fix(WEB): 기타 버그 수정 및 개선

- CardTransactionInquiry: account_code 'unset' 처리 개선
- WorkOrderCreate: DevFill 공정 옵션 로딩 타이밍 수정
- accountingData: 생성기 개선
- api/errors: 에러 처리 개선
This commit is contained in:
2026-01-22 23:19:54 +09:00
parent c8890c1a85
commit e7fb3b1f96
5 changed files with 51 additions and 13 deletions

View File

@@ -296,7 +296,7 @@ export async function createCardTransaction(data: {
merchant_name: data.merchantName, merchant_name: data.merchantName,
amount: data.amount, amount: data.amount,
description: data.memo, description: data.memo,
account_code: data.usageType, account_code: data.usageType === 'unset' ? null : data.usageType,
}), }),
}); });
@@ -349,7 +349,7 @@ export async function updateCardTransaction(
merchant_name: data.merchantName, merchant_name: data.merchantName,
amount: data.amount, amount: data.amount,
description: data.memo, description: data.memo,
account_code: data.usageType, account_code: data.usageType === 'unset' ? null : data.usageType,
}), }),
}); });

View File

@@ -164,6 +164,19 @@ export interface Vendor {
} }
// ===== API 응답 타입 ===== // ===== API 응답 타입 =====
export interface SaleApiOrderItem {
id: number;
order_id: number;
item_name: string;
quantity: string | number;
unit_price: string | number;
supply_amount: string | number;
tax_amount: string | number;
total_amount: string | number;
note: string | null;
sort_order: number;
}
export interface SaleApiData { export interface SaleApiData {
id: number; id: number;
sale_number: string; sale_number: string;
@@ -173,6 +186,10 @@ export interface SaleApiData {
id: number; id: number;
name: string; name: string;
}; };
order?: {
id: number;
items?: SaleApiOrderItem[];
};
supply_amount: string | number; supply_amount: string | number;
tax_amount: string | number; tax_amount: string | number;
total_amount: string | number; total_amount: string | number;
@@ -197,6 +214,17 @@ export const API_STATUS_MAP: Record<string, SalesStatus> = {
// API 응답 → 프론트엔드 타입 변환 // API 응답 → 프론트엔드 타입 변환
export function transformApiToFrontend(apiData: SaleApiData): SalesRecord { export function transformApiToFrontend(apiData: SaleApiData): SalesRecord {
// 수주(Order)의 품목(items)을 매출 품목으로 변환
const items: SalesItem[] = (apiData.order?.items ?? []).map((item, index) => ({
id: String(item.id),
itemName: item.item_name ?? '',
quantity: parseFloat(String(item.quantity)) || 0,
unitPrice: parseFloat(String(item.unit_price)) || 0,
supplyAmount: parseFloat(String(item.supply_amount)) || 0,
vat: parseFloat(String(item.tax_amount)) || 0,
note: item.note ?? '',
}));
return { return {
id: String(apiData.id), id: String(apiData.id),
salesNo: apiData.sale_number, salesNo: apiData.sale_number,
@@ -205,7 +233,7 @@ export function transformApiToFrontend(apiData: SaleApiData): SalesRecord {
vendorName: apiData.client?.name ?? '', vendorName: apiData.client?.name ?? '',
salesType: 'other', // API에 없음, 기본값 salesType: 'other', // API에 없음, 기본값
accountSubject: 'other', // API에 없음, 기본값 accountSubject: 'other', // API에 없음, 기본값
items: [], // API에 없음, 빈 배열 items, // 수주 품목에서 가져옴
totalSupplyAmount: parseFloat(String(apiData.supply_amount)) || 0, totalSupplyAmount: parseFloat(String(apiData.supply_amount)) || 0,
totalVat: parseFloat(String(apiData.tax_amount)) || 0, totalVat: parseFloat(String(apiData.tax_amount)) || 0,
totalAmount: parseFloat(String(apiData.total_amount)) || 0, totalAmount: parseFloat(String(apiData.total_amount)) || 0,

View File

@@ -60,7 +60,7 @@ const DEPOSIT_NOTES = [
export interface DepositFormData { export interface DepositFormData {
depositDate: string; depositDate: string;
accountName: string; bankAccountId: string;
depositorName: string; depositorName: string;
depositAmount: number; depositAmount: number;
note: string; note: string;
@@ -70,15 +70,17 @@ export interface DepositFormData {
export interface GenerateDepositDataOptions { export interface GenerateDepositDataOptions {
vendors?: Array<{ id: string; name: string }>; vendors?: Array<{ id: string; name: string }>;
bankAccounts?: Array<{ id: string; name: string }>;
} }
export function generateDepositData(options: GenerateDepositDataOptions = {}): DepositFormData { export function generateDepositData(options: GenerateDepositDataOptions = {}): DepositFormData {
const { vendors = SAMPLE_VENDORS } = options; const { vendors = SAMPLE_VENDORS, bankAccounts = [] } = options;
const vendor = randomPick(vendors); const vendor = randomPick(vendors);
const bankAccount = bankAccounts.length > 0 ? randomPick(bankAccounts) : null;
return { return {
depositDate: today(), depositDate: today(),
accountName: randomPick(ACCOUNT_NAMES), bankAccountId: bankAccount?.id || '',
depositorName: randomPick(DEPOSITOR_NAMES), depositorName: randomPick(DEPOSITOR_NAMES),
depositAmount: randomInt(100000, 10000000), depositAmount: randomInt(100000, 10000000),
note: randomPick(DEPOSIT_NOTES), note: randomPick(DEPOSIT_NOTES),
@@ -119,7 +121,7 @@ const WITHDRAWAL_NOTES = [
export interface WithdrawalFormData { export interface WithdrawalFormData {
withdrawalDate: string; withdrawalDate: string;
accountName: string; bankAccountId: string;
recipientName: string; recipientName: string;
withdrawalAmount: number; withdrawalAmount: number;
note: string; note: string;
@@ -129,15 +131,17 @@ export interface WithdrawalFormData {
export interface GenerateWithdrawalDataOptions { export interface GenerateWithdrawalDataOptions {
vendors?: Array<{ id: string; name: string }>; vendors?: Array<{ id: string; name: string }>;
bankAccounts?: Array<{ id: string; name: string }>;
} }
export function generateWithdrawalData(options: GenerateWithdrawalDataOptions = {}): WithdrawalFormData { export function generateWithdrawalData(options: GenerateWithdrawalDataOptions = {}): WithdrawalFormData {
const { vendors = SAMPLE_VENDORS } = options; const { vendors = SAMPLE_VENDORS, bankAccounts = [] } = options;
const vendor = randomPick(vendors); const vendor = randomPick(vendors);
const bankAccount = bankAccounts.length > 0 ? randomPick(bankAccounts) : null;
return { return {
withdrawalDate: today(), withdrawalDate: today(),
accountName: randomPick(ACCOUNT_NAMES), bankAccountId: bankAccount?.id || '',
recipientName: randomPick(RECIPIENT_NAMES), recipientName: randomPick(RECIPIENT_NAMES),
withdrawalAmount: randomInt(50000, 5000000), withdrawalAmount: randomInt(50000, 5000000),
note: randomPick(WITHDRAWAL_NOTES), note: randomPick(WITHDRAWAL_NOTES),
@@ -264,6 +268,7 @@ export function generatePurchaseApprovalData(options: GeneratePurchaseApprovalDa
approvalLine: [currentUser], approvalLine: [currentUser],
references: [randomReference], references: [randomReference],
proposalData: { proposalData: {
vendorId: vendor.id,
vendor: vendor.name, vendor: vendor.name,
vendorPaymentDate: today(), vendorPaymentDate: today(),
title: randomPick(PROPOSAL_TITLES), title: randomPick(PROPOSAL_TITLES),

View File

@@ -119,8 +119,12 @@ export function WorkOrderCreate() {
// DevToolbar 자동 채우기 // DevToolbar 자동 채우기
useDevFill( useDevFill(
'workOrder', 'workOrder',
useCallback(() => { useCallback(async () => {
const sampleData = generateWorkOrderData({ processOptions }); // 공정 옵션 직접 가져오기 (state가 아직 로딩 전일 수 있음)
const processResult = await getProcessOptions();
const processes = processResult.success ? processResult.data : [];
const sampleData = generateWorkOrderData({ processOptions: processes });
// 수동 등록 모드로 변경 // 수동 등록 모드로 변경
setMode('manual'); setMode('manual');
@@ -139,7 +143,7 @@ export function WorkOrderCreate() {
})); }));
toast.success('[Dev] 작업지시 폼이 자동으로 채워졌습니다.'); toast.success('[Dev] 작업지시 폼이 자동으로 채워졌습니다.');
}, [processOptions]) }, [])
); );
// 수주 선택 핸들러 // 수주 선택 핸들러

View File

@@ -48,13 +48,14 @@ export function createAuthErrorResponse(message?: string): ApiErrorResponse {
/** /**
* 일반 API 에러 응답 생성 헬퍼 * 일반 API 에러 응답 생성 헬퍼
* - 디버깅을 위해 상태 코드를 메시지에 포함
*/ */
export function createErrorResponse(status: number, message: string, code?: string): ApiErrorResponse { export function createErrorResponse(status: number, message: string, code?: string): ApiErrorResponse {
return { return {
__error: true, __error: true,
__authError: status === 401, __authError: status === 401,
status, status,
message, message: `[${status}] ${message}`,
code, code,
}; };
} }