feat: 모든 카테고리에 추가요청사항 저장 기능 및 엑셀 내보내기 개선
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
- 모든 BOM 카테고리(Pipe, Fitting, Flange, Gasket, Bolt, Support)에 추가요청사항 저장/편집 기능 추가
- 저장된 데이터의 카테고리 변경 및 페이지 새로고침 시 지속성 보장
- 백엔드 materials 테이블에 brand, user_requirement 컬럼 추가
- 새로운 /materials/{id}/brand, /materials/{id}/user-requirement PATCH API 엔드포인트 추가
- 모든 카테고리에서 Additional Request 컬럼 너비 확장 (UI 겹침 방지)
- GASKET 카테고리 엑셀 내보내기에 누락된 '추가요청사항' 컬럼 추가
- 엑셀 내보내기 시 저장된 추가요청사항이 우선 반영되도록 개선
- P열 납기일 규칙 유지하며 관리항목 개수 조정
This commit is contained in:
@@ -115,6 +115,56 @@ const consolidateMaterials = (materials, isComparison = false) => {
|
||||
return Object.values(consolidated);
|
||||
};
|
||||
|
||||
/**
|
||||
* 벨브 연결방식 추출 함수
|
||||
*/
|
||||
const extractValveConnectionType = (description) => {
|
||||
const descUpper = description.toUpperCase();
|
||||
|
||||
if (descUpper.includes('SW X THRD') || descUpper.includes('SW×THRD')) {
|
||||
return 'SW×THRD';
|
||||
} else if (descUpper.includes('FLG') || descUpper.includes('FLANGE')) {
|
||||
return 'FLG';
|
||||
} else if (descUpper.includes('SW') || descUpper.includes('SOCKET')) {
|
||||
return 'SW';
|
||||
} else if (descUpper.includes('THRD') || descUpper.includes('THREAD')) {
|
||||
return 'THRD';
|
||||
} else if (descUpper.includes('BW') || descUpper.includes('BUTT WELD')) {
|
||||
return 'BW';
|
||||
} else {
|
||||
return '-';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 벨브 추가 정보 추출 함수
|
||||
*/
|
||||
const extractValveAdditionalInfo = (description) => {
|
||||
const descUpper = description.toUpperCase();
|
||||
let additionalInfo = '';
|
||||
|
||||
const additionalPatterns = [
|
||||
'3-WAY', '3WAY', 'THREE WAY',
|
||||
'DOUL PLATE', 'DOUBLE PLATE', 'DUAL PLATE',
|
||||
'DOUBLE DISC', 'DUAL DISC',
|
||||
'SWING', 'LIFT', 'TILTING',
|
||||
'WAFER', 'LUG', 'FLANGED',
|
||||
'FULL BORE', 'REDUCED BORE',
|
||||
'FIRE SAFE', 'ANTI STATIC'
|
||||
];
|
||||
|
||||
for (const pattern of additionalPatterns) {
|
||||
if (descUpper.includes(pattern)) {
|
||||
if (additionalInfo) {
|
||||
additionalInfo += ', ';
|
||||
}
|
||||
additionalInfo += pattern;
|
||||
}
|
||||
}
|
||||
|
||||
return additionalInfo || '-';
|
||||
};
|
||||
|
||||
/**
|
||||
* 자재 데이터를 엑셀용 형태로 변환
|
||||
*/
|
||||
@@ -401,24 +451,28 @@ const formatMaterialForExcel = (material, includeComparison = false) => {
|
||||
} else if (valveType === 'RELIEF') {
|
||||
itemName = '릴리프 밸브';
|
||||
} else {
|
||||
// description에서 추출
|
||||
// description에서 추출 (BOM 페이지와 동일한 로직)
|
||||
const desc = cleanDescription.toUpperCase();
|
||||
if (desc.includes('GATE')) {
|
||||
itemName = '게이트 밸브';
|
||||
} else if (desc.includes('BALL')) {
|
||||
itemName = '볼 밸브';
|
||||
} else if (desc.includes('GLOBE')) {
|
||||
itemName = '글로브 밸브';
|
||||
} else if (desc.includes('CHECK')) {
|
||||
itemName = '체크 밸브';
|
||||
} else if (desc.includes('BUTTERFLY')) {
|
||||
itemName = '버터플라이 밸브';
|
||||
} else if (desc.includes('NEEDLE')) {
|
||||
itemName = '니들 밸브';
|
||||
} else if (desc.includes('RELIEF')) {
|
||||
itemName = '릴리프 밸브';
|
||||
if (desc.includes('SIGHT GLASS') || desc.includes('사이트글라스')) {
|
||||
itemName = 'SIGHT GLASS';
|
||||
} else if (desc.includes('STRAINER') || desc.includes('스트레이너')) {
|
||||
itemName = 'STRAINER';
|
||||
} else if (desc.includes('GATE') || desc.includes('게이트')) {
|
||||
itemName = 'GATE VALVE';
|
||||
} else if (desc.includes('BALL') || desc.includes('볼')) {
|
||||
itemName = 'BALL VALVE';
|
||||
} else if (desc.includes('CHECK') || desc.includes('체크')) {
|
||||
itemName = 'CHECK VALVE';
|
||||
} else if (desc.includes('GLOBE') || desc.includes('글로브')) {
|
||||
itemName = 'GLOBE VALVE';
|
||||
} else if (desc.includes('BUTTERFLY') || desc.includes('버터플라이')) {
|
||||
itemName = 'BUTTERFLY VALVE';
|
||||
} else if (desc.includes('NEEDLE') || desc.includes('니들')) {
|
||||
itemName = 'NEEDLE VALVE';
|
||||
} else if (desc.includes('RELIEF') || desc.includes('릴리프')) {
|
||||
itemName = 'RELIEF VALVE';
|
||||
} else {
|
||||
itemName = '밸브';
|
||||
itemName = 'VALVE';
|
||||
}
|
||||
}
|
||||
} else if (category === 'GASKET') {
|
||||
@@ -489,13 +543,15 @@ const formatMaterialForExcel = (material, includeComparison = false) => {
|
||||
const desc = cleanDescription.toUpperCase();
|
||||
if (desc.includes('URETHANE') || desc.includes('BLOCK SHOE')) {
|
||||
itemName = 'URETHANE BLOCK SHOE';
|
||||
// 우레탄 블럭슈의 경우 두께 정보 추가
|
||||
const thicknessMatch = desc.match(/(\d+)\s*[tT](?![oO])/);
|
||||
if (thicknessMatch) {
|
||||
itemName += ` ${thicknessMatch[1]}t`;
|
||||
}
|
||||
// 우레탄 블럭슈의 경우 두께 정보는 품목명에 포함하지 않음 (재질 열에서 처리)
|
||||
} else if (desc.includes('CLAMP')) {
|
||||
itemName = 'CLAMP';
|
||||
// 클램프 타입 상세 분류 (CL-1, CL-2, CL-3 등)
|
||||
const clampMatch = desc.match(/CL[-\s]*(\d+)/i);
|
||||
if (clampMatch) {
|
||||
itemName = `CLAMP CL-${clampMatch[1]}`;
|
||||
} else {
|
||||
itemName = 'CLAMP CL-1'; // 기본값
|
||||
}
|
||||
} else if (desc.includes('U-BOLT') || desc.includes('U BOLT')) {
|
||||
itemName = 'U-BOLT';
|
||||
} else if (desc.includes('HANGER')) {
|
||||
@@ -823,17 +879,17 @@ const formatMaterialForExcel = (material, includeComparison = false) => {
|
||||
base['관리항목3'] = ''; // N열
|
||||
base['관리항목4'] = ''; // O열
|
||||
} else if (category === 'VALVE') {
|
||||
// 밸브 전용 컬럼 (F~O) - 타입 제거, 품목명에 포함됨
|
||||
base['크기'] = material.size_spec || '-'; // F열
|
||||
// 밸브 전용 컬럼 (F~O) - 새로운 구조
|
||||
base['크기'] = material.size_spec || material.main_nom || '-'; // F열
|
||||
base['압력등급'] = pressure; // G열
|
||||
base['재질'] = grade; // H열
|
||||
base['상세내역'] = detailInfo || '-'; // I열
|
||||
base['사용자요구'] = material.user_requirement || ''; // J열
|
||||
base['관리항목1'] = ''; // K열
|
||||
base['관리항목2'] = ''; // L열
|
||||
base['관리항목3'] = ''; // M열
|
||||
base['관리항목4'] = ''; // N열
|
||||
base['관리항목5'] = ''; // O열
|
||||
base['브랜드'] = material.brand || '-'; // H열 (사용자 입력)
|
||||
base['추가정보'] = extractValveAdditionalInfo(cleanDescription); // I열 (3-WAY, DOUL PLATE 등)
|
||||
base['연결방식'] = material.connection_type || extractValveConnectionType(cleanDescription); // J열
|
||||
base['추가요청사항'] = material.user_requirement || ''; // K열
|
||||
base['관리항목1'] = ''; // L열
|
||||
base['관리항목2'] = ''; // M열
|
||||
base['관리항목3'] = ''; // N열
|
||||
base['관리항목4'] = ''; // O열
|
||||
} else if (category === 'GASKET') {
|
||||
// 가스켓 전용 컬럼 (F~O) - 재질을 2개로 분리
|
||||
// 재질 분리 로직: SS304/GRAPHITE/SS304/SS304 → 재질1: SS304/GRAPHITE, 재질2: /SS304/SS304
|
||||
@@ -866,10 +922,10 @@ const formatMaterialForExcel = (material, includeComparison = false) => {
|
||||
base['재질1'] = material1; // I열: SS304/GRAPHITE
|
||||
base['재질2'] = material2; // J열: SS304/SS304
|
||||
base['두께'] = gasketThickness || '-'; // K열: 4.5mm
|
||||
base['사용자요구'] = material.user_requirement || ''; // L열
|
||||
base['관리항목1'] = ''; // M열
|
||||
base['관리항목2'] = ''; // N열
|
||||
base['관리항목3'] = ''; // O열
|
||||
base['사용자요구'] = detailInfo; // L열: 분류기에서 추출된 요구사항
|
||||
base['추가요청사항'] = material.user_requirement || ''; // M열: 사용자 입력 요구사항
|
||||
base['관리항목1'] = ''; // N열
|
||||
base['관리항목2'] = ''; // O열
|
||||
} else if (category === 'BOLT') {
|
||||
// 볼트 전용 컬럼 (F~O) - User Requirements와 Additional Request 분리
|
||||
base['크기'] = material.size_spec || '-'; // F열
|
||||
@@ -883,17 +939,17 @@ const formatMaterialForExcel = (material, includeComparison = false) => {
|
||||
base['관리항목3'] = ''; // N열
|
||||
base['관리항목4'] = ''; // O열
|
||||
} else if (category === 'SUPPORT') {
|
||||
// 서포트 전용 컬럼 (F~O)
|
||||
// 서포트 전용 컬럼 (F~O) - 압력등급, 상세내역 제거
|
||||
base['크기'] = material.size_spec || material.main_nom || '-'; // F열
|
||||
base['압력등급'] = pressure; // G열
|
||||
base['재질'] = grade; // H열
|
||||
base['상세내역'] = material.original_description || '-'; // I열
|
||||
base['사용자요구'] = material.user_requirements?.join(', ') || ''; // J열 (분류기에서 추출)
|
||||
base['추가요청사항'] = material.user_requirement || ''; // K열 (사용자 입력)
|
||||
base['관리항목1'] = ''; // L열
|
||||
base['관리항목2'] = ''; // M열
|
||||
base['관리항목3'] = ''; // N열
|
||||
base['관리항목4'] = ''; // O열
|
||||
base['재질'] = grade; // G열
|
||||
base['사용자요구'] = material.user_requirements?.join(', ') || ''; // H열 (분류기에서 추출)
|
||||
base['추가요청사항'] = material.user_requirement || ''; // I열 (사용자 입력)
|
||||
base['관리항목1'] = ''; // J열
|
||||
base['관리항목2'] = ''; // K열
|
||||
base['관리항목3'] = ''; // L열
|
||||
base['관리항목4'] = ''; // M열
|
||||
base['관리항목5'] = ''; // N열
|
||||
base['관리항목6'] = ''; // O열
|
||||
} else {
|
||||
// 기타 카테고리 기본 컬럼 (F~O) - 타입 제거, 품목명에 포함됨
|
||||
base['크기'] = material.size_spec || '-'; // F열
|
||||
@@ -1153,10 +1209,10 @@ export const createExcelBlob = async (materials, filename, options = {}) => {
|
||||
'PIPE': ['크기', '스케줄', '재질', '제조방법', '사용자요구', '추가요청사항', '관리항목1', '관리항목2', '관리항목3', '관리항목4'],
|
||||
'FITTING': ['크기', '압력등급', '스케줄', '재질', '사용자요구', '추가요청사항', '관리항목1', '관리항목2', '관리항목3', '관리항목4'],
|
||||
'FLANGE': ['크기', '페이싱', '압력등급', '스케줄', '재질', '추가요구사항', '관리항목1', '관리항목2', '관리항목3', '관리항목4'],
|
||||
'VALVE': ['크기', '압력등급', '재질', '상세내역', '사용자요구', '관리항목1', '관리항목2', '관리항목3', '관리항목4'],
|
||||
'GASKET': ['크기', '압력등급', '구조', '재질1', '재질2', '두께', '사용자요구', '관리항목1', '관리항목2', '관리항목3'],
|
||||
'VALVE': ['크기', '압력등급', '브랜드', '추가정보', '연결방식', '추가요청사항', '관리항목1', '관리항목2', '관리항목3', '관리항목4'],
|
||||
'GASKET': ['크기', '압력등급', '구조', '재질1', '재질2', '두께', '사용자요구', '추가요청사항', '관리항목1', '관리항목2'],
|
||||
'BOLT': ['크기', '압력등급', '길이', '재질', '사용자요구', '추가요청사항', '관리항목1', '관리항목2', '관리항목3', '관리항목4'],
|
||||
'SUPPORT': ['크기', '압력등급', '재질', '상세내역', '사용자요구', '관리항목1', '관리항목2', '관리항목3', '관리항목4'],
|
||||
'SUPPORT': ['크기', '재질', '사용자요구', '추가요청사항', '관리항목1', '관리항목2', '관리항목3', '관리항목4', '관리항목5', '관리항목6'],
|
||||
};
|
||||
|
||||
const deliveryDateHeader = '납기일(YYYY-MM-DD)';
|
||||
|
||||
Reference in New Issue
Block a user