feat(WEB): 부실채권, 재고, 입고, 수주 UI 개선

- BadDebtCollection 액션/타입 리팩토링
- ReceivingProcessDialog 입고처리 개선
- StockStatusList 재고현황 UI 개선
- OrderSalesDetailView 수주 상세 수정
- UniversalListPage 범용 리스트 개선
- production-order 페이지 수정
This commit is contained in:
2026-01-23 21:32:24 +09:00
parent 9fb5c171eb
commit a0343eec93
12 changed files with 315 additions and 251 deletions

View File

@@ -17,7 +17,7 @@
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { revalidatePath } from 'next/cache';
import { serverFetch } from '@/lib/api/fetch-wrapper';
import type { BadDebtRecord, CollectionStatus } from './types';
import type { BadDebtRecord, BadDebtItem, CollectionStatus } from './types';
// ============================================
// API 응답 타입 정의
@@ -37,69 +37,47 @@ interface PaginatedResponse<T> {
last_page: number;
}
// API 악성채권 데이터 타입
interface BadDebtApiData {
// API 개별 악성채권 타입
interface BadDebtItemApiData {
id: number;
tenant_id: number;
client_id: number;
debt_amount: string;
debt_amount: number;
status: 'collecting' | 'legal_action' | 'recovered' | 'bad_debt';
overdue_days: number;
occurred_at: string;
closed_at: string | null;
assigned_manager_id: number | null;
is_active: boolean;
note: string | null;
created_at: string;
updated_at: string;
deleted_at: string | null;
client?: {
id: number;
code: string;
name: string;
business_number: string | null;
representative_name: string | null;
client_type: string | null;
business_type: string | null;
business_category: string | null;
zip_code: string | null;
address1: string | null;
address2: string | null;
phone: string | null;
mobile: string | null;
fax: string | null;
email: string | null;
contact_name: string | null;
contact_phone: string | null;
};
assigned_manager?: {
occurred_at: string | null;
assigned_user?: {
id: number;
name: string;
department?: {
id: number;
name: string;
};
position: string | null;
phone: string | null;
};
documents?: Array<{
} | null;
}
// API 악성채권 데이터 타입 (거래처 기준)
interface BadDebtApiData {
id: number;
client_id: number;
client_code: string;
client_name: string;
business_no: string | null;
contact_person: string | null;
phone: string | null;
mobile: string | null;
email: string | null;
address: string | null;
client_type: string | null;
// 집계 데이터
total_debt_amount: number;
max_overdue_days: number;
bad_debt_count: number;
// 대표 상태
status: 'collecting' | 'legal_action' | 'recovered' | 'bad_debt';
is_active: boolean;
// 담당자
assigned_user?: {
id: number;
file_name: string;
file_path: string;
file_size: number;
mime_type: string;
created_at: string;
}>;
memos?: Array<{
id: number;
content: string;
created_at: string;
created_by: number;
created_by_user?: {
id: number;
name: string;
};
}>;
name: string;
} | null;
// 개별 악성채권 목록
bad_debts: BadDebtItemApiData[];
}
// 통계 API 응답 타입
@@ -165,61 +143,66 @@ function mapClientTypeToVendorType(clientType?: string | null): 'sales' | 'purch
}
/**
* API 데이터 → 프론트엔드 타입 변환
* API 데이터 → 프론트엔드 타입 변환 (거래처 기준)
*/
function transformApiToFrontend(apiData: BadDebtApiData): BadDebtRecord {
const client = apiData.client;
const manager = apiData.assigned_manager;
const manager = apiData.assigned_user;
const firstBadDebt = apiData.bad_debts?.[0];
return {
id: String(apiData.id),
id: String(apiData.id), // Client ID
vendorId: String(apiData.client_id),
vendorCode: client?.code || '',
vendorName: client?.name || '거래처 없음',
businessNumber: client?.business_number || '',
representativeName: client?.representative_name || '',
vendorType: mapClientTypeToVendorType(client?.client_type),
businessType: client?.business_type || '',
businessCategory: client?.business_category || '',
zipCode: client?.zip_code || '',
address1: client?.address1 || '',
address2: client?.address2 || '',
phone: client?.phone || '',
mobile: client?.mobile || '',
fax: client?.fax || '',
email: client?.email || '',
contactName: client?.contact_name || '',
contactPhone: client?.contact_phone || '',
vendorCode: apiData.client_code || '',
vendorName: apiData.client_name || '거래처 없음',
businessNumber: apiData.business_no || '',
representativeName: '',
vendorType: mapClientTypeToVendorType(apiData.client_type),
businessType: '',
businessCategory: '',
zipCode: '',
address1: apiData.address || '',
address2: '',
phone: apiData.phone || '',
mobile: apiData.mobile || '',
fax: '',
email: apiData.email || '',
contactName: apiData.contact_person || '',
contactPhone: '',
systemManager: '',
debtAmount: parseFloat(apiData.debt_amount) || 0,
// 집계 데이터
debtAmount: apiData.total_debt_amount || 0,
badDebtCount: apiData.bad_debt_count || 0,
status: mapApiStatusToFrontend(apiData.status),
overdueDays: apiData.overdue_days || 0,
overdueDays: apiData.max_overdue_days || 0,
overdueToggle: apiData.is_active,
occurrenceDate: apiData.occurred_at,
endDate: apiData.closed_at,
assignedManagerId: apiData.assigned_manager_id ? String(apiData.assigned_manager_id) : null,
occurrenceDate: firstBadDebt?.occurred_at || '',
endDate: null,
assignedManagerId: manager ? String(manager.id) : null,
assignedManager: manager ? {
id: String(manager.id),
departmentName: manager.department?.name || '',
departmentName: '',
name: manager.name,
position: manager.position || '',
phone: manager.phone || '',
position: '',
phone: '',
} : null,
settingToggle: apiData.is_active,
files: apiData.documents?.map(doc => ({
id: String(doc.id),
name: doc.file_name,
url: doc.file_path,
type: 'additional' as const,
})) || [],
memos: apiData.memos?.map(memo => ({
id: String(memo.id),
content: memo.content,
createdAt: memo.created_at,
createdBy: memo.created_by_user?.name || `User ${memo.created_by}`,
})) || [],
createdAt: apiData.created_at,
updatedAt: apiData.updated_at,
// 개별 악성채권 목록
badDebts: (apiData.bad_debts || []).map(bd => ({
id: String(bd.id),
debtAmount: bd.debt_amount || 0,
status: mapApiStatusToFrontend(bd.status),
overdueDays: bd.overdue_days || 0,
isActive: bd.is_active,
occurredAt: bd.occurred_at,
assignedManager: bd.assigned_user ? {
id: String(bd.assigned_user.id),
name: bd.assigned_user.name,
} : null,
})),
files: [],
memos: [],
createdAt: '',
updatedAt: '',
};
}

