7218edc500
wace partMngTempList.jsp btnDrawingUpload + PartMngController.uploadDrawingFilesForPartList +
partMng.xml partMngListByPartNos 1:1 이식.
- 백엔드 (devPartService.drawingMultiUpload):
· 확장자 → doc_type 매핑 (STP/STEP=3D_CAD, DWG/DXF=2D_DRAWING_CAD, PDF=2D_PDF_CAD)
· 파일명에서 알려진 확장자 반복 제거 (.idw .dwg .dxf .stp .step .pdf .chg)
· PART_NO 매칭: 정확 일치 우선 → 안 되면 longest prefix
· partNoList 지정 → 그 목록 IN 절로 후보 제한 (M1, 현재 그리드 기반)
· partNoList 미지정 → IS_LAST='1' 전체 part_mng 매칭 (M2, 페이지 밖도 허용)
(wace partMngListByPartNos <if PART_NO_LIST != null> 분기 1:1)
· 매칭 성공 → attach_file_info INSERT (target_objid = part_mng.objid)
· 매칭 실패 → notFoundCount + 임시 파일 삭제
· 결과 details[] 반환 (파일별 상태/매칭품번/사유)
- 엔드포인트: POST /api/development/part/drawing-multi-upload
· multer 파일당 200MB · 최대 500개 · 임시 디스크 저장 후 회사/날짜 폴더 이동
- 프론트 PartDrawingMultiUploadButton (개발관리 공용):
· 버튼 클릭 → 숨김 input(multiple, accept=.stp,.step,.dwg,.dxf,.pdf)
· 확장자별 분류 + "총 N개 업로드?" confirm (wace 1:1 텍스트)
· 결과 다이얼로그 — 총합/성공/품번 미존재/실패 + 파일별 상세표
- M1(part-regist): partNoList = 현재 그리드 rows.part_no 전달
- M2(part-search): partNoList 미전달 → 전체 part_mng 매칭
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
349 lines
9.6 KiB
TypeScript
349 lines
9.6 KiB
TypeScript
import { apiClient } from "./client";
|
|
|
|
// ============================================================
|
|
// 개발관리 PART (M1 등록 / M2 조회) — wace partMng.xml 1:1
|
|
// 라우트: /api/development/part-temp/*, /api/development/part/*
|
|
// ============================================================
|
|
|
|
export interface PartListFilter {
|
|
search_part_no?: string;
|
|
search_part_name?: string;
|
|
search_material?: string;
|
|
search_spec?: string;
|
|
search_part_type?: string;
|
|
writer?: string;
|
|
status?: string;
|
|
status_arr?: string[];
|
|
product_code?: string;
|
|
upg_no?: string;
|
|
page?: number;
|
|
page_size?: number;
|
|
|
|
// M2 추가 필터
|
|
search_year?: string;
|
|
search_design_date_from?: string;
|
|
search_design_date_to?: string;
|
|
customer_objid?: string;
|
|
customer_cd?: string;
|
|
project_name?: string;
|
|
unit_code?: string;
|
|
is_last?: string;
|
|
eo?: string;
|
|
}
|
|
|
|
/** partMngBaseSimple + M1/M2 추가 컬럼 평탄화 — Postgres는 컬럼명을 소문자로 반환 */
|
|
export interface PartRow {
|
|
objid: string;
|
|
part_no: string | null;
|
|
part_name: string | null;
|
|
product_mgmt_objid: string | null;
|
|
upg_no: string | null;
|
|
unit: string | null;
|
|
unit_title: string | null;
|
|
qty: string | null;
|
|
spec: string | null;
|
|
post_processing: string | null;
|
|
material: string | null;
|
|
weight: string | null;
|
|
part_type: string | null;
|
|
part_type_title: string | null;
|
|
remark: string | null;
|
|
es_spec: string | null;
|
|
ms_spec: string | null;
|
|
change_type: string | null;
|
|
design_apply_point: string | null;
|
|
change_option: string | null;
|
|
change_option_name: string | null;
|
|
management_flag: string | null;
|
|
revision: string | null;
|
|
status: string | null;
|
|
reg_date: string | null;
|
|
part_regdate_title: string | null;
|
|
edit_date: string | null;
|
|
writer: string | null;
|
|
is_last: string | null;
|
|
is_longd: string | null;
|
|
eo_date: string | null;
|
|
eo_no: string | null;
|
|
eo_temp: string | null;
|
|
maker: string | null;
|
|
contract_objid: string | null;
|
|
thickness: string | null;
|
|
width: string | null;
|
|
height: string | null;
|
|
out_diameter: string | null;
|
|
in_diameter: string | null;
|
|
length: string | null;
|
|
sourcing_code: string | null;
|
|
supply_code: string | null;
|
|
supply_name: string | null;
|
|
sub_material: string | null;
|
|
parent_part_no: string | null;
|
|
design_date: string | null;
|
|
deploy_date: string | null;
|
|
excel_upload_seq: string | number | null;
|
|
cu01_cnt: number | string | null;
|
|
cu02_cnt: number | string | null;
|
|
cu03_cnt: number | string | null;
|
|
cu_total_cnt: number | string | null;
|
|
|
|
// 추가 15컬럼 + 라벨
|
|
heat_treatment_hardness: string | null;
|
|
heat_treatment_method: string | null;
|
|
surface_treatment: string | null;
|
|
acctfg: string | null;
|
|
acctfg_nm: string | null;
|
|
odrfg: string | null;
|
|
odrfg_nm: string | null;
|
|
unit_dc: string | null;
|
|
unit_dc_nm: string | null;
|
|
unitmang_dc: string | null;
|
|
unitmang_dc_nm: string | null;
|
|
unitchng_nb: string | number | null;
|
|
lot_fg: string | null;
|
|
lot_fg_nm: string | null;
|
|
use_yn: string | null;
|
|
use_yn_nm: string | null;
|
|
qc_fg: string | null;
|
|
qc_fg_nm: string | null;
|
|
setitem_fg: string | null;
|
|
setitem_fg_nm: string | null;
|
|
req_fg: string | null;
|
|
req_fg_nm: string | null;
|
|
unit_length: string | null;
|
|
unit_qty: string | null;
|
|
|
|
// M1 전용 부속
|
|
partner_title?: string | null;
|
|
parent_part_info?: string | null;
|
|
bom_report_objid?: string | null;
|
|
objid_qty?: string | null;
|
|
child_objid?: string | null;
|
|
q_qty?: string | null;
|
|
q_qty_raw?: string | null;
|
|
qty_temp?: string | null;
|
|
sort?: string | null;
|
|
|
|
// M2 전용 부속
|
|
num?: number | null;
|
|
bom_qty?: string | null;
|
|
}
|
|
|
|
export interface PartListResponse {
|
|
rows: PartRow[];
|
|
total: number;
|
|
page: number;
|
|
pageSize: number;
|
|
}
|
|
|
|
export interface PartCreateBody {
|
|
part_objid?: string;
|
|
part_no: string;
|
|
part_name: string;
|
|
unit?: string;
|
|
qty?: string;
|
|
spec?: string;
|
|
material?: string;
|
|
thickness?: string;
|
|
width?: string;
|
|
height?: string;
|
|
out_diameter?: string;
|
|
in_diameter?: string;
|
|
length?: string;
|
|
remark?: string;
|
|
part_type: string;
|
|
product_mgmt_objid?: string;
|
|
supply_code?: string;
|
|
maker?: string;
|
|
contract_objid?: string;
|
|
post_processing?: string;
|
|
heat_treatment_hardness?: string;
|
|
heat_treatment_method?: string;
|
|
surface_treatment?: 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;
|
|
unit_length?: string;
|
|
unit_qty?: string;
|
|
}
|
|
|
|
export interface PartUpdateBody {
|
|
part_name?: string;
|
|
material?: string;
|
|
heat_treatment_hardness?: string;
|
|
heat_treatment_method?: string;
|
|
surface_treatment?: string;
|
|
maker?: string;
|
|
part_type?: string;
|
|
acctfg?: string;
|
|
odrfg?: string;
|
|
spec?: 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;
|
|
unit_length?: string;
|
|
unit_qty?: string;
|
|
remark?: string;
|
|
}
|
|
|
|
export interface DeployResult {
|
|
deployed: number;
|
|
eo_nos: Record<string, string>;
|
|
}
|
|
|
|
// ─── Excel Import ────────────────────────────────────────────
|
|
|
|
export interface PartExcelRow {
|
|
NOTE: string;
|
|
PART_NO: string;
|
|
PART_NAME: string;
|
|
MATERIAL: string;
|
|
HEAT_TREATMENT_HARDNESS: string;
|
|
HEAT_TREATMENT_METHOD: string;
|
|
SURFACE_TREATMENT: string;
|
|
MAKER: string;
|
|
PART_TYPE: string;
|
|
PART_TYPE_NAME?: string;
|
|
SPEC: string;
|
|
ACCTFG: string;
|
|
ACCTFG_NAME?: string;
|
|
ODRFG: string;
|
|
ODRFG_NAME?: string;
|
|
UNIT_DC: string;
|
|
UNIT_DC_NAME?: string;
|
|
UNITMANG_DC: string;
|
|
UNITMANG_DC_NAME?: string;
|
|
UNITCHNG_NB: string;
|
|
LOT_FG: string;
|
|
USE_YN: string;
|
|
QC_FG: string;
|
|
SETITEM_FG: string;
|
|
REQ_FG: string;
|
|
UNIT_LENGTH: string;
|
|
UNIT_QTY: string;
|
|
REMARK: string;
|
|
}
|
|
|
|
export interface ExcelParseResponse {
|
|
rows: PartExcelRow[];
|
|
hasError: boolean;
|
|
}
|
|
|
|
export interface ExcelSaveResponse {
|
|
inserted: number;
|
|
skipped: number;
|
|
skippedPartNos: string[];
|
|
}
|
|
|
|
// ─── API ────────────────────────────────────────────────────
|
|
|
|
export const devPartApi = {
|
|
// M1 그리드
|
|
async listTemp(filter: PartListFilter = {}): Promise<PartListResponse> {
|
|
const res = await apiClient.get("/development/part-temp/list", { params: filter });
|
|
return res.data?.data as PartListResponse;
|
|
},
|
|
|
|
// M2 그리드
|
|
async list(filter: PartListFilter = {}): Promise<PartListResponse> {
|
|
const res = await apiClient.get("/development/part/list", { params: filter });
|
|
return res.data?.data as PartListResponse;
|
|
},
|
|
|
|
// 단건 상세
|
|
async detail(objid: string): Promise<PartRow | null> {
|
|
const res = await apiClient.get(`/development/part/${objid}`);
|
|
return res.data?.data ?? null;
|
|
},
|
|
|
|
// 신규 등록 (38 컬럼)
|
|
async create(body: PartCreateBody): Promise<{ objid: string }> {
|
|
const res = await apiClient.post("/development/part", body);
|
|
return res.data?.data;
|
|
},
|
|
|
|
// 상세 수정 (21 컬럼)
|
|
async update(objid: string, body: PartUpdateBody) {
|
|
return (await apiClient.put(`/development/part/${objid}`, body)).data;
|
|
},
|
|
|
|
// 확정 (M1→M2): EO_NO 채번 + part_mng_history 이력
|
|
async deploy(objids: string[]): Promise<DeployResult> {
|
|
const res = await apiClient.post("/development/part-temp/deploy", { objids });
|
|
return res.data?.data as DeployResult;
|
|
},
|
|
|
|
// 다중 삭제
|
|
async remove(objids: string[]) {
|
|
const res = await apiClient.delete("/development/part", { data: { objids } });
|
|
return res.data;
|
|
},
|
|
|
|
// Excel Import — 파싱 + 검증
|
|
async excelParse(file: File): Promise<ExcelParseResponse> {
|
|
const fd = new FormData();
|
|
fd.append("file", file);
|
|
const res = await apiClient.post("/development/part/excel-parse", fd, {
|
|
headers: { "Content-Type": "multipart/form-data" },
|
|
});
|
|
return res.data?.data as ExcelParseResponse;
|
|
},
|
|
|
|
// Excel Import — 저장 (신규 PART_NO 만 INSERT)
|
|
async excelSave(rows: PartExcelRow[]): Promise<ExcelSaveResponse> {
|
|
const res = await apiClient.post("/development/part/excel-save", { rows });
|
|
return res.data?.data as ExcelSaveResponse;
|
|
},
|
|
|
|
// 도면 다중 업로드 (wace btnDrawingUpload 1:1)
|
|
// 확장자 stp/step → 3D_CAD, dwg/dxf → 2D_DRAWING_CAD, pdf → 2D_PDF_CAD
|
|
// 파일명 ↔ part_no 자동 매칭 (정확 일치 → longest prefix)
|
|
// partNoList 지정 → 그 목록만 매칭 후보 (M1)
|
|
// partNoList null/undefined → IS_LAST='1' 전체 매칭 (M2)
|
|
async drawingMultiUpload(
|
|
files: File[],
|
|
partNoList?: string[] | null
|
|
): Promise<DrawingMultiUploadResult> {
|
|
const fd = new FormData();
|
|
for (const f of files) fd.append("files", f);
|
|
if (Array.isArray(partNoList) && partNoList.length > 0) {
|
|
fd.append("partNoList", JSON.stringify(partNoList));
|
|
}
|
|
const res = await apiClient.post(
|
|
"/development/part/drawing-multi-upload",
|
|
fd,
|
|
{ headers: { "Content-Type": "multipart/form-data" } }
|
|
);
|
|
return res.data?.data as DrawingMultiUploadResult;
|
|
},
|
|
};
|
|
|
|
// ─── 도면 다중 업로드 결과 타입 ──────────────────────────────
|
|
|
|
export interface DrawingMultiUploadDetail {
|
|
fileName: string;
|
|
partNo?: string;
|
|
docType?: string;
|
|
status: "success" | "fail" | "notFound" | "unsupported";
|
|
reason?: string;
|
|
}
|
|
|
|
export interface DrawingMultiUploadResult {
|
|
successCount: number;
|
|
failCount: number;
|
|
notFoundCount: number;
|
|
details: DrawingMultiUploadDetail[];
|
|
}
|