feat(WEB): 수주등록 품목을 제품 모델+타입별 그룹핑 표시

- 견적의 calculation_inputs에서 productCode 정보를 수주등록으로 전달
- quote_items.note 파싱으로 floor/code 추출 (type_code/symbol 컬럼 부재 대응)
- 제품코드(FG-KWE01-벽면형-SUS)에서 그룹핑 키(KWE01-SUS) 추출
- 그룹 내 동일 품목(item_code 기준) 수량/금액 합산하여 1행으로 표시
- quotes/actions.ts BomCalculationResult 타입을 types.ts와 일치시켜 TS 에러 해결
This commit is contained in:
2026-02-04 23:04:47 +09:00
parent abd243fce2
commit f1c4ab62bf
4 changed files with 316 additions and 74 deletions

View File

@@ -120,6 +120,18 @@ interface ApiQuoteForSelect {
grade?: string; // 등급 (A, B, C)
} | null;
items?: ApiQuoteItem[];
calculation_inputs?: {
items?: Array<{
productCategory?: string;
productCode?: string;
productName?: string;
openWidth?: string;
openHeight?: string;
quantity?: number;
floor?: string;
code?: string;
}>;
} | null;
}
interface ApiQuoteItem {
@@ -128,6 +140,7 @@ interface ApiQuoteItem {
item_name: string;
type_code?: string;
symbol?: string;
note?: string; // "5F FSS-01" 형태 (floor + code)
specification?: string;
// QuoteItem 모델 필드명 (calculated_quantity, total_price)
calculated_quantity?: number;
@@ -402,6 +415,18 @@ export interface QuotationForSelect {
manager?: string; // 담당자
contact?: string; // 연락처
items?: QuotationItem[]; // 품목 내역
calculationInputs?: {
items?: Array<{
productCategory?: string;
productCode?: string;
productName?: string;
openWidth?: string;
openHeight?: string;
quantity?: number;
floor?: string;
code?: string;
}>;
};
}
export interface QuotationItem {
@@ -643,6 +668,18 @@ function transformQuoteForSelect(apiData: ApiQuoteForSelect): QuotationForSelect
manager: apiData.manager ?? undefined,
contact: apiData.contact ?? apiData.client?.phone ?? undefined,
items: apiData.items?.map(transformQuoteItemForSelect),
calculationInputs: apiData.calculation_inputs ? {
items: apiData.calculation_inputs.items?.map(item => ({
productCategory: item.productCategory,
productCode: item.productCode,
productName: item.productName,
openWidth: item.openWidth,
openHeight: item.openHeight,
quantity: item.quantity,
floor: item.floor,
code: item.code,
})),
} : undefined,
};
}
@@ -655,12 +692,23 @@ function transformQuoteItemForSelect(apiItem: ApiQuoteItem): QuotationItem {
// amount fallback: total_price → total_amount → 수량 * 단가 계산
const amount = Number(apiItem.total_price ?? apiItem.total_amount ?? 0) || (quantity * unitPrice);
// note에서 floor+code 추출: "5F FSS-01" → type="5F", symbol="FSS-01"
let typeFromNote = apiItem.type_code || '';
let symbolFromNote = apiItem.symbol || '';
if (!typeFromNote && !symbolFromNote && apiItem.note) {
const noteParts = apiItem.note.trim().split(/\s+/);
if (noteParts.length >= 2) {
typeFromNote = noteParts[0];
symbolFromNote = noteParts.slice(1).join(' ');
}
}
return {
id: String(apiItem.id),
itemCode: apiItem.item_code || '',
itemName: apiItem.item_name,
type: apiItem.type_code || '',
symbol: apiItem.symbol || '',
type: typeFromNote,
symbol: symbolFromNote,
spec: apiItem.specification || '',
quantity,
unit: apiItem.unit || 'EA',