feat(WEB): 수주관리 Phase 2 타입 정의 확장 및 공정관리 개별 품목 표시 수정
- Order, OrderItem 인터페이스에 상세 페이지용 필드 추가 - OrderFormData, OrderItemFormData에 수정 페이지용 필드 추가 - 변환 함수에서 새 필드 매핑 처리 - 공정관리 개별 품목을 ID 대신 품목명으로 표시
This commit is contained in:
@@ -156,19 +156,36 @@ export interface Order {
|
||||
remarks?: string;
|
||||
note?: string;
|
||||
items?: OrderItem[];
|
||||
// 상세 페이지용 추가 필드
|
||||
manager?: string; // 담당자
|
||||
contact?: string; // 연락처 (client_contact)
|
||||
deliveryRequestDate?: string; // 납품요청일
|
||||
shippingCost?: string; // 운임비용
|
||||
receiver?: string; // 수신자
|
||||
receiverContact?: string; // 수신처 연락처
|
||||
address?: string; // 수신처 주소
|
||||
addressDetail?: string; // 상세주소
|
||||
subtotal?: number; // 소계 (supply_amount와 동일)
|
||||
discountRate?: number; // 할인율
|
||||
totalAmount?: number; // 총금액 (amount와 동일하지만 명시적)
|
||||
}
|
||||
|
||||
export interface OrderItem {
|
||||
id: string;
|
||||
itemId?: number;
|
||||
itemCode?: string; // 품목코드
|
||||
itemName: string;
|
||||
specification?: string;
|
||||
spec?: string; // specification alias
|
||||
type?: string; // 층 (layer)
|
||||
symbol?: string; // 부호
|
||||
quantity: number;
|
||||
unit?: string;
|
||||
unitPrice: number;
|
||||
supplyAmount: number;
|
||||
taxAmount: number;
|
||||
totalAmount: number;
|
||||
amount?: number; // totalAmount alias
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
@@ -191,10 +208,20 @@ export interface OrderFormData {
|
||||
remarks?: string;
|
||||
note?: string;
|
||||
items?: OrderItemFormData[];
|
||||
// 수정 페이지용 추가 필드
|
||||
expectedShipDate?: string; // 출고예정일
|
||||
deliveryRequestDate?: string; // 납품요청일
|
||||
deliveryMethod?: string; // 배송방식 (deliveryMethodCode alias)
|
||||
shippingCost?: string; // 운임비용
|
||||
receiver?: string; // 수신자
|
||||
receiverContact?: string; // 수신처 연락처
|
||||
address?: string; // 수신처 주소
|
||||
addressDetail?: string; // 상세주소
|
||||
}
|
||||
|
||||
export interface OrderItemFormData {
|
||||
itemId?: number;
|
||||
itemCode?: string; // 품목코드
|
||||
itemName: string;
|
||||
specification?: string;
|
||||
quantity: number;
|
||||
@@ -302,7 +329,18 @@ function transformApiToFrontend(apiData: ApiOrder): Order {
|
||||
memo: apiData.memo ?? undefined,
|
||||
remarks: apiData.remarks ?? undefined,
|
||||
note: apiData.note ?? undefined,
|
||||
items: apiData.items?.map(transformItemApiToFrontend),
|
||||
items: apiData.items?.map(transformItemApiToFrontend), // 상세 페이지용 추가 필드 (API에서 매핑)
|
||||
manager: apiData.client?.representative ?? undefined,
|
||||
contact: apiData.client_contact ?? apiData.client?.phone ?? undefined,
|
||||
deliveryRequestDate: apiData.delivery_date ?? undefined, // delivery_date를 공유
|
||||
shippingCost: undefined, // API에 해당 필드 없음 - 추후 구현
|
||||
receiver: undefined, // API에 해당 필드 없음 - 추후 구현
|
||||
receiverContact: undefined, // API에 해당 필드 없음 - 추후 구현
|
||||
address: undefined, // API에 해당 필드 없음 - 추후 구현
|
||||
addressDetail: undefined, // API에 해당 필드 없음 - 추후 구현
|
||||
subtotal: apiData.supply_amount,
|
||||
discountRate: apiData.discount_rate,
|
||||
totalAmount: apiData.total_amount,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -310,14 +348,19 @@ function transformItemApiToFrontend(apiItem: ApiOrderItem): OrderItem {
|
||||
return {
|
||||
id: String(apiItem.id),
|
||||
itemId: apiItem.item_id ?? undefined,
|
||||
itemCode: apiItem.item_id ? `ITEM-${apiItem.item_id}` : undefined, // 임시: 실제 item_code는 API에서 제공 필요
|
||||
itemName: apiItem.item_name,
|
||||
specification: apiItem.specification ?? undefined,
|
||||
spec: apiItem.specification ?? undefined, // specification alias
|
||||
type: undefined, // 층 - API에 해당 필드 없음
|
||||
symbol: undefined, // 부호 - API에 해당 필드 없음
|
||||
quantity: apiItem.quantity,
|
||||
unit: apiItem.unit ?? undefined,
|
||||
unitPrice: apiItem.unit_price,
|
||||
supplyAmount: apiItem.supply_amount,
|
||||
taxAmount: apiItem.tax_amount,
|
||||
totalAmount: apiItem.total_amount,
|
||||
amount: apiItem.total_amount, // totalAmount alias
|
||||
sortOrder: apiItem.sort_order,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -178,39 +178,36 @@ export function ProcessDetail({ process }: ProcessDetailProps) {
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<Package className="h-4 w-4" />
|
||||
개별 품목
|
||||
{individualItems.length > 0 && individualItems[0].items && (
|
||||
<Badge variant="secondary" className="ml-2">
|
||||
{individualItems[0].items.length}개
|
||||
</Badge>
|
||||
)}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-6">
|
||||
{individualItems.length === 0 ? (
|
||||
{individualItems.length === 0 || !individualItems[0].items?.length ? (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
<Package className="h-10 w-10 mx-auto mb-3 opacity-30" />
|
||||
<p className="text-sm">등록된 개별 품목이 없습니다</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{individualItems.map((rule) => (
|
||||
<div
|
||||
key={rule.id}
|
||||
className="flex items-center justify-between p-4 border rounded-lg"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<Badge variant={rule.isActive ? 'default' : 'secondary'}>
|
||||
{rule.isActive ? '활성' : '비활성'}
|
||||
</Badge>
|
||||
<div>
|
||||
<div className="font-medium">
|
||||
{rule.conditionValue}
|
||||
</div>
|
||||
{rule.description && (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{rule.description}
|
||||
</div>
|
||||
)}
|
||||
<div className="max-h-80 overflow-y-auto">
|
||||
<div className="space-y-2">
|
||||
{individualItems[0].items.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="flex items-center justify-between p-3 border rounded-lg bg-muted/30 hover:bg-muted/50 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge variant="outline" className="font-mono text-xs">
|
||||
{item.code}
|
||||
</Badge>
|
||||
<span className="font-medium">{item.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="outline">우선순위: {rule.priority}</Badge>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { Process, ProcessFormData, ClassificationRule } from '@/types/process';
|
||||
import type { Process, ProcessFormData, ClassificationRule, IndividualItem } from '@/types/process';
|
||||
|
||||
// ============================================================================
|
||||
// API 타입 정의
|
||||
@@ -107,13 +107,22 @@ function transformApiToFrontend(apiData: ApiProcess): Process {
|
||||
function transformProcessItemsToRules(processItems: ApiProcessItem[]): ClassificationRule[] {
|
||||
if (processItems.length === 0) return [];
|
||||
|
||||
const activeItems = processItems.filter(pi => pi.is_active);
|
||||
if (activeItems.length === 0) return [];
|
||||
|
||||
// 모든 품목 ID를 쉼표로 구분하여 하나의 규칙으로 통합
|
||||
const itemIds = processItems
|
||||
.filter(pi => pi.is_active)
|
||||
const itemIds = activeItems
|
||||
.map(pi => String(pi.item_id))
|
||||
.join(',');
|
||||
|
||||
if (!itemIds) return [];
|
||||
// 품목 상세 정보 추출 (code, name 포함)
|
||||
const items: IndividualItem[] = activeItems
|
||||
.filter(pi => pi.item) // item 정보가 있는 것만
|
||||
.map(pi => ({
|
||||
id: String(pi.item!.id),
|
||||
code: pi.item!.code,
|
||||
name: pi.item!.name,
|
||||
}));
|
||||
|
||||
return [{
|
||||
id: `individual-${Date.now()}`,
|
||||
@@ -122,9 +131,10 @@ function transformProcessItemsToRules(processItems: ApiProcessItem[]): Classific
|
||||
matchingType: 'equals',
|
||||
conditionValue: itemIds,
|
||||
priority: 0,
|
||||
description: `개별 품목 ${processItems.length}개`,
|
||||
description: `개별 품목 ${activeItems.length}개`,
|
||||
isActive: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
items, // 품목 상세 정보 추가
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,13 @@ export type RuleType = '품목코드' | '품목명' | '품목구분';
|
||||
// 매칭 방식
|
||||
export type MatchingType = 'startsWith' | 'endsWith' | 'contains' | 'equals';
|
||||
|
||||
// 개별 품목 정보
|
||||
export interface IndividualItem {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
// 자동 분류 규칙
|
||||
export interface ClassificationRule {
|
||||
id: string;
|
||||
@@ -28,6 +35,7 @@ export interface ClassificationRule {
|
||||
description?: string;
|
||||
isActive: boolean;
|
||||
createdAt: string;
|
||||
items?: IndividualItem[]; // 개별 품목인 경우 품목 정보
|
||||
}
|
||||
|
||||
// 자동 분류 규칙 입력용 (id, createdAt 제외)
|
||||
|
||||
Reference in New Issue
Block a user