429b1d1e8a
사용자 검증 중 두 가지 문제 발견:
1) 트리가 1레벨만 표시되고 자식들이 안 풀림 — ascending/descending CTE 의 재귀
조인 조건 버그.
잘못된 조인: B.parent_objid = T.objid
올바른 조인: B.parent_objid = T.child_objid (ascending),
B.child_objid = T.parent_objid (descending)
wace relatePartInfo 1:1 패턴 — BOM_PART_QTY INSERT 시 OBJID 와 별도로
createObjId() 따로 발급되는 CHILD_OBJID 가 부모 식별자. 자식 행의 PARENT_OBJID
는 부모 행의 CHILD_OBJID 와 매칭됨 (PARENT_OBJID = T.OBJID 가 아님).
4 함수 일괄 정정 : ascending / descending / ascendingForExcel / descendingForExcel
검증 : test-20003-0082 BOM → Level 1 루트 + Level 2 자식 2건 정상 트리 출력.
2) E-BOM 컬럼이 숫자(bom_cnt)로 표시되어 어디를 눌러 BOM 구조를 봐야 할지 불명확.
운영판 wace 는 fnc_getFolderIcon 으로 폴더 아이콘 + 클릭 → setStructurePopupMainFS.
backend:
- GET /api/development/ebom-tree/full → ascendingForExcel 1:1, 풀 컬럼 JSON 노출
(HEAT_TREATMENT_HARDNESS/METHOD/SURFACE_TREATMENT/MAKER/PART_TYPE_TITLE/CU_파일 카운트 포함)
frontend:
- BomReportTreeDialog.tsx 신설 (wace 4-Frame 팝업 → 단일 다이얼로그 통합)
· 헤더 메타 8필드 (제품구분/품번/품명/Version/상태/등록자/등록일/확정일)
· 동적 LEVEL 컬럼 (L1..LMaxLevel, "*" 표시) + 운영판 14컬럼
· 노란 배경 헤더 (운영판 스타일 1:1)
· 엑셀 다운로드 버튼 (해당 BOM 만 ascendingForExcel 호출)
- lib/api/devBom.ts : treeFull() API + BomTreeFullRow 타입
- app/.../ebom-regist/page.tsx :
· bom_cnt 컬럼 formatNumber → renderType: "folder" (wace fnc_getFolderIcon 1:1)
· 클릭 핸들러를 품번 → E-BOM 폴더 셀로 이동 (운영판 fn_openSetStructure 동작)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
304 lines
9.0 KiB
TypeScript
304 lines
9.0 KiB
TypeScript
import { apiClient } from "./client";
|
|
|
|
// Content-Disposition 의 filename / filename* 파싱 (UTF-8 인코딩 우선)
|
|
function extractFileName(cd: string | undefined): string | null {
|
|
if (!cd) return null;
|
|
const utf8 = /filename\*=UTF-8''([^;]+)/i.exec(cd);
|
|
if (utf8 && utf8[1]) {
|
|
try { return decodeURIComponent(utf8[1]); } catch { /* fallthrough */ }
|
|
}
|
|
const plain = /filename="?([^";]+)"?/i.exec(cd);
|
|
if (plain && plain[1]) {
|
|
try { return decodeURIComponent(plain[1]); } catch { return plain[1]; }
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// ============================================================
|
|
// 개발관리 E-BOM (M3 등록 / M4 조회) — wace partMng.xml 1:1
|
|
// 라우트: /api/development/ebom/*, /api/development/ebom-tree/*
|
|
// ============================================================
|
|
|
|
export interface BomReportListFilter {
|
|
customer_cd?: string;
|
|
project_name?: string;
|
|
unit_code?: string;
|
|
search_unit_name?: string;
|
|
search_writer?: string;
|
|
product_cd?: string;
|
|
search_part_no?: string;
|
|
search_part_name?: string;
|
|
search_from_date?: string;
|
|
search_to_date?: string;
|
|
status?: string;
|
|
page?: number;
|
|
page_size?: number;
|
|
}
|
|
|
|
export interface BomReportRow {
|
|
num: number | string;
|
|
objid: string;
|
|
customer_objid: string | null;
|
|
customer_name: string | null;
|
|
contract_objid: string | null;
|
|
customer_project_name: string | null;
|
|
project_no: string | null;
|
|
unit_code: string | null;
|
|
unit_name: string | null;
|
|
status: string | null;
|
|
status_title: string | null;
|
|
writer: string | null;
|
|
dept_name: string | null;
|
|
user_name: string | null;
|
|
dept_user_name: string | null;
|
|
regdate: string | null;
|
|
reg_date: string | null;
|
|
deploy_date: string | null;
|
|
revision: string | null;
|
|
eo_no: string | null;
|
|
eo_date: string | null;
|
|
note: string | null;
|
|
multi_yn: string | null;
|
|
multi_master_yn: string | null;
|
|
multi_break_yn: string | null;
|
|
multi_master_objid: string | null;
|
|
bom_cnt: number | string | null;
|
|
product_cd: string | null;
|
|
product_name: string | null;
|
|
part_no: string | null;
|
|
part_name: string | null;
|
|
}
|
|
|
|
export interface BomReportListResponse {
|
|
rows: BomReportRow[];
|
|
total: number;
|
|
page: number;
|
|
pageSize: number;
|
|
}
|
|
|
|
export interface BomReportStatusBody {
|
|
product_cd?: string;
|
|
part_no?: string;
|
|
part_name?: string;
|
|
version?: string;
|
|
status: string;
|
|
}
|
|
|
|
export interface BomTreeFilter {
|
|
bom_report_objid?: string;
|
|
project_name?: string;
|
|
unit_code?: string;
|
|
search_part_no?: string;
|
|
search_part_name?: string;
|
|
}
|
|
|
|
export interface BomTreeRow {
|
|
bom_report_objid: string | null;
|
|
objid: string;
|
|
parent_objid: string | null;
|
|
child_objid: string | null;
|
|
part_no: string | null; // bom_part_qty.part_no (= part_mng.objid)
|
|
qty: string | null;
|
|
seq: number | string | null;
|
|
status: string | null;
|
|
lev: number;
|
|
path: string[] | null;
|
|
// part_mng JOIN
|
|
pm_part_no: string | null;
|
|
pm_part_name: string | null;
|
|
spec: string | null;
|
|
material: string | null;
|
|
weight: string | null;
|
|
remark: string | null;
|
|
edit_date: string | null;
|
|
eo_no: string | null;
|
|
revision: string | null;
|
|
cu01_cnt: number | string | null;
|
|
cu02_cnt: number | string | null;
|
|
cu03_cnt: number | string | null;
|
|
max_level: number | string | null;
|
|
}
|
|
|
|
export interface BomTreeResponse {
|
|
rows: BomTreeRow[];
|
|
max_level: number;
|
|
}
|
|
|
|
// 트리 풀 컬럼 (ascendingForExcel 1:1) — BomReportTreeDialog 용
|
|
export interface BomTreeFullRow {
|
|
lev: number | string;
|
|
pm_part_no: string | null;
|
|
pm_part_name: string | null;
|
|
qty: string | number | null;
|
|
p_qty: string | number | null;
|
|
material: string | null;
|
|
remark: string | null;
|
|
heat_treatment_hardness: string | null;
|
|
heat_treatment_method: string | null;
|
|
surface_treatment: string | null;
|
|
maker: string | null;
|
|
part_type: string | null;
|
|
part_type_title: string | null;
|
|
cu01_cnt: number | string | null;
|
|
cu02_cnt: number | string | null;
|
|
cu03_cnt: number | string | null;
|
|
}
|
|
export interface BomTreeFullResponse {
|
|
rows: BomTreeFullRow[];
|
|
max_level: number;
|
|
}
|
|
|
|
// ─── API ─────────────────────────────────────────────────
|
|
|
|
export const devBomApi = {
|
|
// M3 그리드
|
|
async list(filter: BomReportListFilter = {}): Promise<BomReportListResponse> {
|
|
const res = await apiClient.get("/development/ebom/list", { params: filter });
|
|
return res.data?.data as BomReportListResponse;
|
|
},
|
|
|
|
async detail(objid: string): Promise<BomReportRow | null> {
|
|
const res = await apiClient.get(`/development/ebom/${objid}`);
|
|
return res.data?.data ?? null;
|
|
},
|
|
|
|
async updateStatus(objid: string, body: BomReportStatusBody) {
|
|
return (await apiClient.put(`/development/ebom/${objid}/status`, body)).data;
|
|
},
|
|
|
|
async remove(objids: string[]) {
|
|
const res = await apiClient.delete("/development/ebom", { data: { objids } });
|
|
return res.data;
|
|
},
|
|
|
|
// M4
|
|
async ascending(filter: BomTreeFilter): Promise<BomTreeResponse> {
|
|
const res = await apiClient.get("/development/ebom-tree/ascending", { params: filter });
|
|
return res.data?.data as BomTreeResponse;
|
|
},
|
|
|
|
async descending(filter: BomTreeFilter): Promise<BomTreeResponse> {
|
|
const res = await apiClient.get("/development/ebom-tree/descending", { params: filter });
|
|
return res.data?.data as BomTreeResponse;
|
|
},
|
|
|
|
// E-BOM 트리 (풀 컬럼) — M3 그리드 행 클릭 → BomReportTreeDialog
|
|
async treeFull(filter: BomTreeFilter): Promise<BomTreeFullResponse> {
|
|
const res = await apiClient.get("/development/ebom-tree/full", { params: filter });
|
|
return res.data?.data as BomTreeFullResponse;
|
|
},
|
|
|
|
// M4 엑셀 다운로드 (정/역전개) — wace 1:1
|
|
async excelAscending(filter: BomTreeFilter): Promise<{ blob: Blob; fileName: string }> {
|
|
const res = await apiClient.get("/development/ebom-tree/ascending/excel", {
|
|
params: filter, responseType: "blob",
|
|
});
|
|
return { blob: res.data as Blob, fileName: extractFileName(res.headers?.["content-disposition"]) ?? "BOM_ascending.xlsx" };
|
|
},
|
|
async excelDescending(filter: BomTreeFilter): Promise<{ blob: Blob; fileName: string }> {
|
|
const res = await apiClient.get("/development/ebom-tree/descending/excel", {
|
|
params: filter, responseType: "blob",
|
|
});
|
|
return { blob: res.data as Blob, fileName: extractFileName(res.headers?.["content-disposition"]) ?? "BOM_descending.xlsx" };
|
|
},
|
|
|
|
// Excel Import
|
|
async excelParse(file: File): Promise<BomExcelParseResponse> {
|
|
const fd = new FormData();
|
|
fd.append("file", file);
|
|
const res = await apiClient.post("/development/ebom/excel-parse", fd, {
|
|
headers: { "Content-Type": "multipart/form-data" },
|
|
});
|
|
return res.data?.data as BomExcelParseResponse;
|
|
},
|
|
|
|
async excelCheckDuplicate(partNo: string, exclude?: string): Promise<boolean> {
|
|
const res = await apiClient.get("/development/ebom/excel-check-duplicate", {
|
|
params: { partNo, exclude },
|
|
});
|
|
return !!res.data?.data?.isDuplicate;
|
|
},
|
|
|
|
async excelCopySource(productCd?: string): Promise<BomCopySourceRow[]> {
|
|
const res = await apiClient.get("/development/ebom/excel-copy-source", {
|
|
params: productCd ? { productCd } : undefined,
|
|
});
|
|
return (res.data?.data as BomCopySourceRow[]) ?? [];
|
|
},
|
|
|
|
async excelCopy(objid: string): Promise<BomCsvRow[]> {
|
|
const res = await apiClient.get(`/development/ebom/excel-copy/${objid}`);
|
|
return ((res.data?.data?.rows as BomCsvRow[]) ?? []);
|
|
},
|
|
|
|
async excelSave(input: BomExcelSaveInput): Promise<BomExcelSaveResult> {
|
|
const res = await apiClient.post("/development/ebom/excel-save", input);
|
|
return res.data?.data as BomExcelSaveResult;
|
|
},
|
|
};
|
|
|
|
// ─── CSV Import 타입 (wace parsingCsvFile 1:1) ─────────────
|
|
|
|
export interface BomCsvRow {
|
|
NOTE: string;
|
|
LEVEL: string;
|
|
PARENT_PART_NO: string;
|
|
PART_NO: string;
|
|
PART_NAME: string;
|
|
QTY: string;
|
|
ITEM_QTY: string;
|
|
MATERIAL: string;
|
|
HEAT_TREATMENT_HARDNESS: string;
|
|
HEAT_TREATMENT_METHOD: string;
|
|
SURFACE_TREATMENT: string;
|
|
MAKER: string;
|
|
PART_TYPE: string;
|
|
PART_TYPE_NAME: string;
|
|
ACCTFG: string;
|
|
ODRFG: string;
|
|
UNIT_DC: string;
|
|
UNITMANG_DC: string;
|
|
UNITCHNG_NB: string;
|
|
LOT_FG: string;
|
|
USE_YN: string;
|
|
QC_FG: string;
|
|
SETITEM_FG: string;
|
|
REQ_FG: string;
|
|
}
|
|
|
|
// 기존 코드 호환용 별칭 (필요 시 마이그레이션)
|
|
export type BomExcelRow = BomCsvRow;
|
|
|
|
export interface BomExcelParseResponse {
|
|
rows: BomCsvRow[];
|
|
hasError: boolean;
|
|
firstLevel: { part_no: string; part_name: string } | null;
|
|
encoding: string;
|
|
}
|
|
|
|
export interface BomCopySourceRow {
|
|
objid: string;
|
|
part_no: string;
|
|
part_name: string;
|
|
revision: string | null;
|
|
product_cd: string | null;
|
|
regdate: string | null;
|
|
}
|
|
|
|
export interface BomExcelSaveInput {
|
|
bomReportObjid?: string;
|
|
productCd: string;
|
|
partNo: string;
|
|
partName: string;
|
|
version?: string;
|
|
rows: BomCsvRow[];
|
|
}
|
|
|
|
export interface BomExcelSaveResult {
|
|
bomReportObjid: string;
|
|
insertedParts: number;
|
|
updatedParts: number;
|
|
bomRows: number;
|
|
mode: "create" | "update";
|
|
}
|