View File

@@ -31,7 +31,18 @@ export interface AttachedFile {
type: 'businessRegistration' | 'taxInvoice' | 'additional';
}
// 악성채권 레코드
// 개별 악성채권 항목 (거래처별 하위 목록)
export interface BadDebtItem {
id: string;
debtAmount: number;
status: CollectionStatus;
overdueDays: number;
isActive: boolean;
occurredAt: string | null;
assignedManager: { id: string; name: string } | null;
}
// 악성채권 레코드 (거래처 기준)
export interface BadDebtRecord {
id: string;
// 거래처 기본 정보
@@ -55,16 +66,19 @@ export interface BadDebtRecord {
contactName: string;
contactPhone: string;
systemManager: string;
// 악성채권 정보
debtAmount: number;
status: CollectionStatus;
overdueDays: number;
// 악성채권 집계 정보 (거래처 기준)
debtAmount: number; // 총 미수금액
badDebtCount: number; // 악성채권 건수
status: CollectionStatus; // 대표 상태 (가장 최근)
overdueDays: number; // 최대 연체일수
overdueToggle: boolean;
occurrenceDate: string;
endDate: string | null;
assignedManagerId: string | null;
assignedManager: Manager | null;
settingToggle: boolean;
// 개별 악성채권 목록
badDebts: BadDebtItem[];
// 첨부 파일
files: AttachedFile[];
// 메모