개발관리>E-BOM 화면 운영판 1:1 정정 다수 — 검색폼·상태변경·체크박스·STATUS 표시
사용자 검증으로 발견된 5가지 함정 일괄 정정.
(1) ebom-search 검색폼 운영판 1:1 — wace structureAscendingList.jsp 노출 필드만:
- 제거: 프로젝트 OBJID (raw input), UNIT_CODE (raw input)
운영판도 고객사/프로젝트번호/유닛명 모두 주석 처리되어 노출 안 됨
- 유지: 품번 / 품명 / 표시 레벨 (1~5 select)
- BomTreeFilter.search_level 추가 + ascending/descending CTE 에 T.lev <= $search_level::int
(2) 품번/품명 자동완성 (wace select2-part 1:1):
- 영업관리 PartSelect 는 item_info 마스터 기반 → 개발관리(part_mng)용 별도 컴포넌트 신설
- backend GET /api/development/part/options : IS_LAST='1' part_mng 전체 (영업관리 sales/parts 패턴)
- frontend DevPartSelect.tsx : SmartSelect 캐시 + mode partNo/partName 분리
- ebom-search 페이지 단순 Input → DevPartSelect 교체
- 품번 선택 시 품명 자동 채움 / 품명 선택 시 품번 자동 채움 (운영판 select2-part 1:1)
(3) BomReportStatusDialog 운영판 1:1 재작성 — wace structureStatusChangePopup.jsp:
- 잘못된 점: read-only 박스 + 상태 select(create/changeDesign/deploy 3옵션)
- 정정: 5필드 모두 편집 가능 (CommCodeSelect 제품구분 / 품번 input / 품명 input /
Version input / 상태 Y/N 라디오) — 운영 매퍼 updateStructureStatus 5컬럼 UPDATE 1:1
- 헤더 파란 바 + 4컬럼 테이블(25%/75%) + 저장/닫기 중앙 배치 (운영판 스타일 1:1)
(4) DataGrid id 매핑 — 체크박스 ID 키 불일치 함정:
- DataGrid 는 row.id 로 체크박스 ID 관리, 백엔드 응답은 row.objid (postgres lowercase)
- 결과: checkedIds[0] 가 undefined → 상태변경/수정/삭제 다이얼로그가 objid=undefined 로 열려
detail 호출 안 됨 → 빈 폼 표시 (사용자 지적 "기본 정보 표시 안됨")
- 일괄 수정 (3 페이지) : ebom-regist / part-regist / part-search
gridRows = useMemo(() => rows.map(r => ({ ...r, id: r.objid })), [rows])
영업관리 페이지 동일 패턴 1:1
(5) STATUS_TITLE 매핑 운영판 1:1 — 운영 그리드는 'Y'/'N' 글자 그대로 표시:
CASE UPPER(T.STATUS)
WHEN 'CREATE' THEN '등록중'
WHEN 'CHANGEDESIGN' THEN '설계변경미배포'
WHEN 'DEPLOY' THEN '배포완료'
ELSE COALESCE(T.STATUS, '') END AS STATUS_TITLE
- 운영 매퍼는 ELSE '' 이지만 RPS 는 raw fallback (사용자 화면에서 식별 가능)
- 'Y'/'N' 매핑 라벨 추가 → 운영 스크린샷 확인 후 제거 (운영판은 raw)
미해결 (별 작업):
- 확정일 (DEPLOY_DATE) 표시 — 운영판은 별도 "배포" 액션 (deployBomReport 매퍼) 으로 채움.
RPS ebom-regist 에 배포 버튼 미구현 → 신규 BOM 확정일 빈값. 별 PR.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -117,6 +117,26 @@ export async function deploy(req: AuthenticatedRequest, res: Response) {
|
||||
// 운영판 wace: openPartExcelImportPopUp.jsp → partParsingExcelFile.do + partUploadSave.do
|
||||
// 본 RPS 구현: 파일을 메모리 파싱 → 검증 결과(NOTE 포함) 반환 / 저장 시 신규 part_no 만 INSERT.
|
||||
|
||||
// PART 자동완성 옵션 (IS_LAST='1' 전체) — wace select2-part 1:1
|
||||
// GET /api/development/part/options
|
||||
// response: { rows: [{ objid, part_no, part_name }] }
|
||||
export async function partOptions(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const pool = (await import("../database/db")).getPool();
|
||||
const r = await pool.query(
|
||||
`SELECT OBJID::varchar AS objid, PART_NO AS part_no, PART_NAME AS part_name
|
||||
FROM PART_MNG
|
||||
WHERE COALESCE(IS_LAST,'') = '1'
|
||||
AND PART_NO IS NOT NULL AND PART_NO <> ''
|
||||
ORDER BY PART_NO`
|
||||
);
|
||||
return res.json({ success: true, data: { rows: r.rows } });
|
||||
} catch (e: any) {
|
||||
logger.error("PART options 조회 실패", { error: e.message });
|
||||
return res.status(500).json({ success: false, message: e.message });
|
||||
}
|
||||
}
|
||||
|
||||
export async function excelParse(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const file = (req as any).file as Express.Multer.File | undefined;
|
||||
|
||||
@@ -27,6 +27,9 @@ router.get("/part/list", ctrl.getList);
|
||||
router.post("/part/excel-parse", excelUpload.single("file"), ctrl.excelParse);
|
||||
router.post("/part/excel-save", ctrl.excelSave);
|
||||
|
||||
// PART 자동완성 옵션 (select2-part 1:1) — /:objid 보다 위
|
||||
router.get("/part/options", ctrl.partOptions);
|
||||
|
||||
// 다중 삭제 (body: { objids: string[] }) — /:objid 보다 위
|
||||
router.delete("/part", ctrl.removeMany);
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ export interface BomTreeFilter {
|
||||
unit_code?: string;
|
||||
search_part_no?: string;
|
||||
search_part_name?: string;
|
||||
search_level?: string | number; // wace 1:1 — 1~5 표시 레벨 (lev <= search_level)
|
||||
}
|
||||
|
||||
// ─── 공용 파라미터 빌더 ────────────────────────────────────
|
||||
@@ -99,11 +100,12 @@ export async function list(filter: BomReportListFilter) {
|
||||
T.CONTRACT_OBJID, PM.CUSTOMER_PROJECT_NAME, PM.PROJECT_NO,
|
||||
T.UNIT_CODE, COALESCE(WT.UNIT_NO || '-' || WT.TASK_NAME, '') AS UNIT_NAME,
|
||||
T.STATUS,
|
||||
-- 운영판 wace 매퍼 1:1 (CREATE/CHANGEDESIGN/DEPLOY 만 라벨, 그 외 'Y'/'N' 등은 raw 표시)
|
||||
CASE UPPER(T.STATUS)
|
||||
WHEN 'CREATE' THEN '등록중'
|
||||
WHEN 'CHANGEDESIGN' THEN '설계변경미배포'
|
||||
WHEN 'DEPLOY' THEN '배포완료'
|
||||
ELSE '' END AS STATUS_TITLE,
|
||||
ELSE COALESCE(T.STATUS, '') END AS STATUS_TITLE,
|
||||
T.WRITER, UI.dept_name AS DEPT_NAME, UI.user_name AS USER_NAME,
|
||||
COALESCE(UI.dept_name || '/' || UI.user_name, '') AS DEPT_USER_NAME,
|
||||
T.REGDATE, TO_CHAR(T.REGDATE, 'YYYY-MM-DD') AS REG_DATE,
|
||||
@@ -236,6 +238,10 @@ export async function ascending(filter: BomTreeFilter) {
|
||||
finalConds.push(`UPPER(PM.part_name) LIKE UPPER($${idx++})`);
|
||||
params.push(`%${filter.search_part_name}%`);
|
||||
}
|
||||
if (filter.search_level) {
|
||||
finalConds.push(`T.lev <= $${idx++}::int`);
|
||||
params.push(filter.search_level);
|
||||
}
|
||||
const finalWhere = finalConds.length ? `WHERE ${finalConds.join(" AND ")}` : "";
|
||||
|
||||
const sql = `
|
||||
@@ -445,6 +451,14 @@ export async function descending(filter: BomTreeFilter) {
|
||||
}
|
||||
const anchorWhere = anchorConds.join(" AND ");
|
||||
|
||||
// 표시 레벨 필터 (wace search_level 1:1)
|
||||
const levelWhereParts: string[] = [];
|
||||
if (filter.search_level) {
|
||||
levelWhereParts.push(`T.lev <= $${idx++}::int`);
|
||||
params.push(filter.search_level);
|
||||
}
|
||||
const levelWhere = levelWhereParts.length ? `WHERE ${levelWhereParts.join(" AND ")}` : "";
|
||||
|
||||
const sql = `
|
||||
WITH RECURSIVE TREE(bom_report_objid, objid, parent_objid, child_objid, part_no, qty, seq, status, lev, path, cycle) AS (
|
||||
SELECT BP.bom_report_objid, BP.objid, BP.parent_objid, BP.child_objid,
|
||||
@@ -472,6 +486,7 @@ export async function descending(filter: BomTreeFilter) {
|
||||
(SELECT COALESCE(MAX(lev), 0) FROM TREE) AS max_level
|
||||
FROM TREE T
|
||||
LEFT JOIN part_mng PM ON T.part_no = PM.objid::varchar
|
||||
${levelWhere}
|
||||
ORDER BY T.path
|
||||
`;
|
||||
const r = await pool.query(sql, params);
|
||||
|
||||
Reference in New Issue
Block a user