· 메인 그리드 5컬럼(제품구분/제목/WBS/등록자/등록일) + 통합 팝업(트리 CRUD + 엑셀 임포트 + 템플릿 다운로드) · 운영 매핑: pms_wbs_template(헤더) + pms_wbs_task_standard(트리) — 활성 갈래 확정 (_info/_standard2 갈래는 2021년 멈춘 레거시) · wace mergeExcelUploadWBS 1:1: 신규=헤더+트리 INSERT, 수정=트리 일괄 DELETE→INSERT (헤더 변경 없음) · objid 채번 gen_random_uuid()::text, 엑셀 파싱 xlsx(SheetJS), 정적 템플릿 frontend/public/templates/ · DataGrid 컬럼 단위 onClick 추가 (WBS 폴더 셀 클릭용) · DDL: 8개 테이블 162컬럼 (docs/migration/project/ddl-extracted/200_pms_wbs.sql) / GAP: docs/migration/project/02-wbs-template.md · 프로젝트 자동 복사/진행관리 연계는 wace도 미완성 — P2 범위 외 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
21 KiB
P2: 프로젝트관리 > 제품구분_WBS관리 (WBS 템플릿)
작성일: 2026-05-12 원본: wace_plm
/project/wbsTemplateMngList.do+/project/WBSExcelImportPopUp.do통합 워크플로 운영판 URL: https://waceplm.esgrin.com/main.do → 프로젝트관리 > 제품구분_WBS관리 대상 DB 테이블:pms_wbs_template(헤더),pms_wbs_task_standard(트리)
1. 범위 (Scope)
1.1 범위 안
- 메인 그리드 (5컬럼): 제품구분 / 제목 / WBS(폴더 아이콘) / 등록자 / 등록일
- 검색: 제품구분 단일 셀렉트
- 신규 등록: 통합 팝업 진입 (
WBSExcelImportPopUp.do?product=...) - 수정: 통합 팝업 진입 (
WBSExcelImportPopUp.do?templateObjId=...) - 삭제: 다건 선택 후 헤더+트리 cascade
- 통합 팝업: 헤더(제품구분/제목) + 트리 CRUD(추가/하위추가/삭제) + 엑셀 임포트 + 템플릿 다운로드 + 저장
1.2 범위 밖 (의도적 제외)
- 진행관리(P1) WBS 연계 (= 프로젝트 생성 시 템플릿 자동 복사) — wace도 미완성 영역, vexplor_rps에서도 손대지 않음
- 간트차트 / FN Task 연결 / 작업확정 / 제품별 WBS / 셋업 WBS — wace의 30+ endpoint 중 진행관리/별도 메뉴용
pms_wbs_task본체(트리) — 진행관리에서 사용 예정, 본 메뉴 미사용
2. 운영판 화면 검증 (2026-05-11 캡처)
2.1 메인 그리드
| 영역 | 내용 |
|---|---|
| 화면 제목 | "프로젝트관리_제품구분_WBS관리" |
| 우상단 버튼 | 삭제 / 등록 / 조회 / 초기화 / [엑셀 다운로드] |
| 검색 필터 | 제품구분 select 단일 |
| 그리드 컬럼 | 체크박스 / 제품구분 / 제목 / WBS(폴더) / 등록자 / 등록일 |
| 운영 데이터 | 1건: Machine / test 생산 / [폴더] / 경영지원팀관리자 / 2026-04-08 |
2.2 통합 팝업 (등록/수정)
- URL:
/project/WBSExcelImportPopUp.do?templateObjId=1120026346(운영 확인) - 제목: 신규 시 "WBS 템플릿 등록" / 수정 시 "WBS 템플릿 수정"
- 헤더: 제품구분(수정 시 disabled) + 제목(수정 시 disabled)
- 버튼: Template Download / 추가 / 하위추가 / 삭제 / 저장 / 닫기
- 드롭존: "Drag & Drop 엑셀 템플릿"
- 트리 그리드: 선택 / 수준(1/2/3 세 칸) / Unit Name·공정 — TOTAL 행 + 자식 5행 (운영 데이터)
2.3 엑셀 템플릿 (WBS_EXCEL_IMPORT_TEMPLATE.xlsx)
| A (수준1) | B (수준2) | C (수준3) | D (unit name /공정) |
|---|---|---|---|
| 1 | TASK1 | ||
| 1.1 | TASK2 | ||
| 1.1.1 | TASK3 | ||
| ... |
- 1행 = "입력" 라벨(노란색), 2행 = "수준/unit name /공정" 헤더, 3행부터 데이터.
- 수준 위치 = depth(1/2/3), 셀 값 = UNIT_NO ("1", "1.1", "1.1.1" 형식 자유 입력).
- TASK_NAME은 D열.
3. 데이터 모델 (운영 1:1)
3.1 pms_wbs_template (헤더, 1건 운영)
| 컬럼 | 타입 | 용도 |
|---|---|---|
objid |
varchar PK | 헤더 키 (Java 측 채번) |
product_objid |
varchar | 제품구분 코드 (CODE_NAME 함수로 라벨화) |
title |
varchar | 템플릿 제목 |
writer |
varchar | 등록자 user_id |
reg_date |
timestamp | 등록일 |
customer_product |
varchar | (미사용 — 운영 비활성 컬럼 CUSTOMER_PRODUCT로 흔적만) |
3.2 pms_wbs_task_standard (트리, 5건 운영, 활성 갈래)
| 컬럼 | 타입 | 용도 |
|---|---|---|
objid |
varchar PK | task 키 |
parent_objid |
varchar | pms_wbs_template.objid 참조 (헤더 FK) |
task_name |
varchar | task 이름 (TOTAL / 사용자 입력 / 엑셀 임포트) |
task_seq |
varchar | 폼 제출 순서 (INTEGER 캐스팅 정렬용) |
task_level |
varchar | depth (0=TOTAL, 1/2/3=수준) |
unit_no |
varchar | "1" / "1.1" / "1.1.1" 표기 — depth와 일치 |
upper_task_objid |
varchar | 트리 부모 objid (TOTAL의 objid가 depth=1의 부모, 이전 depth-1 행이 depth>1의 부모) |
user_id / writer / reg_date |
varchar/varchar/timestamp | 메타 |
→ vexplor_rps DDL 위치: docs/migration/project/ddl-extracted/200_pms_wbs.sql (8개 테이블 중 2개가 본 메뉴 대상).
3.3 폐기 / 미사용 (참고)
| 테이블 | 운영 카운트 | 폐기 이유 |
|---|---|---|
pms_wbs_task_info |
518 (2021 멈춤) | 매퍼 사용 없음 — 레거시 |
pms_wbs_task_standard2 |
74 | 매퍼 사용 없음 |
pms_wbs_task_confirm |
0 | 매퍼 사용 없음 |
4. wace 1:1 매핑 카탈로그
4.1 ProjectController.java endpoint (P2 범위 8개)
| URL | 라인 | 호출 service | 매퍼 |
|---|---|---|---|
/project/wbsTemplateMngList.do |
2242 | (forward only) + bizMakeOptionList('0000001') |
(코드맵만) |
/project/wbsTemplateMngGridList.do |
2270 | commonService.selectListPagingNew |
project.wbsTemplateMngGridList |
/project/WBSExcelImportPopUp.do |
2282 | getWBSTemplateMasterInfo + getWBSTemplateTaskList (수정 시) |
getWBSTemplateMasterInfo + getWBSTemplateTaskList |
/project/getWBSTemplateTaskList.do |
2419 | getWBSTemplateTaskList (AJAX, 팝업 로드 시) |
getWBSTemplateTaskList |
/project/parsingExcelFile.do |
2319 | parsingExcelFile |
(Apache POI 직접) |
/project/excelImportFileProc.do |
2336 | commonService.insertUploadFileInfo |
(파일 메타만) |
/project/checkWBSTemplateProduct.do |
2371 | getWBSTemplateProductList |
getWBSTemplateProductList |
/project/saveExcelUploadWBS.do |
2379 | mergeExcelUploadWBS (통합 저장) |
deleteWBSTemplateTaskByMaster + saveWBSTaskTemp + saveWBSTemplateTaskInfo |
/project/deleteWBSTemplateMaster.do |
2474 | deleteWBSTemplateMaster |
deleteWBSTemplateMaster + deleteWBSTemplateMasterTask |
→ 9개 endpoint, 그 중 핵심 워크플로는 4개 (grid / popup-forward / parse / merge-save).
4.2 project.xml 매퍼 SQL (라인번호 + 본문 핵심)
wbsTemplateMngGridList (5552) — 메인 그리드
SELECT
OBJID, PRODUCT_OBJID,
CODE_NAME(PRODUCT_OBJID) AS PRODUCT_NAME,
TITLE,
WRITER,
(SELECT DEPT_NAME || USER_NAME FROM USER_INFO WHERE USER_ID = WRITER) AS WRITER_TITLE,
REG_DATE,
TO_CHAR(REG_DATE, 'YYYY-MM-DD') AS REG_DATE_TITLE,
(SELECT COUNT(1) FROM PMS_WBS_TASK_STANDARD PWTS WHERE PWTS.PARENT_OBJID = OBJID) AS WBS_TASK_CNT,
CUSTOMER_PRODUCT
FROM PMS_WBS_TEMPLATE
WHERE 1=1
<if test="product != null and product !=''">
AND PRODUCT_OBJID = #{product}
</if>
→ CODE_NAME() 함수 호출 (RPS DB 보유, P1 진행관리에서도 사용).
getWBSTemplateMasterInfo (5647) — 팝업 헤더
SELECT OBJID, PRODUCT_OBJID, CODE_NAME(PRODUCT_OBJID) AS PRODUCT_OBJID_NAME,
TITLE, WRITER, REG_DATE, CUSTOMER_PRODUCT
FROM PMS_WBS_TEMPLATE AS T
WHERE OBJID = #{OBJID}
getWBSTemplateTaskList (5661) — 팝업 트리
SELECT T.OBJID, T.PARENT_OBJID, T.TASK_NAME, T.TASK_SEQ, T.TASK_LEVEL,
T.USER_ID,
(SELECT USER_NAME FROM USER_INFO WHERE USER_ID = T.USER_ID) AS USER_ID_TITLE,
T.WRITER, T.REG_DATE, T.UNIT_NO, T.UPPER_TASK_OBJID
FROM PMS_WBS_TASK_STANDARD AS T
WHERE T.PARENT_OBJID = #{OBJID}
ORDER BY CAST(T.TASK_SEQ AS INTEGER)
saveWBSTemplateTaskInfo (5609) — 트리 upsert
INSERT INTO PMS_WBS_TASK_STANDARD
(OBJID, PARENT_OBJID, TASK_NAME, TASK_SEQ, TASK_LEVEL, USER_ID,
WRITER, REG_DATE, UNIT_NO, UPPER_TASK_OBJID)
VALUES (#{objid}, #{parent_objid}, #{task_name}, #{task_seq}, #{task_level},
#{user_id}, #{writer}, now(), #{unit_no}, #{upper_task_objid})
ON CONFLICT (OBJID) DO UPDATE
SET TASK_NAME = #{task_name}, TASK_SEQ = #{task_seq},
TASK_LEVEL = #{task_level}, USER_ID = #{user_id},
UNIT_NO = #{unit_no}, UPPER_TASK_OBJID = #{upper_task_objid}
→ upsert (INSERT … ON CONFLICT DO UPDATE) — neon/node-postgres에서 동일 패턴 사용.
saveWBSTaskTemp (5587) — 헤더 INSERT
INSERT INTO PMS_WBS_TEMPLATE
(OBJID, PRODUCT_OBJID, TITLE, CUSTOMER_PRODUCT, WRITER, REG_DATE)
VALUES (#{objid}, #{product}, #{title}, #{customer_product}, #{writer}, now())
deleteWBSTemplateTaskByMaster (5643) — 수정 시 트리 cascade clear
DELETE FROM PMS_WBS_TASK_STANDARD WHERE PARENT_OBJID = #{parent_objid}
deleteWBSTemplateMaster (5726) / deleteWBSTemplateMasterTask (5734)
DELETE FROM PMS_WBS_TEMPLATE WHERE OBJID IN (...)
DELETE FROM PMS_WBS_TASK_STANDARD WHERE PARENT_OBJID IN (...)
→ 헤더 다건 삭제 + cascade. 트랜잭션 1개.
getWBSTemplateProductList (5576) — 중복 체크
SELECT *
FROM PMS_WBS_TEMPLATE AS T
LEFT OUTER JOIN PMS_WBS_TASK_STANDARD AS T1 ON T.OBJID = T1.PARENT_OBJID
WHERE T.PRODUCT_OBJID = #{PRODUCT}
AND T.TITLE = #{TITLE}
→ 신규 등록 시 동일 (제품구분 + 제목) 조합 있는지 사전 체크.
4.3 ProjectService 핵심 메소드
mergeExcelUploadWBS (line 2902, 통합 저장)
PersonBean.userId → writer
request.parameter("product"|"title"|"templateObjId"|"customer_product")
분기:
if templateObjId != "":
wbsMasterObjId = templateObjId # 헤더 재사용 (UPDATE 안 함!)
sqlSession.delete("deleteWBSTemplateTaskByMaster", {parent_objid: wbsMasterObjId})
else:
wbsMasterObjId = CommonUtils.createObjId()
sqlSession.insert("saveWBSTaskTemp", {objid, product, title, customer_product, writer})
루프 (WBS_TASK_OBJID 배열):
for i, wbsObjId in enumerate(WBS_TASK_OBJID):
{TASK_NAME, UNIT_NO, UPPER_TASK_OBJID, TASK_LEVEL} = request 각 hidden
sqlSession.insert("saveWBSTemplateTaskInfo", {
objid, task_name, task_seq=i+1, task_level, unit_no,
upper_task_objid, parent_objid=wbsMasterObjId, writer
})
sqlSession.commit()
핵심 패턴:
- 수정 모드는 헤더 UPDATE 안 함 (product/title 변경 불가). 트리만 일괄 DELETE → INSERT.
- 신규 모드는 헤더 INSERT + 트리 INSERT.
- 트리 순서(task_seq)는 폼 제출 순서.
parsingExcelFile (line 2779)
fileList = commonService.getFileList({targetObjId, docType: "WBS_EXCEL_IMPORT"})
첫 파일을 XSSFWorkbook으로 읽음.
루프 rowIndex = 2 ~ end (3행부터):
열 0/1/2 → levelValues[0..2] # 수준1/2/3 셀
열 3 → taskName
unitNo = levelValues[2] or levelValues[1] or levelValues[0] # depth 3→2→1 우선
if unitNo and taskName:
result.add({WBS_OBJID = createObjId(), UNIT_NO = unitNo, TASK_NAME = taskName})
→ depth는 클라이언트가 unit_no의 . 개수로 계산 (.match(/\./g) || []).length + 1).
→ 파싱 결과는 클라이언트에 List 리턴, DB 저장은 사용자가 "저장" 버튼 누를 때 mergeExcelUploadWBS에서.
deleteWBSTemplateMaster (line 3284)
SqlSession (transactional)
sqlSession.delete("deleteWBSTemplateMaster", {checkArr})
sqlSession.delete("deleteWBSTemplateMasterTask", {checkArr})
commit
5. UI 동작 명세 (WBSExcelImportPopUp.jsp 1:1)
5.1 모드 분기
isEditMode = (templateObjId !== "")
if isEditMode:
loadExistingTasks() // AJAX → /project/getWBSTemplateTaskList.do
title.value = masterInfo.TITLE
product.value = masterInfo.PRODUCT_OBJID
else:
addTotalRow() // 빈 트리에 TOTAL 행 1개만
5.2 트리 행 구조
<tr id="row_{objId}" data-depth="?">
<td><input type="checkbox" name="rowCheck" value="{objId}"></td>
<input type="hidden" name="WBS_TASK_OBJID" value="{objId}">
<input type="hidden" name="UNIT_NO_{objId}" value="">
<input type="hidden" name="UPPER_TASK_OBJID_{objId}" value="">
<input type="hidden" name="TASK_LEVEL_{objId}" value="">
<td><input class="lvl_input" data-level="1"></td> <!-- 수준1 -->
<td><input class="lvl_input" data-level="2"></td> <!-- 수준2 -->
<td><input class="lvl_input" data-level="3"></td> <!-- 수준3 -->
<td><input name="TASK_NAME_{objId}" value="..."></td>
</tr>
- TOTAL 행은 hidden TASK_LEVEL=0 / UNIT_NO=0 / TASK_NAME=TOTAL / 체크박스 없음 / "TOTAL" 텍스트 표시.
- 수준 1/2/3은 셋 중 하나에만 값 입력 가능 (
bindLevelInput): 한 칸 입력 시 다른 두 칸 비우고 UNIT_NO + TASK_LEVEL 동기화.
5.3 버튼 핸들러
| 버튼 | 함수 | 동작 |
|---|---|---|
| Template Download | templateDownload.click |
location.href="/template/WBS_EXCEL_IMPORT_TEMPLATE.xlsx" (정적 파일) |
| 추가 | addRow() |
선택된 행 다음에 같은 depth 행 추가. 선택 없으면 마지막에 append. |
| 하위추가 | addChildRow() |
선택된 행의 하위(depth+1) 행을 자식 마지막 다음에 추가. depth>=3 거부. |
| 삭제 | deleteRow() |
선택된 행 + 하위 후손 행 일괄 삭제. cascade 확인 alert. |
| 저장 | saveWBS() |
검증 → calculateParentRelations() → POST /project/saveExcelUploadWBS.do |
| 닫기 | self.close() |
팝업 닫기 |
5.4 저장 검증 로직 (saveWBS())
1. title 빈값 거부
2. WBS_TASK_OBJID 0개 거부 (등록할 항목 없음)
3. 각 행에서 UNIT_NO + TASK_NAME 빈값 거부 (TOTAL 행 제외)
4. 신규 모드일 때 fn_checkWBSTemplateRevision() — (PRODUCT, TITLE) 중복 거부
5. calculateParentRelations() — 모든 행 순회하며 UPPER_TASK_OBJID 채우기:
- depth=1 → totalObjId (TOTAL 행 objid)
- depth>1 → 이전 prevAll 중 depth-1인 가장 가까운 행 objid
6. POST form serialize → opener.fn_search() + self.close()
5.5 엑셀 임포트 흐름
사용자 파일 드롭 → fileUploadPreProc() → preFileDelete() (기존 삭제 alert)
↓
fnc_setFileDropZone POST /project/excelImportFileProc.do (Multipart)
↓ 업로드 완료 콜백 → setExcelFileArea() → setUploadTemplateFile()
↓ getFileList.do AJAX → 첨부 표시 + parsingExcelFile()
↓ parsingExcelFile() POST /project/parsingExcelFile.do
↓ 응답 List(WBS_OBJID, UNIT_NO, TASK_NAME)
↓ 각 row를 #wbsTaskList 에 append (depth는 unit_no의 '.' 개수로 계산)
5.6 그리드 행 클릭 (메인 화면)
- WBS 폴더 아이콘 클릭 →
fn_openWBSTaskListPopUp(objid)→/project/WBSExcelImportPopUp.do?templateObjId={objid}팝업 (1340x700) - 등록 버튼 →
/project/WBSExcelImportPopUp.do?product={product}팝업- 제품구분 미선택 시 alert "제품은 필수값입니다 제품을 선택해 주세요"
6. vexplor_rps 이식 매핑 (구현 계획)
6.1 backend-node
| wace | vexplor_rps | 비고 |
|---|---|---|
ProjectController WBS template 9개 |
backend-node/src/controllers/wbsTemplateController.ts |
REST 통합 |
ProjectService.mergeExcelUploadWBS |
backend-node/src/services/wbsTemplateService.ts saveTemplate(payload) |
upsert 패턴 그대로 |
ProjectService.parsingExcelFile |
backend-node/src/services/wbsTemplateService.ts parseExcelFile(buffer) |
Apache POI → xlsx npm 패키지 |
CODE_NAME(PRODUCT_OBJID) |
DB 함수 직접 호출 (P1 진행관리와 동일) | RPS DB 보유 |
commonService.getFileList |
영업관리 첨부 패턴 재사용 또는 임포트 시 메모리 처리 |
REST endpoint 매핑
GET /api/project/wbs-template — 메인 그리드 (product 필터)
GET /api/project/wbs-template/:id — 헤더 + 트리 (팝업 진입)
POST /api/project/wbs-template — 신규 저장 (헤더 + 트리)
PUT /api/project/wbs-template/:id — 수정 저장 (트리만 일괄 DELETE→INSERT)
DELETE /api/project/wbs-template — 다건 삭제 (헤더 + cascade)
POST /api/project/wbs-template/parse-excel — 엑셀 파일 multipart → 파싱 결과 JSON
GET /api/project/wbs-template/check-duplicate?product=&title= — 중복 체크
GET /api/project/wbs-template/excel-template — 엑셀 템플릿 다운로드 (`public/templates/WBS_EXCEL_IMPORT_TEMPLATE.xlsx` 정적)
objid 채번
wace는 CommonUtils.createObjId() (시퀀스 함수 기반 string). vexplor_rps는 nanoid() 또는 crypto.randomUUID() → 영업관리 패턴 확인 후 통일.
6.2 frontend
| wace | vexplor_rps | 비고 |
|---|---|---|
wbsTemplateMngList.jsp |
frontend/app/(main)/COMPANY_16/project/wbs-template/page.tsx |
메인 페이지 |
통합 팝업 WBSExcelImportPopUp.jsp |
frontend/components/project/WbsTemplateDialog.tsx |
shadcn Dialog + 트리 테이블 + 파일 드롭존 |
| 메인 그리드 | DataGrid 5컬럼 (영업관리 패턴 재사용) |
frozen 제품구분 |
| API 클라이언트 | frontend/lib/api/wbsTemplate.ts |
영업관리 패턴 |
| 메뉴 | AdminPageRenderer.tsx dynamic 등록 |
라우트 /COMPANY_16/project/wbs-template |
트리 UI 결정 사항
운영판은 jQuery 기반 단순 테이블 + hidden input 직렬화. vexplor_rps는 React 상태로 트리 행 관리:
- 행 배열 + depth 필드 (1~3) +
objid클라이언트 측 생성 (nanoid()) - 추가/하위추가/삭제는 배열 조작
- 수준 1/2/3 컬럼 단일 입력 보장
- 저장 시 task_seq = index+1, upper_task_objid 자동 계산 (depth=1→TOTAL, depth>1→이전 depth-1 항목 objid)
엑셀 라이브러리
xlsx(SheetJS) — backend-node에서 파싱- 정적 엑셀 템플릿 파일은
frontend/public/templates/WBS_EXCEL_IMPORT_TEMPLATE.xlsx에 wace 원본 그대로 배치
7. 함정 & 결정 메모
7.1 수정 모드 헤더 변경 불가
운영판 mergeExcelUploadWBS는 templateObjId 있으면 헤더 UPDATE를 호출하지 않음. 화면에서 product1 disabled / title은 표시만. vexplor_rps도 동일하게 disabled 처리 — 헤더 수정 기능은 추가하지 않음.
7.2 트리 일괄 DELETE → INSERT
수정 시 운영은 deleteWBSTemplateTaskByMaster로 트리 전체 삭제 후 폼 데이터로 재삽입. vexplor_rps도 같은 패턴 (트랜잭션 1개). 부분 update/delete 분기 없음 — 간단하고 안전.
7.3 task_seq는 INTEGER 캐스팅 정렬
SQL ORDER BY CAST(T.TASK_SEQ AS INTEGER). wace는 폼 제출 순서로 1, 2, 3, ... 매김. vexplor_rps도 동일.
7.4 upper_task_objid 자동 계산
클라이언트 측 calculateParentRelations() — 백엔드로 보내기 전 결정. depth=1 행의 부모는 TOTAL 행 objid. 운영판 그대로.
7.5 CUSTOMER_PRODUCT 컬럼은 비활성
운영판 메인 그리드의 "고객사_장비목적" 컬럼은 JSP 주석 처리됨. saveWBSTemplateMasterInfo(UPDATE)는 CUSTOMER_PRODUCT만 수정하는데 호출하는 곳이 비활성 마스터 폼뿐. vexplor_rps 초기 이식에서는 빼고, customer_product는 DB에 보존만 함.
7.6 엑셀 1행/2행 무시
파싱 루프 for(rowIndex = 2 ; ...) — 1행(입력 라벨) + 2행(수준/unit name 헤더) 무시, 3행부터 데이터. vexplor_rps도 동일.
7.7 신규 등록 시 product 필수
wbsTemplateMngList.jsp 라인 53-57: 등록 버튼 클릭 시 product 비었으면 alert. 운영 UX 그대로.
7.8 폐기 갈래 안 건드림
pms_wbs_task_info, _standard2, _confirm은 DDL은 보존했지만 매퍼/서비스/UI에서 절대 사용하지 않음. 향후 진행관리 P2(WBS 진행 트리) 진입 시 pms_wbs_task 본체만 추가 매핑.
7.9 진행관리 P1과의 연결은 P2 범위 외
프로젝트(주문) 생성 시 템플릿 자동 복사 흐름은 wace도 미완성 — 사용자 명시. vexplor_rps도 추후 별도 단계로.
8. 검증 베이스라인 (운영DB)
운영DB에 1건만 있어 화면 검증 단순:
-- 메인 그리드 조회 (운영 wbsTemplateMngGridList 1:1)
SELECT OBJID, CODE_NAME(PRODUCT_OBJID) AS PRODUCT_NAME, TITLE, WRITER,
TO_CHAR(REG_DATE,'YYYY-MM-DD') AS REG_DATE_TITLE,
(SELECT COUNT(1) FROM pms_wbs_task_standard WHERE parent_objid = t.objid) AS WBS_TASK_CNT
FROM pms_wbs_template t;
-- 운영: 1건 (Machine / test 생산 / 경영지원팀관리자 / 2026-04-08 / WBS_TASK_CNT=5)
-- 트리 조회 (운영 getWBSTemplateTaskList 1:1)
SELECT objid, task_name, task_seq, task_level, unit_no, upper_task_objid
FROM pms_wbs_task_standard
WHERE parent_objid = '1120026346'
ORDER BY CAST(task_seq AS INTEGER);
-- 운영: 5건 (TOTAL + ㅁㅁ4건)
vexplor_rps 측은 운영 데이터 시드 없이 빈 상태에서 시작 — 화면 검증은 사용자가 직접 등록·저장·수정·삭제로.
9. 산출물 체크리스트
- DDL:
docs/migration/project/ddl-extracted/200_pms_wbs.sql(8개 테이블) - DDL README:
docs/migration/project/ddl-extracted/README.md - GAP 문서: 본 파일
- backend service:
backend-node/src/services/wbsTemplateService.ts - backend controller/route:
backend-node/src/controllers/wbsTemplateController.ts+routes/wbsTemplateRoutes.ts - frontend page:
frontend/app/(main)/COMPANY_16/project/wbs-template/page.tsx - frontend dialog:
frontend/components/project/WbsTemplateDialog.tsx - frontend api:
frontend/lib/api/wbsTemplate.ts - AdminPageRenderer dynamic 등록
- 엑셀 템플릿 정적 파일:
frontend/public/templates/WBS_EXCEL_IMPORT_TEMPLATE.xlsx - 검증: 운영판 1:1 등록/수정/삭제/엑셀 임포트 동작