생산관리 M-BOM PR-B5+ — BOM 할당 다이얼로그 운영판 1:1 재구성 + 미리보기 트리

운영판 mBomEbomSelectPopup.jsp (324 lines) 1:1 레이아웃:
  · 헤더 — "E-BOM 선택" / "E-BOM 상세 및 변경" 토글 제목 + 우측 [E-BOM 변경] 버튼
  · 현재 할당된 E-BOM 정보 카드 — 2×3 table (제품구분/품번/품명/Ver/등록일/작성자)
  · 리스트 토글 — 할당된 경우 변경 모드 ON 시에만 노출
  · 검색폼 운영판 매칭 — 제품구분(SmartSelect) + 품번 + 품명 + 등록일(범위)
  · 리스트 헤더 우측 [조회][E-BOM 할당] 버튼 (footer 제거)
  · 선택 시 하단 미리보기 트리 자동 로드 (read-only, 동적 LEVEL + 폴더아이콘)

신규 백엔드:
  · mbomService.previewEbomTree(bomReportObjid) — EBOM_WORKING_TREE_SQL 재사용
  · GET /api/production/mbom/ebom-preview/:bomReportObjid
  · searchAssignableEboms 필터: material/supplier → product_cd/from_date/to_date 운영판 매칭
  · objid 단건 필터 추가 — 현재 할당 E-BOM 카드 정보 자체 로드용

프론트:
  · MbomAssignDialog 완전 재작성 (full layout, SmartSelect, 미리보기 PreviewTree 컴포넌트)
  · MbomDetailDialog: currentEbomObjid prop 으로 단순화 (다이얼로그 내부에서 상세 fetch)
  · mbomApi.previewEbomTree + AssignableEbomFilter 타입 매칭

이전 b38f5957 의 PR-B5 베이스(검색/할당) 위에 운영판 UX 정합 강화.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
hjjeong
2026-05-14 18:13:10 +09:00
parent b38f5957f2
commit bd47ca80df
6 changed files with 368 additions and 132 deletions
+17 -2
View File
@@ -58,10 +58,12 @@ export async function searchAssignableEboms(req: AuthenticatedRequest, res: Resp
try {
const q = req.query as Record<string, any>;
const data = await svc.searchAssignableEboms({
objid: String(q.objid ?? "").trim() || undefined,
product_cd: String(q.product_cd ?? "").trim() || undefined,
search_part_no: String(q.search_part_no ?? "").trim() || undefined,
search_part_name: String(q.search_part_name ?? "").trim() || undefined,
search_material: String(q.search_material ?? "").trim() || undefined,
search_supplier: String(q.search_supplier ?? "").trim() || undefined,
search_from_date: String(q.search_from_date ?? "").trim() || undefined,
search_to_date: String(q.search_to_date ?? "").trim() || undefined,
limit: q.limit ? Number(q.limit) : undefined,
});
return res.json({ success: true, data });
@@ -71,6 +73,19 @@ export async function searchAssignableEboms(req: AuthenticatedRequest, res: Resp
}
}
// PR-B5 — E-BOM 미리보기 트리 (운영 mBomEbomSelectPopup.jsp iframe 대체)
export async function previewEbomTree(req: AuthenticatedRequest, res: Response) {
try {
const bomReportObjid = String(req.params.bomReportObjid ?? "").trim();
if (!bomReportObjid) return res.status(400).json({ success: false, message: "bomReportObjid 누락" });
const data = await svc.previewEbomTree(bomReportObjid);
return res.json({ success: true, data });
} catch (e: any) {
logger.error("E-BOM 미리보기 조회 실패", { error: e.message });
return res.status(500).json({ success: false, message: e.message });
}
}
// PR-B5 — BOM 할당 (운영 saveBomAssignment.do 1:1)
export async function assignBom(req: AuthenticatedRequest, res: Response) {
try {
@@ -17,6 +17,7 @@ router.post("/save", ctrl.save); // PR-B1 본 편집 저장
router.get("/history/:projectObjid", ctrl.getHistory); // PR-B4 변경이력 조회
router.post("/sales-request", ctrl.createSalesRequest); // PR-B3 구매리스트 생성
router.get("/assignable-eboms", ctrl.searchAssignableEboms); // PR-B5 할당 가능 E-BOM 검색
router.get("/ebom-preview/:bomReportObjid", ctrl.previewEbomTree); // PR-B5 E-BOM 미리보기
router.post("/assign", ctrl.assignBom); // PR-B5 BOM 할당
export default router;
+28 -8
View File
@@ -1241,10 +1241,12 @@ export async function save(payload: MbomSavePayload, sessionUserId: string): Pro
// (M-BOM 할당은 PR-B5 v2 — 우선 E-BOM 만)
export interface AssignableEbomFilter {
objid?: string; // 단건 정확매칭 (현재 할당 E-BOM 정보 조회용)
product_cd?: string;
search_part_no?: string;
search_part_name?: string;
search_material?: string;
search_supplier?: string;
search_from_date?: string; // YYYY-MM-DD
search_to_date?: string;
limit?: number;
}
@@ -1268,6 +1270,14 @@ export async function searchAssignableEboms(filter: AssignableEbomFilter): Promi
const conds: string[] = [];
const params: any[] = [];
let idx = 1;
if (filter.objid) {
conds.push(`T.OBJID::VARCHAR = $${idx++}`);
params.push(filter.objid);
}
if (filter.product_cd) {
conds.push(`T.PRODUCT_CD = $${idx++}`);
params.push(filter.product_cd);
}
if (filter.search_part_no) {
conds.push(`UPPER(T.PART_NO) LIKE '%' || UPPER($${idx++}) || '%'`);
params.push(filter.search_part_no);
@@ -1276,13 +1286,13 @@ export async function searchAssignableEboms(filter: AssignableEbomFilter): Promi
conds.push(`UPPER(T.PART_NAME) LIKE '%' || UPPER($${idx++}) || '%'`);
params.push(filter.search_part_name);
}
if (filter.search_material) {
conds.push(`UPPER(PM.MATERIAL) LIKE '%' || UPPER($${idx++}) || '%'`);
params.push(filter.search_material);
if (filter.search_from_date) {
conds.push(`T.REGDATE >= TO_DATE($${idx++}, 'YYYY-MM-DD')`);
params.push(filter.search_from_date);
}
if (filter.search_supplier) {
conds.push(`UPPER(PM.MAKER) LIKE '%' || UPPER($${idx++}) || '%'`);
params.push(filter.search_supplier);
if (filter.search_to_date) {
conds.push(`T.REGDATE < TO_DATE($${idx++}, 'YYYY-MM-DD') + INTERVAL '1 day'`);
params.push(filter.search_to_date);
}
const limit = Math.min(500, Math.max(1, Number(filter.limit) || 100));
const sql = `
@@ -1314,6 +1324,16 @@ export async function searchAssignableEboms(filter: AssignableEbomFilter): Promi
return r.rows;
}
// E-BOM 미리보기 트리 — bom_report_objid 만으로 EBOM_WORKING_TREE_SQL 호출.
// 운영판 mBomEbomSelectPopup.jsp 의 iframe 미리보기 대체.
export async function previewEbomTree(bomReportObjid: string): Promise<MbomTreeResult> {
if (!bomReportObjid) {
return { bom_data_type: "NONE", bom_report_objid: null, max_level: 1, rows: [] };
}
const rows = await getEbomWorkingTree(bomReportObjid);
return finalize("ASSIGNED_EBOM", bomReportObjid, rows);
}
export type AssignSourceType = "EBOM" | "MBOM";
export async function assignBom(