개발관리>설계변경 리스트 메뉴 신설 (PR-C) — wace partMng 1:1 이식
backend (M5, read-only): - devEoHistoryService: list + getByObjid - partMngHistList SQL 1:1 (NVL→COALESCE, PART_MNG.OBJID bigint cast, CODE_NAME→LEFT JOIN comm_code) - 동적 필터 10종 (Year/contract_objid/unit_code/part_no/part_name/change_option/eo_start~end/change_type/part_type/writer_id) - 신규등록 제외 가드: NOT (HIS_STATUS='DEPLOY' AND CHANGE_TYPE IS NULL AND REVISION='RE') + BOM_STATUS='deploy' - 품번변경(CHANGE_OPTION=0001790) 'A->B' 머지 CASE (part_no_disp/part_name_disp/revision_disp) frontend (M5): - change-list/page.tsx: 16셀 그리드 + 검색 8필드 + 페이징 - PartHisDetailDialog: 모든 필드 disabled, 4 섹션 (EO/프로젝트/PART/수량) - AdminPageRenderer dynamic 임포트 + 기존 menu_info URL 그대로 사용 개발관리 5개 메뉴 (M1~M5) baseline 완료. 본 PR 제외 (별 PR): writer SmartSelect, change_type/change_option comm_code 그룹 SmartSelect (그룹 ID 확정 후) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -179,6 +179,7 @@ import projectMgmtRoutes from "./routes/projectMgmtRoutes"; // 프로젝트관
|
||||
import wbsTemplateRoutes from "./routes/wbsTemplateRoutes"; // 프로젝트관리>제품구분_WBS관리 (wace_plm 도메인 이식)
|
||||
import devPartRoutes from "./routes/devPartRoutes"; // 개발관리>PART 등록/조회 (wace_plm 도메인 이식)
|
||||
import devBomRoutes from "./routes/devBomRoutes"; // 개발관리>E-BOM 등록/조회 (wace_plm 도메인 이식)
|
||||
import devEoHistoryRoutes from "./routes/devEoHistoryRoutes"; // 개발관리>설계변경 리스트 (wace_plm 도메인 이식)
|
||||
import erpSyncRoutes from "./routes/erpSyncRoutes"; // ERP 마스터 동기화 (사원/부서/창고/거래처/계정과목)
|
||||
import ecrMngRoutes from "./routes/ecrMngRoutes"; // ECR(Engineering Change Request) 관리
|
||||
import customerCsRoutes from "./routes/customerCsRoutes"; // 고객 CS 관리
|
||||
@@ -427,6 +428,7 @@ app.use("/api/project/progress", projectMgmtRoutes); // 프로젝트관리>진
|
||||
app.use("/api/project/wbs-template", wbsTemplateRoutes); // 프로젝트관리>제품구분_WBS관리 (wace_plm 도메인)
|
||||
app.use("/api/development", devPartRoutes); // 개발관리>PART 등록/조회 (wace_plm 도메인)
|
||||
app.use("/api/development", devBomRoutes); // 개발관리>E-BOM 등록/조회 (wace_plm 도메인)
|
||||
app.use("/api/development", devEoHistoryRoutes); // 개발관리>설계변경 리스트 (wace_plm 도메인)
|
||||
app.use("/api", screenEmbeddingRoutes); // 화면 임베딩 및 데이터 전달
|
||||
app.use("/api/ai/v1", aiAssistantProxy); // AI 어시스턴트 (동일 서비스 내 프록시 → AI 서비스 포트)
|
||||
app.use("/api/vehicle", vehicleTripRoutes); // 차량 운행 이력 관리
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// ============================================================
|
||||
// 개발관리 설계변경 리스트 (M5) 컨트롤러 — read-only.
|
||||
// GET /api/development/eo-history/list
|
||||
// GET /api/development/eo-history/:objid
|
||||
// ============================================================
|
||||
|
||||
import { Response } from "express";
|
||||
import { AuthenticatedRequest } from "../types/auth";
|
||||
import * as svc from "../services/devEoHistoryService";
|
||||
import { logger } from "../utils/logger";
|
||||
|
||||
function parseListFilter(q: Record<string, any>): svc.EoHistoryListFilter {
|
||||
const filter: svc.EoHistoryListFilter = { ...q };
|
||||
if (q.page) filter.page = Number(q.page);
|
||||
if (q.page_size) filter.page_size = Number(q.page_size);
|
||||
return filter;
|
||||
}
|
||||
|
||||
export async function getList(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const data = await svc.list(parseListFilter(req.query as Record<string, any>));
|
||||
return res.json({ success: true, data });
|
||||
} catch (e: any) {
|
||||
logger.error("설계변경 리스트 실패", { error: e.message });
|
||||
return res.status(500).json({ success: false, message: e.message });
|
||||
}
|
||||
}
|
||||
|
||||
export async function getByObjid(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const { objid } = req.params;
|
||||
const row = await svc.getByObjid(objid);
|
||||
if (!row) return res.status(404).json({ success: false, message: "not_found" });
|
||||
return res.json({ success: true, data: row });
|
||||
} catch (e: any) {
|
||||
logger.error("설계변경 상세 실패", { error: e.message });
|
||||
return res.status(500).json({ success: false, message: e.message });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// ============================================================
|
||||
// 개발관리 설계변경 리스트 (M5) — read-only 라우트.
|
||||
// app.ts: app.use("/api/development", devEoHistoryRoutes) — prefix 공유.
|
||||
// ============================================================
|
||||
|
||||
import { Router } from "express";
|
||||
import { authenticateToken } from "../middleware/authMiddleware";
|
||||
import * as ctrl from "../controllers/devEoHistoryController";
|
||||
|
||||
const router = Router();
|
||||
router.use(authenticateToken);
|
||||
|
||||
router.get("/eo-history/list", ctrl.getList);
|
||||
router.get("/eo-history/:objid", ctrl.getByObjid);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,155 @@
|
||||
// ============================================================
|
||||
// 개발관리 설계변경 리스트 (M5) — wace partMng.xml#partMngHistList 1:1 read-only.
|
||||
//
|
||||
// 매퍼 매핑:
|
||||
// partMngHistList (line 7,787) → list()
|
||||
// + getByObjid() — 행 클릭 상세 다이얼로그용 (raw PART_MNG_HISTORY 행)
|
||||
//
|
||||
// vexplor_rps 적응:
|
||||
// · NVL() → COALESCE()
|
||||
// · PART_MNG.OBJID(bigint) = PM.PARENT_PART_NO(varchar) → ::varchar cast
|
||||
// · CODE_NAME() → LEFT JOIN comm_code 별칭
|
||||
// ============================================================
|
||||
|
||||
import { getPool } from "../database/db";
|
||||
|
||||
export interface EoHistoryListFilter {
|
||||
Year?: string;
|
||||
contract_objid?: string;
|
||||
unit_code?: string;
|
||||
part_no?: string;
|
||||
part_name?: string;
|
||||
change_option?: string;
|
||||
eo_start_date?: string;
|
||||
eo_end_date?: string;
|
||||
change_type?: string;
|
||||
part_type?: string;
|
||||
writer_id?: string;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
}
|
||||
|
||||
function buildWhere(filter: EoHistoryListFilter, startIdx: number) {
|
||||
const params: any[] = [];
|
||||
const conds: string[] = [];
|
||||
let idx = startIdx;
|
||||
|
||||
if (filter.Year) { conds.push(`TO_CHAR(CM.REGDATE,'YYYY') = $${idx++}`); params.push(filter.Year); }
|
||||
if (filter.contract_objid) { conds.push(`CM.OBJID = $${idx++}`); params.push(filter.contract_objid); }
|
||||
if (filter.unit_code) { conds.push(`B.UNIT_CODE = $${idx++}`); params.push(filter.unit_code); }
|
||||
if (filter.part_no) { conds.push(`UPPER(PM.PART_NO) LIKE UPPER($${idx++})`); params.push(`%${filter.part_no}%`); }
|
||||
if (filter.part_name) { conds.push(`UPPER(PM.PART_NAME) LIKE UPPER($${idx++})`); params.push(`%${filter.part_name}%`); }
|
||||
if (filter.change_option) { conds.push(`PM.CHANGE_OPTION = $${idx++}`); params.push(filter.change_option); }
|
||||
if (filter.eo_start_date) { conds.push(`TO_DATE(PM.EO_DATE,'YYYY-MM-DD') >= TO_DATE($${idx++},'YYYY-MM-DD')`); params.push(filter.eo_start_date); }
|
||||
if (filter.eo_end_date) { conds.push(`TO_DATE(PM.EO_DATE,'YYYY-MM-DD') <= TO_DATE($${idx++},'YYYY-MM-DD')`); params.push(filter.eo_end_date); }
|
||||
if (filter.change_type) { conds.push(`PM.CHANGE_TYPE = $${idx++}`); params.push(filter.change_type); }
|
||||
if (filter.part_type) { conds.push(`PM.PART_TYPE = $${idx++}`); params.push(filter.part_type); }
|
||||
if (filter.writer_id) { conds.push(`PM.WRITER = $${idx++}`); params.push(filter.writer_id); }
|
||||
|
||||
return { sql: conds.length ? conds.join(" AND ") : "1=1", params };
|
||||
}
|
||||
|
||||
function paginate(filter: { page?: number; page_size?: number }) {
|
||||
const page = Math.max(1, Number(filter.page) || 1);
|
||||
const pageSize = Math.min(500, Math.max(1, Number(filter.page_size) || 50));
|
||||
return { limit: pageSize, offset: (page - 1) * pageSize, page, pageSize };
|
||||
}
|
||||
|
||||
// ─── 그리드 ────────────────────────────────────────────────
|
||||
|
||||
export async function list(filter: EoHistoryListFilter) {
|
||||
const { limit, offset, page, pageSize } = paginate(filter);
|
||||
const where = buildWhere(filter, 1);
|
||||
const pool = getPool();
|
||||
|
||||
const baseSql = `
|
||||
SELECT
|
||||
PM.OBJID,
|
||||
PM.EO_NO,
|
||||
TO_CHAR(CM.REGDATE, 'YYYY') AS YEAR,
|
||||
COALESCE(CM.CUSTOMER_PROJECT_NAME, CM2.CUSTOMER_PROJECT_NAME) AS PROJECT_NAME,
|
||||
COALESCE(CM2.PROJECT_NO, CM.PROJECT_NO) AS PROJECT_NO,
|
||||
(SELECT SP.PART_NO || ' ' || SP.PART_NAME
|
||||
FROM PART_MNG SP
|
||||
WHERE SP.OBJID::varchar = PM.PARENT_PART_NO) AS PARENT_PART_INFO,
|
||||
CASE WHEN PM.CHANGE_OPTION = '0001790'
|
||||
THEN COALESCE(PM.PART_NO,'') || '->' || COALESCE(PM.CHG_PART_NO,'')
|
||||
ELSE PM.PART_NO END AS PART_NO_DISP,
|
||||
CASE WHEN PM.CHANGE_OPTION = '0001790'
|
||||
THEN COALESCE(PM.PART_NAME,'') || '->' ||
|
||||
COALESCE((SELECT P.PART_NAME FROM PART_MNG P WHERE P.OBJID::varchar = PM.CHG_PART_OBJID), '')
|
||||
ELSE PM.PART_NAME END AS PART_NAME_DISP,
|
||||
PM.PART_NO,
|
||||
PM.PART_NAME,
|
||||
PM.BOM_QTY_STATUS,
|
||||
CASE WHEN PM.BOM_QTY_STATUS = 'adding' THEN PM.QTY_TEMP ELSE PM.QTY END AS QTY,
|
||||
CASE WHEN PM.BOM_QTY_STATUS = 'adding' THEN ''
|
||||
WHEN PM.BOM_QTY_STATUS = 'beforeEdit' AND PM.QTY = PM.QTY_TEMP THEN ''
|
||||
ELSE PM.QTY_TEMP END AS QTY_TEMP,
|
||||
PM.CHANGE_TYPE,
|
||||
CC_CHGTYPE.code_name AS CHANGE_TYPE_NAME,
|
||||
PM.CHANGE_OPTION,
|
||||
CC_CHGOPT.code_name AS CHANGE_OPTION_NAME,
|
||||
CASE WHEN PM.CHANGE_OPTION = '0001790'
|
||||
THEN COALESCE(PM.REVISION,'') || '->' || COALESCE(PM.CHG_PART_REV,'')
|
||||
ELSE PM.REVISION END AS REVISION_DISP,
|
||||
PM.REVISION,
|
||||
PM.EO_DATE,
|
||||
PM.PART_TYPE,
|
||||
CC_PARTTYPE.code_name AS PART_TYPE_NAME,
|
||||
PM.WRITER,
|
||||
UI.user_name AS WRITER_NAME,
|
||||
COALESCE(WTS.UNIT_NO || '-' || WTS.TASK_NAME, '') AS UNIT_NAME,
|
||||
TO_CHAR(PM.HIS_REG_DATE, 'YYYY-MM-DD') AS HIS_REG_DATE_TITLE,
|
||||
PM.BOM_DEPLOY_DATE,
|
||||
TO_CHAR(PM.BOM_DEPLOY_DATE, 'YYYY-MM-DD') AS BOM_DEPLOY_DATE_TITLE
|
||||
FROM PART_MNG_HISTORY PM
|
||||
LEFT JOIN PROJECT_MGMT CM ON CM.OBJID = PM.CONTRACT_OBJID
|
||||
LEFT JOIN PART_BOM_REPORT B ON B.OBJID = PM.BOM_REPORT_OBJID
|
||||
LEFT JOIN PROJECT_MGMT CM2 ON CM2.OBJID = B.CONTRACT_OBJID
|
||||
LEFT JOIN PMS_WBS_TASK WTS ON WTS.OBJID = B.UNIT_CODE
|
||||
LEFT JOIN user_info UI ON UI.user_id = PM.WRITER
|
||||
LEFT JOIN COMM_CODE CC_CHGTYPE ON CC_CHGTYPE.code_id = PM.CHANGE_TYPE AND CC_CHGTYPE.status='active'
|
||||
LEFT JOIN COMM_CODE CC_CHGOPT ON CC_CHGOPT.code_id = PM.CHANGE_OPTION AND CC_CHGOPT.status='active'
|
||||
LEFT JOIN COMM_CODE CC_PARTTYPE ON CC_PARTTYPE.code_id = PM.PART_TYPE AND CC_PARTTYPE.status='active'
|
||||
WHERE NOT (PM.HIS_STATUS = 'DEPLOY' AND PM.CHANGE_TYPE IS NULL AND PM.REVISION = 'RE')
|
||||
AND PM.REVISION IS NOT NULL
|
||||
AND COALESCE(PM.BOM_STATUS, '') = 'deploy'
|
||||
AND ${where.sql}
|
||||
`;
|
||||
|
||||
const dataSql = `${baseSql}
|
||||
ORDER BY COALESCE(PM.HIS_REG_DATE, PM.REG_DATE) DESC, PM.PART_NO
|
||||
LIMIT $${where.params.length + 1} OFFSET $${where.params.length + 2}`;
|
||||
const countSql = `SELECT COUNT(*)::int AS total FROM (${baseSql}) X`;
|
||||
|
||||
const [dataRes, countRes] = await Promise.all([
|
||||
pool.query(dataSql, [...where.params, limit, offset]),
|
||||
pool.query(countSql, where.params),
|
||||
]);
|
||||
|
||||
return { rows: dataRes.rows, total: countRes.rows[0]?.total ?? 0, page, pageSize };
|
||||
}
|
||||
|
||||
// ─── 단건 ────────────────────────────────────────────────
|
||||
|
||||
export async function getByObjid(objid: string) {
|
||||
const sql = `
|
||||
SELECT PM.*,
|
||||
CC_CHGTYPE.code_name AS change_type_name,
|
||||
CC_CHGOPT.code_name AS change_option_name,
|
||||
CC_PARTTYPE.code_name AS part_type_name,
|
||||
UI.user_name AS writer_name,
|
||||
CM.customer_project_name,
|
||||
CM.project_no
|
||||
FROM PART_MNG_HISTORY PM
|
||||
LEFT JOIN PROJECT_MGMT CM ON CM.OBJID = PM.CONTRACT_OBJID
|
||||
LEFT JOIN user_info UI ON UI.user_id = PM.WRITER
|
||||
LEFT JOIN COMM_CODE CC_CHGTYPE ON CC_CHGTYPE.code_id = PM.CHANGE_TYPE AND CC_CHGTYPE.status='active'
|
||||
LEFT JOIN COMM_CODE CC_CHGOPT ON CC_CHGOPT.code_id = PM.CHANGE_OPTION AND CC_CHGOPT.status='active'
|
||||
LEFT JOIN COMM_CODE CC_PARTTYPE ON CC_PARTTYPE.code_id = PM.PART_TYPE AND CC_PARTTYPE.status='active'
|
||||
WHERE PM.OBJID = $1::numeric
|
||||
`;
|
||||
const r = await getPool().query(sql, [objid]);
|
||||
return r.rows[0] ?? null;
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
# PR-C : 설계변경 리스트 구현 명세 (M5)
|
||||
|
||||
> 작성: 2026-05-12 / 범위: 개발관리 M5 (설계변경 리스트) — read-only `part_mng_history` 조회.
|
||||
|
||||
---
|
||||
|
||||
## 1. 매퍼 쿼리 1:1 매핑
|
||||
|
||||
| Query id | Line | 본 PR 매핑 | 용도 |
|
||||
|---|---:|---|---|
|
||||
| `partMngHistList` | 7,787 | `GET /api/development/eo-history/list` | M5 그리드 (read-only) |
|
||||
|
||||
추가 엔드포인트:
|
||||
- `GET /api/development/eo-history/:objid` — 행 클릭 상세 다이얼로그용 (raw row 반환).
|
||||
|
||||
---
|
||||
|
||||
## 2. API 명세
|
||||
|
||||
### 2.1 그리드 — `GET /api/development/eo-history/list`
|
||||
|
||||
**Query** (wace 10 필터 1:1):
|
||||
```
|
||||
Year?: string // TO_CHAR(project_mgmt.regdate, 'YYYY')
|
||||
contract_objid?: string // project_mgmt.objid
|
||||
unit_code?: string // part_bom_report.unit_code
|
||||
part_no?: string // LIKE
|
||||
part_name?: string // LIKE
|
||||
change_option?: string // comm_code id (EO사유)
|
||||
eo_start_date?: string
|
||||
eo_end_date?: string
|
||||
change_type?: string // comm_code id (EO구분)
|
||||
part_type?: string // comm_code id
|
||||
writer_id?: string
|
||||
page?, page_size?
|
||||
```
|
||||
|
||||
**SQL** (wace `partMngHistList` 1:1, vexplor 적응):
|
||||
```sql
|
||||
SELECT
|
||||
PM.OBJID,
|
||||
PM.EO_NO,
|
||||
TO_CHAR(CM.REGDATE, 'YYYY') AS YEAR,
|
||||
COALESCE(CM.CUSTOMER_PROJECT_NAME, CM2.CUSTOMER_PROJECT_NAME) AS PROJECT_NAME,
|
||||
COALESCE(CM2.PROJECT_NO, CM.PROJECT_NO) AS PROJECT_NO,
|
||||
/* 모품번: PART_MNG SP에서 PARENT_PART_NO로 조회. PART_MNG.OBJID는 bigint → varchar cast */
|
||||
(SELECT SP.PART_NO || ' ' || SP.PART_NAME
|
||||
FROM PART_MNG SP
|
||||
WHERE SP.OBJID::varchar = PM.PARENT_PART_NO) AS PARENT_PART_INFO,
|
||||
/* 품번변경(0001790) 시 'A->B' 머지 */
|
||||
CASE WHEN PM.CHANGE_OPTION = '0001790' THEN COALESCE(PM.PART_NO,'') || '->' || COALESCE(PM.CHG_PART_NO,'')
|
||||
ELSE PM.PART_NO END AS PART_NO_DISP,
|
||||
CASE WHEN PM.CHANGE_OPTION = '0001790'
|
||||
THEN COALESCE(PM.PART_NAME,'') || '->' ||
|
||||
COALESCE((SELECT P.PART_NAME FROM PART_MNG P WHERE P.OBJID::varchar = PM.CHG_PART_OBJID), '')
|
||||
ELSE PM.PART_NAME END AS PART_NAME_DISP,
|
||||
PM.PART_NO, -- raw
|
||||
PM.PART_NAME, -- raw
|
||||
PM.BOM_QTY_STATUS,
|
||||
CASE WHEN PM.BOM_QTY_STATUS = 'adding' THEN PM.QTY_TEMP ELSE PM.QTY END AS QTY,
|
||||
CASE WHEN PM.BOM_QTY_STATUS = 'adding' THEN ''
|
||||
WHEN PM.BOM_QTY_STATUS = 'beforeEdit' AND PM.QTY = PM.QTY_TEMP THEN ''
|
||||
ELSE PM.QTY_TEMP END AS QTY_TEMP,
|
||||
PM.CHANGE_TYPE,
|
||||
CC_CHGTYPE.code_name AS CHANGE_TYPE_NAME,
|
||||
PM.CHANGE_OPTION,
|
||||
CC_CHGOPT.code_name AS CHANGE_OPTION_NAME,
|
||||
CASE WHEN PM.CHANGE_OPTION = '0001790'
|
||||
THEN COALESCE(PM.REVISION,'') || '->' || COALESCE(PM.CHG_PART_REV,'')
|
||||
ELSE PM.REVISION END AS REVISION_DISP,
|
||||
PM.REVISION, -- raw
|
||||
PM.EO_DATE,
|
||||
PM.PART_TYPE,
|
||||
CC_PARTTYPE.code_name AS PART_TYPE_NAME,
|
||||
PM.WRITER,
|
||||
UI.user_name AS WRITER_NAME,
|
||||
COALESCE(WTS.UNIT_NO || '-' || WTS.TASK_NAME, '') AS UNIT_NAME,
|
||||
TO_CHAR(PM.HIS_REG_DATE, 'YYYY-MM-DD') AS HIS_REG_DATE_TITLE,
|
||||
PM.BOM_DEPLOY_DATE,
|
||||
TO_CHAR(PM.BOM_DEPLOY_DATE, 'YYYY-MM-DD') AS BOM_DEPLOY_DATE_TITLE
|
||||
FROM PART_MNG_HISTORY PM
|
||||
LEFT JOIN PROJECT_MGMT CM ON CM.OBJID = PM.CONTRACT_OBJID
|
||||
LEFT JOIN PART_BOM_REPORT B ON B.OBJID = PM.BOM_REPORT_OBJID
|
||||
LEFT JOIN PROJECT_MGMT CM2 ON CM2.OBJID = B.CONTRACT_OBJID
|
||||
LEFT JOIN PMS_WBS_TASK WTS ON WTS.OBJID = B.UNIT_CODE
|
||||
LEFT JOIN user_info UI ON UI.user_id = PM.WRITER
|
||||
LEFT JOIN COMM_CODE CC_CHGTYPE ON CC_CHGTYPE.code_id = PM.CHANGE_TYPE AND CC_CHGTYPE.status='active'
|
||||
LEFT JOIN COMM_CODE CC_CHGOPT ON CC_CHGOPT.code_id = PM.CHANGE_OPTION AND CC_CHGOPT.status='active'
|
||||
LEFT JOIN COMM_CODE CC_PARTTYPE ON CC_PARTTYPE.code_id = PM.PART_TYPE AND CC_PARTTYPE.status='active'
|
||||
WHERE 1=1
|
||||
/* 파트 신규등록건 조회 제외 — wace 1:1 */
|
||||
AND NOT (PM.HIS_STATUS = 'DEPLOY' AND PM.CHANGE_TYPE IS NULL AND PM.REVISION = 'RE')
|
||||
AND PM.REVISION IS NOT NULL
|
||||
AND COALESCE(PM.BOM_STATUS, '') = 'deploy'
|
||||
+ 동적 (위 10 필터)
|
||||
ORDER BY COALESCE(PM.HIS_REG_DATE, PM.REG_DATE) DESC, PM.PART_NO
|
||||
```
|
||||
|
||||
**Response**: `{ rows: EoHistoryRow[], total, page, pageSize }`
|
||||
|
||||
### 2.2 단건 — `GET /api/development/eo-history/:objid`
|
||||
|
||||
`SELECT PM.* FROM PART_MNG_HISTORY PM WHERE PM.OBJID = $1::numeric` + comm_code JOIN (CHANGE_TYPE/OPTION/PART_TYPE 라벨).
|
||||
|
||||
---
|
||||
|
||||
## 3. Backend 파일 구조
|
||||
|
||||
```
|
||||
backend-node/src/
|
||||
routes/devEoHistoryRoutes.ts // 2 endpoint
|
||||
controllers/devEoHistoryController.ts
|
||||
services/devEoHistoryService.ts // list + getByObjid
|
||||
```
|
||||
|
||||
`app.ts`: `app.use("/api/development", devEoHistoryRoutes)`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Frontend 파일 구조
|
||||
|
||||
```
|
||||
frontend/
|
||||
app/(main)/COMPANY_16/development/change-list/page.tsx
|
||||
components/development/PartHisDetailDialog.tsx
|
||||
lib/api/devEoHistory.ts
|
||||
```
|
||||
|
||||
### 4.1 그리드 16셀 (wace partMngHisList.jsp:50~75 1:1)
|
||||
|
||||
| key | 라벨 | 정렬 | 너비 |
|
||||
|---|---|---|---:|
|
||||
| eo_no | EO No | left | 100 |
|
||||
| project_no | 프로젝트번호 | left | 120 |
|
||||
| project_name | 프로젝트명 | left | 180 |
|
||||
| unit_name | 유닛명 | left | 160 |
|
||||
| parent_part_info | 모품번 | left | 160 |
|
||||
| part_no_disp | 품번 | left | 160 |
|
||||
| part_name_disp | 품명 | left | flex |
|
||||
| qty | 수량 | right | 70 |
|
||||
| qty_temp | 변경수량 | right | 80 |
|
||||
| change_type_name | EO구분 | center | 90 |
|
||||
| change_option_name | EO사유 | center | 100 |
|
||||
| revision_disp | Revision | center | 90 |
|
||||
| eo_date | EO Date | center | 100 |
|
||||
| part_type_name | PART구분 | center | 90 |
|
||||
| writer_name | 담당자 | center | 90 |
|
||||
| his_reg_date_title | 실행일 | center | 100 |
|
||||
|
||||
### 4.2 검색 폼 (wace 10 필드 1:1)
|
||||
|
||||
Year · contract_objid · unit_code · part_no · part_name · change_option · eo_start_date~end_date · change_type · part_type · writer_id
|
||||
|
||||
본 PR 1차: Year · 프로젝트 OBJID · part_no · part_name · eo_start_date~end_date · change_type · change_option · part_type (Smart 폼). writer_id 는 UserSelect 별 PR.
|
||||
|
||||
### 4.3 액션
|
||||
|
||||
- 조회 (only) — read-only 메뉴
|
||||
- 행 클릭 → PartHisDetailDialog (모든 필드 disabled)
|
||||
|
||||
---
|
||||
|
||||
## 5. 검증 시나리오
|
||||
|
||||
1. M5 페이지 진입 → part_mng_history 빈 그리드 (시드 후 표시 확인)
|
||||
2. PART 등록(M1) → 확정(deploy) 트랜잭션으로 part_mng_history INSERT (PR-A 의 deploy 경로)
|
||||
- 단, deploy 시 `bom_status` 가 NULL 이므로 본 SQL의 `bom_status='deploy'` 필터에서 제외됨
|
||||
- 본 메뉴는 **BOM 변경 이력** 표시 목적 — 실제 데이터는 M3 BOM 배포 시 INSERT됨 (PR-B 범위 밖)
|
||||
3. 검색 필터(year/part_no/change_type/eo_start_date) → 동적 WHERE 적용 확인
|
||||
4. 행 클릭 → 상세 다이얼로그에 모든 필드 표시 (raw + 라벨)
|
||||
|
||||
---
|
||||
|
||||
## 6. 적응 사항
|
||||
|
||||
| # | 항목 | 변경 |
|
||||
|---|---|---|
|
||||
| 1 | `NVL()` → `COALESCE()` | wace는 Oracle 호환 NVL 사용, PG 표준은 COALESCE |
|
||||
| 2 | `PART_MNG.OBJID = PM.PARENT_PART_NO` JOIN | OBJID bigint vs PARENT_PART_NO varchar → cast `::varchar` |
|
||||
| 3 | `CODE_NAME()` 함수 | LEFT JOIN comm_code 별칭으로 풀어 쓰기 |
|
||||
| 4 | wace `STATUS_NQ`/`SEARCH_TYPE=CHANGE_LIST` 필터 제거 | 본 PR 메뉴는 변경리스트 한 화면 — 필터 단순화 |
|
||||
@@ -0,0 +1,187 @@
|
||||
"use client";
|
||||
|
||||
// 개발관리 > 설계변경 리스트 (M5, read-only) — wace partMngHisList.jsp 1:1
|
||||
// 그리드: part_mng_history 16셀
|
||||
// 검색: 8 필드 (1차) — Year/contract_objid/part_no/part_name/eo_start~end/change_type/change_option/part_type
|
||||
// 참조: docs/migration/development/03-eo-history.md
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Search, Loader2, RotateCcw } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { DataGrid, DataGridColumn } from "@/components/common/DataGrid";
|
||||
import { CommCodeSelect } from "@/components/common/CommCodeSelect";
|
||||
import { devEoHistoryApi, EoHistoryListFilter, EoHistoryRow } from "@/lib/api/devEoHistory";
|
||||
import { PartHisDetailDialog } from "@/components/development/PartHisDetailDialog";
|
||||
|
||||
// comm_code 그룹 (vexplor_rps)
|
||||
const GROUP_PART_TYPE = "0000062";
|
||||
// change_type/change_option은 wace 운영판 그룹 ID가 명확하지 않으므로 text input으로 우선 운영.
|
||||
// (시드 후 그룹 ID 확인되면 SmartSelect 전환)
|
||||
|
||||
const YEAR_OPTIONS = (() => {
|
||||
const cur = new Date().getFullYear();
|
||||
const arr: string[] = [];
|
||||
for (let y = cur + 4; y >= cur - 8; y--) arr.push(String(y));
|
||||
return arr;
|
||||
})();
|
||||
|
||||
const GRID_COLUMNS: DataGridColumn[] = [
|
||||
{ key: "eo_no", label: "EO No", width: "w-[100px]", frozen: true },
|
||||
{ key: "project_no", label: "프로젝트번호", width: "w-[120px]" },
|
||||
{ key: "project_name", label: "프로젝트명", width: "w-[180px]" },
|
||||
{ key: "unit_name", label: "유닛명", width: "w-[160px]" },
|
||||
{ key: "parent_part_info", label: "모품번", width: "w-[160px]" },
|
||||
{ key: "part_no_disp", label: "품번", width: "w-[160px]" },
|
||||
{ key: "part_name_disp", label: "품명", minWidth: "min-w-[180px]" },
|
||||
{ key: "qty", label: "수량", width: "w-[70px]", align: "right", formatNumber: true },
|
||||
{ key: "qty_temp", label: "변경수량", width: "w-[80px]", align: "right", formatNumber: true },
|
||||
{ key: "change_type_name", label: "EO구분", width: "w-[90px]", align: "center" },
|
||||
{ key: "change_option_name", label: "EO사유", width: "w-[100px]", align: "center" },
|
||||
{ key: "revision_disp", label: "Revision", width: "w-[90px]", align: "center" },
|
||||
{ key: "eo_date", label: "EO Date", width: "w-[100px]", align: "center" },
|
||||
{ key: "part_type_name", label: "PART구분", width: "w-[90px]", align: "center" },
|
||||
{ key: "writer_name", label: "담당자", width: "w-[90px]", align: "center" },
|
||||
{ key: "his_reg_date_title", label: "실행일", width: "w-[100px]", align: "center" },
|
||||
];
|
||||
|
||||
const EMPTY_FILTER: EoHistoryListFilter = {
|
||||
Year: "", contract_objid: "",
|
||||
part_no: "", part_name: "",
|
||||
change_option: "", change_type: "", part_type: "",
|
||||
eo_start_date: "", eo_end_date: "",
|
||||
page: 1, page_size: 50,
|
||||
};
|
||||
|
||||
export default function EoHistoryPage() {
|
||||
const [rows, setRows] = useState<EoHistoryRow[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [filter, setFilter] = useState<EoHistoryListFilter>(EMPTY_FILTER);
|
||||
|
||||
const [detailOpen, setDetailOpen] = useState(false);
|
||||
const [detailObjid, setDetailObjid] = useState<string | null>(null);
|
||||
|
||||
const fetchList = useCallback(async (override?: Partial<EoHistoryListFilter>) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const f = { ...filter, ...override };
|
||||
const res = await devEoHistoryApi.list(f);
|
||||
setRows(res.rows ?? []);
|
||||
setTotal(res.total ?? 0);
|
||||
} catch (e: any) {
|
||||
toast.error(e?.response?.data?.message ?? e?.message ?? "조회 실패");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [filter]);
|
||||
|
||||
useEffect(() => { fetchList(); /* eslint-disable-next-line */ }, []);
|
||||
|
||||
// EO_NO 셀 클릭 → 상세 다이얼로그
|
||||
const columns = useMemo(
|
||||
() => GRID_COLUMNS.map((c) =>
|
||||
c.key === "eo_no"
|
||||
? { ...c, onClick: (row: any) => { setDetailObjid(row.objid); setDetailOpen(true); } }
|
||||
: c,
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="border-b bg-card px-4 py-3">
|
||||
<div className="grid grid-cols-4 gap-3 text-sm">
|
||||
<Field label="년도">
|
||||
<select className="h-9 w-full rounded-md border bg-background px-2 text-sm"
|
||||
value={filter.Year ?? ""}
|
||||
onChange={(e) => setFilter({ ...filter, Year: e.target.value })}>
|
||||
<option value="">전체</option>
|
||||
{YEAR_OPTIONS.map((y) => <option key={y} value={y}>{y}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="프로젝트 OBJID">
|
||||
<Input value={filter.contract_objid ?? ""}
|
||||
onChange={(e) => setFilter({ ...filter, contract_objid: e.target.value })}
|
||||
placeholder="project_mgmt.objid" />
|
||||
</Field>
|
||||
<Field label="품번">
|
||||
<Input value={filter.part_no ?? ""}
|
||||
onChange={(e) => setFilter({ ...filter, part_no: e.target.value })}
|
||||
placeholder="part_no LIKE" />
|
||||
</Field>
|
||||
<Field label="품명">
|
||||
<Input value={filter.part_name ?? ""}
|
||||
onChange={(e) => setFilter({ ...filter, part_name: e.target.value })}
|
||||
placeholder="part_name LIKE" />
|
||||
</Field>
|
||||
|
||||
<Field label="EO Date 시작">
|
||||
<Input type="date" value={filter.eo_start_date ?? ""}
|
||||
onChange={(e) => setFilter({ ...filter, eo_start_date: e.target.value })} />
|
||||
</Field>
|
||||
<Field label="EO Date 종료">
|
||||
<Input type="date" value={filter.eo_end_date ?? ""}
|
||||
onChange={(e) => setFilter({ ...filter, eo_end_date: e.target.value })} />
|
||||
</Field>
|
||||
<Field label="PART구분">
|
||||
<CommCodeSelect groupId={GROUP_PART_TYPE}
|
||||
value={filter.part_type ?? ""}
|
||||
onValueChange={(v) => setFilter({ ...filter, part_type: v })} />
|
||||
</Field>
|
||||
<Field label="EO구분 / EO사유 (code_id)">
|
||||
<div className="flex items-center gap-1">
|
||||
<Input value={filter.change_type ?? ""}
|
||||
onChange={(e) => setFilter({ ...filter, change_type: e.target.value })}
|
||||
placeholder="EO구분 code_id" />
|
||||
<Input value={filter.change_option ?? ""}
|
||||
onChange={(e) => setFilter({ ...filter, change_option: e.target.value })}
|
||||
placeholder="EO사유 code_id" />
|
||||
</div>
|
||||
</Field>
|
||||
</div>
|
||||
<div className="mt-3 flex items-center justify-between">
|
||||
<div className="text-xs text-muted-foreground">총 {total.toLocaleString()}건 (read-only)</div>
|
||||
<div className="flex items-end gap-2">
|
||||
<Button variant="outline" size="sm"
|
||||
onClick={() => { setFilter(EMPTY_FILTER); fetchList(EMPTY_FILTER); }}>
|
||||
<RotateCcw className="h-4 w-4" /><span className="ml-1">초기화</span>
|
||||
</Button>
|
||||
<Button size="sm" onClick={() => fetchList()} disabled={loading}>
|
||||
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Search className="h-4 w-4" />}
|
||||
<span className="ml-1">조회</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 flex-1 p-2">
|
||||
<DataGrid
|
||||
columns={columns}
|
||||
data={rows}
|
||||
loading={loading}
|
||||
showRowNumber
|
||||
emptyMessage="설계변경 이력이 없습니다."
|
||||
gridId="development-eo-history"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<PartHisDetailDialog
|
||||
open={detailOpen}
|
||||
onOpenChange={setDetailOpen}
|
||||
objid={detailObjid}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Field({ label, children }: { label: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div>
|
||||
<Label className="mb-1 block text-xs text-muted-foreground">{label}</Label>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
"use client";
|
||||
|
||||
// 개발관리 > 설계변경 리스트 상세 다이얼로그 (read-only).
|
||||
// wace partMngHisDetailPopUp.jsp 1:1 — 모든 필드 disabled.
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { devEoHistoryApi, EoHistoryDetail } from "@/lib/api/devEoHistory";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
objid: string | null;
|
||||
}
|
||||
|
||||
export function PartHisDetailDialog({ open, onOpenChange, objid }: Props) {
|
||||
const [row, setRow] = useState<EoHistoryDetail | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || !objid) return;
|
||||
let alive = true;
|
||||
setLoading(true);
|
||||
devEoHistoryApi.detail(objid)
|
||||
.then((data) => { if (alive) setRow(data); })
|
||||
.catch((e: any) => {
|
||||
toast.error(e?.response?.data?.message ?? e?.message ?? "조회 실패");
|
||||
onOpenChange(false);
|
||||
})
|
||||
.finally(() => { if (alive) setLoading(false); });
|
||||
return () => { alive = false; };
|
||||
}, [open, objid, onOpenChange]);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-5xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>설계변경 상세 정보 (PART_MNG_HISTORY)</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{loading || !row ? (
|
||||
<div className="flex h-64 items-center justify-center">
|
||||
<Loader2 className="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="max-h-[70vh] space-y-4 overflow-y-auto px-1 py-2 text-sm">
|
||||
<Section title="EO 정보">
|
||||
<Row>
|
||||
<V label="EO_NO" value={row.eo_no} />
|
||||
<V label="EO_DATE" value={row.eo_date} align="center" />
|
||||
<V label="실행일" value={row.his_reg_date_title} align="center" />
|
||||
</Row>
|
||||
<Row>
|
||||
<V label="EO구분" value={row.change_type_name} align="center" />
|
||||
<V label="EO사유" value={row.change_option_name} align="center" />
|
||||
<V label="담당자" value={row.writer_name} align="center" />
|
||||
</Row>
|
||||
</Section>
|
||||
|
||||
<Section title="프로젝트 / 유닛">
|
||||
<Row>
|
||||
<V label="프로젝트번호" value={row.project_no} />
|
||||
<V label="프로젝트명" value={row.project_name} />
|
||||
<V label="유닛명" value={row.unit_name} />
|
||||
</Row>
|
||||
</Section>
|
||||
|
||||
<Section title="PART 정보">
|
||||
<Row>
|
||||
<V label="모품번" value={row.parent_part_info} />
|
||||
<V label="품번" value={row.part_no_disp ?? row.part_no} />
|
||||
<V label="품명" value={row.part_name_disp ?? row.part_name} />
|
||||
</Row>
|
||||
<Row>
|
||||
<V label="범주" value={row.part_type_name} align="center" />
|
||||
<V label="Revision" value={row.revision_disp ?? row.revision} align="center" />
|
||||
<V label="규격" value={row.spec} />
|
||||
</Row>
|
||||
<Row>
|
||||
<V label="재료" value={row.material} />
|
||||
<V label="중량" value={row.weight} align="right" />
|
||||
<V label="메이커" value={row.maker} />
|
||||
</Row>
|
||||
<Row>
|
||||
<V label="열처리경도" value={row.heat_treatment_hardness} />
|
||||
<V label="열처리방법" value={row.heat_treatment_method} />
|
||||
<V label="표면처리" value={row.surface_treatment} />
|
||||
</Row>
|
||||
<Row>
|
||||
<V label="비고" value={row.remark} />
|
||||
<V label="" value="" />
|
||||
<V label="" value="" />
|
||||
</Row>
|
||||
</Section>
|
||||
|
||||
<Section title="수량 / BOM 상태">
|
||||
<Row>
|
||||
<V label="수량" value={row.qty} align="right" />
|
||||
<V label="변경수량" value={row.qty_temp} align="right" />
|
||||
<V label="BOM 상태" value={row.bom_qty_status} align="center" />
|
||||
</Row>
|
||||
<Row>
|
||||
<V label="BOM 배포일" value={row.bom_deploy_date_title} align="center" />
|
||||
<V label="이력 상태" value={row.his_status} align="center" />
|
||||
<V label="" value="" />
|
||||
</Row>
|
||||
</Section>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>닫기</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function Section({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="rounded-md border bg-card p-3">
|
||||
<div className="mb-2 text-sm font-semibold text-foreground">{title}</div>
|
||||
<div className="space-y-2">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Row({ children }: { children: React.ReactNode }) {
|
||||
return <div className="grid grid-cols-3 gap-3">{children}</div>;
|
||||
}
|
||||
|
||||
function V({ label, value, align }: { label: string; value: any; align?: "left" | "center" | "right" }) {
|
||||
const cls = align === "right" ? "text-right" : align === "center" ? "text-center" : "";
|
||||
return (
|
||||
<div>
|
||||
{label && <Label className="mb-1 block text-xs text-muted-foreground">{label}</Label>}
|
||||
<div className={`min-h-9 rounded-md border bg-muted/30 px-2 py-2 ${cls}`}>
|
||||
{value != null && value !== "" ? value : <span className="text-muted-foreground">—</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -111,6 +111,7 @@ const ADMIN_PAGE_REGISTRY: Record<string, React.ComponentType<any>> = {
|
||||
"/COMPANY_16/development/part-search": dynamic(() => import("@/app/(main)/COMPANY_16/development/part-search/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_16/development/ebom-regist": dynamic(() => import("@/app/(main)/COMPANY_16/development/ebom-regist/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_16/development/ebom-search": dynamic(() => import("@/app/(main)/COMPANY_16/development/ebom-search/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_16/development/change-list": dynamic(() => import("@/app/(main)/COMPANY_16/development/change-list/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_16/production/process-info": dynamic(() => import("@/app/(main)/COMPANY_16/production/process-info/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_16/production/result": dynamic(() => import("@/app/(main)/COMPANY_16/production/result/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_16/production/work-instruction": dynamic(() => import("@/app/(main)/COMPANY_16/production/work-instruction/page"), { ssr: false, loading: LoadingFallback }),
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import { apiClient } from "./client";
|
||||
|
||||
// ============================================================
|
||||
// 개발관리 설계변경 리스트 (M5, read-only) — wace partMngHistList 1:1
|
||||
// 라우트: /api/development/eo-history/*
|
||||
// ============================================================
|
||||
|
||||
export interface EoHistoryListFilter {
|
||||
Year?: string;
|
||||
contract_objid?: string;
|
||||
unit_code?: string;
|
||||
part_no?: string;
|
||||
part_name?: string;
|
||||
change_option?: string;
|
||||
eo_start_date?: string;
|
||||
eo_end_date?: string;
|
||||
change_type?: string;
|
||||
part_type?: string;
|
||||
writer_id?: string;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
}
|
||||
|
||||
export interface EoHistoryRow {
|
||||
objid: string;
|
||||
eo_no: string | null;
|
||||
year: string | null;
|
||||
project_no: string | null;
|
||||
project_name: string | null;
|
||||
unit_name: string | null;
|
||||
parent_part_info: string | null;
|
||||
part_no_disp: string | null;
|
||||
part_name_disp: string | null;
|
||||
part_no: string | null;
|
||||
part_name: string | null;
|
||||
bom_qty_status: string | null;
|
||||
qty: string | null;
|
||||
qty_temp: string | null;
|
||||
change_type: string | null;
|
||||
change_type_name: string | null;
|
||||
change_option: string | null;
|
||||
change_option_name: string | null;
|
||||
revision_disp: string | null;
|
||||
revision: string | null;
|
||||
eo_date: string | null;
|
||||
part_type: string | null;
|
||||
part_type_name: string | null;
|
||||
writer: string | null;
|
||||
writer_name: string | null;
|
||||
his_reg_date_title: string | null;
|
||||
bom_deploy_date: string | null;
|
||||
bom_deploy_date_title: string | null;
|
||||
}
|
||||
|
||||
export interface EoHistoryListResponse {
|
||||
rows: EoHistoryRow[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
export interface EoHistoryDetail extends EoHistoryRow {
|
||||
// raw PART_MNG_HISTORY 추가 필드
|
||||
product_mgmt_objid?: string | null;
|
||||
upg_no?: string | null;
|
||||
unit?: string | null;
|
||||
spec?: string | null;
|
||||
material?: string | null;
|
||||
weight?: string | null;
|
||||
remark?: string | null;
|
||||
es_spec?: string | null;
|
||||
ms_spec?: string | null;
|
||||
design_apply_point?: string | null;
|
||||
management_flag?: string | null;
|
||||
status?: string | null;
|
||||
reg_date?: string | null;
|
||||
is_last?: string | null;
|
||||
sourcing_code?: string | null;
|
||||
sub_material?: string | null;
|
||||
thickness?: string | null;
|
||||
width?: string | null;
|
||||
height?: string | null;
|
||||
out_diameter?: string | null;
|
||||
in_diameter?: string | null;
|
||||
length?: string | null;
|
||||
supply_code?: string | null;
|
||||
contract_objid?: string | null;
|
||||
maker?: string | null;
|
||||
his_status?: string | null;
|
||||
bom_status?: string | null;
|
||||
heat_treatment_hardness?: string | null;
|
||||
heat_treatment_method?: string | null;
|
||||
surface_treatment?: string | null;
|
||||
customer_project_name?: string | null;
|
||||
}
|
||||
|
||||
export const devEoHistoryApi = {
|
||||
async list(filter: EoHistoryListFilter = {}): Promise<EoHistoryListResponse> {
|
||||
const res = await apiClient.get("/development/eo-history/list", { params: filter });
|
||||
return res.data?.data as EoHistoryListResponse;
|
||||
},
|
||||
async detail(objid: string): Promise<EoHistoryDetail | null> {
|
||||
const res = await apiClient.get(`/development/eo-history/${objid}`);
|
||||
return res.data?.data ?? null;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user