feat: 피팅류 엑셀 내보내기 개선 및 프로젝트 비활성화 버그 수정
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
✨ 피팅류 엑셀 내보내기 개선: - 품목명에 상세 피팅 타입 표시 (SOCK-O-LET, ELBOW 90° LR 등) - G열부터 압력등급/스케줄/재질/사용자요구/추가요청사항 체계적 배치 - 분류기 추출 요구사항(J열)과 사용자 입력 요구사항(K열) 분리 - P열 납기일 고정 규칙 유지, 관리항목 자동 채움 🐛 프로젝트 비활성화 버그 수정: - 백엔드: job_no 필드 추가로 프론트엔드 호환성 확보 - 프론트엔드: 안전한 프로젝트 식별자 처리 로직 구현 - 개별 프로젝트 비활성화 시 전체 프로젝트 영향 문제 해결 - 디버깅 로그 추가로 상태 변경 추적 가능 🔧 기타 개선사항: - BOM 페이지 이모지 제거 - 구매신청 후 자재 비활성화 기능 구현 - 모든 카테고리 뷰에 onPurchasedMaterialsUpdate 콜백 추가
This commit is contained in:
@@ -526,6 +526,7 @@ async def get_projects(
|
||||
projects.append({
|
||||
"id": row.id,
|
||||
"official_project_code": row.official_project_code,
|
||||
"job_no": row.official_project_code, # job_no 필드 추가 (프론트엔드 호환성)
|
||||
"project_name": row.project_name,
|
||||
"job_name": row.project_name, # 호환성을 위해 추가
|
||||
"client_name": row.client_name,
|
||||
|
||||
@@ -148,9 +148,13 @@ function App() {
|
||||
|
||||
// 프로젝트 활성화
|
||||
const handleActivateProject = (project) => {
|
||||
const projectId = project.job_no || project.official_project_code || project.id;
|
||||
console.log('🔄 프로젝트 활성화:', { project, projectId });
|
||||
|
||||
setInactiveProjects(prev => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(project.job_no);
|
||||
newSet.delete(projectId);
|
||||
console.log('📦 활성화 프로젝트 업데이트:', { prev: Array.from(prev), new: Array.from(newSet) });
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -10,7 +10,9 @@ const FittingMaterialsView = ({
|
||||
userRequirements,
|
||||
setUserRequirements,
|
||||
purchasedMaterials,
|
||||
onPurchasedMaterialsUpdate,
|
||||
fileId,
|
||||
jobNo,
|
||||
user,
|
||||
onNavigate
|
||||
}) => {
|
||||
@@ -245,16 +247,73 @@ const FittingMaterialsView = ({
|
||||
}
|
||||
}
|
||||
|
||||
// 스케줄 표시 (분리 스케줄 지원)
|
||||
// 스케줄 표시 (분리 스케줄 지원) - 개선된 로직
|
||||
// 레듀싱 자재인지 확인
|
||||
const isReducingFitting = displayType.includes('REDUCING') || displayType.includes('RED') ||
|
||||
description.toUpperCase().includes('RED') ||
|
||||
description.toUpperCase().includes('REDUCING');
|
||||
|
||||
if (hasDifferentSchedules && mainSchedule && redSchedule) {
|
||||
schedule = `${mainSchedule}×${redSchedule}`;
|
||||
} else if (mainSchedule) {
|
||||
} else if (isReducingFitting && mainSchedule && mainSchedule !== 'UNKNOWN') {
|
||||
// 레듀싱 자재는 같은 스케줄이라도 명시적으로 표시
|
||||
schedule = `${mainSchedule}×${mainSchedule}`;
|
||||
} else if (mainSchedule && mainSchedule !== 'UNKNOWN') {
|
||||
schedule = mainSchedule;
|
||||
} else {
|
||||
// Description에서 스케줄 추출
|
||||
const scheduleMatch = description.match(/SCH\s*(\d+[A-Z]*)/i);
|
||||
if (scheduleMatch) {
|
||||
schedule = `SCH ${scheduleMatch[1]}`;
|
||||
// Description에서 스케줄 추출 - 더 강력한 패턴 매칭
|
||||
const schedulePatterns = [
|
||||
/SCH\s*(\d+S?)/i, // SCH 40, SCH 80S
|
||||
/SCHEDULE\s*(\d+S?)/i, // SCHEDULE 40
|
||||
/스케줄\s*(\d+S?)/i, // 스케줄 40
|
||||
/(\d+S?)\s*SCH/i, // 40 SCH (역순)
|
||||
/SCH\.?\s*(\d+S?)/i, // SCH.40
|
||||
/SCH\s*(\d+S?)\s*[xX×]\s*SCH\s*(\d+S?)/i // SCH 40 x SCH 80
|
||||
];
|
||||
|
||||
for (const pattern of schedulePatterns) {
|
||||
const match = description.match(pattern);
|
||||
if (match) {
|
||||
if (match.length > 2) {
|
||||
// 분리 스케줄 패턴 (SCH 40 x SCH 80)
|
||||
schedule = `SCH ${match[1]}×SCH ${match[2]}`;
|
||||
} else {
|
||||
const scheduleNum = match[1];
|
||||
if (isReducingFitting) {
|
||||
// 레듀싱 자재는 같은 스케줄이라도 명시적으로 표시
|
||||
schedule = `SCH ${scheduleNum}×SCH ${scheduleNum}`;
|
||||
} else {
|
||||
schedule = `SCH ${scheduleNum}`;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 여전히 찾지 못했다면 더 넓은 패턴 시도
|
||||
if (schedule === '-') {
|
||||
const broadPatterns = [
|
||||
/\b(\d+)\s*LB/i, // 압력 등급에서 유추
|
||||
/\b(40|80|120|160)\b/i, // 일반적인 스케줄 숫자
|
||||
/\b(10|20|30|40|60|80|100|120|140|160)\b/i // 모든 표준 스케줄
|
||||
];
|
||||
|
||||
for (const pattern of broadPatterns) {
|
||||
const match = description.match(pattern);
|
||||
if (match) {
|
||||
const num = match[1];
|
||||
// 압력 등급이 아닌 경우만 스케줄로 간주
|
||||
if (!description.includes(`${num}LB`)) {
|
||||
if (isReducingFitting) {
|
||||
// 레듀싱 자재는 같은 스케줄이라도 명시적으로 표시
|
||||
schedule = `SCH ${num}×SCH ${num}`;
|
||||
} else {
|
||||
schedule = `SCH ${num}`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,6 +412,36 @@ const FittingMaterialsView = ({
|
||||
}));
|
||||
|
||||
try {
|
||||
// 1. 구매신청 생성
|
||||
const allMaterialIds = selectedMaterialsData.map(m => m.id);
|
||||
const response = await api.post('/purchase-request/create', {
|
||||
file_id: fileId,
|
||||
job_no: jobNo,
|
||||
category: 'FITTING',
|
||||
material_ids: allMaterialIds,
|
||||
materials_data: dataWithRequirements.map(m => ({
|
||||
material_id: m.id,
|
||||
description: m.original_description,
|
||||
category: m.classified_category,
|
||||
size: m.size_inch || m.size_spec,
|
||||
schedule: m.schedule,
|
||||
material_grade: m.material_grade || m.full_material_grade,
|
||||
quantity: m.quantity,
|
||||
unit: m.unit,
|
||||
user_requirement: userRequirements[m.id] || ''
|
||||
}))
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
console.log(`✅ 구매신청 완료: ${response.data.request_no}`);
|
||||
|
||||
// 2. 구매신청된 자재 ID를 purchasedMaterials에 추가
|
||||
if (onPurchasedMaterialsUpdate) {
|
||||
onPurchasedMaterialsUpdate(allMaterialIds);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 서버에 엑셀 파일 저장 요청
|
||||
await api.post('/files/save-excel', {
|
||||
file_id: fileId,
|
||||
category: 'FITTING',
|
||||
@@ -361,21 +450,27 @@ const FittingMaterialsView = ({
|
||||
user_id: user?.id
|
||||
});
|
||||
|
||||
// 4. 클라이언트에서 다운로드
|
||||
exportMaterialsToExcel(dataWithRequirements, excelFileName, {
|
||||
category: 'FITTING',
|
||||
filename: excelFileName,
|
||||
uploadDate: new Date().toLocaleDateString()
|
||||
});
|
||||
|
||||
alert('엑셀 파일이 생성되고 서버에 저장되었습니다.');
|
||||
alert(`구매신청 ${response.data?.request_no || ''}이 생성되고 엑셀 파일이 저장되었습니다.`);
|
||||
} catch (error) {
|
||||
console.error('엑셀 저장 실패:', error);
|
||||
console.error('엑셀 저장 또는 구매신청 실패:', error);
|
||||
// 실패해도 다운로드는 진행
|
||||
exportMaterialsToExcel(dataWithRequirements, excelFileName, {
|
||||
category: 'FITTING',
|
||||
filename: excelFileName,
|
||||
uploadDate: new Date().toLocaleDateString()
|
||||
});
|
||||
alert('엑셀 파일은 다운로드되었지만 구매신청 생성에 실패했습니다.');
|
||||
}
|
||||
|
||||
// 선택 해제
|
||||
setSelectedMaterials(new Set());
|
||||
};
|
||||
|
||||
const filteredMaterials = getFilteredAndSortedMaterials();
|
||||
@@ -506,21 +601,24 @@ const FittingMaterialsView = ({
|
||||
<div style={{
|
||||
background: 'white',
|
||||
borderRadius: '12px',
|
||||
overflow: 'hidden',
|
||||
overflow: 'auto',
|
||||
maxHeight: '600px',
|
||||
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)'
|
||||
}}>
|
||||
{/* 헤더 */}
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '50px 200px 120px 100px 120px 150px 80px 80px 200px',
|
||||
gap: '16px',
|
||||
padding: '16px',
|
||||
background: '#f8fafc',
|
||||
borderBottom: '1px solid #e2e8f0',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600',
|
||||
color: '#374151'
|
||||
}}>
|
||||
<div style={{ minWidth: '1380px' }}>
|
||||
{/* 헤더 */}
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '50px 200px 120px 100px 120px 150px 180px 120px 200px',
|
||||
gap: '16px',
|
||||
padding: '16px',
|
||||
background: '#f8fafc',
|
||||
borderBottom: '1px solid #e2e8f0',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600',
|
||||
color: '#374151',
|
||||
textAlign: 'center'
|
||||
}}>
|
||||
<div>
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -537,13 +635,12 @@ const FittingMaterialsView = ({
|
||||
<div>Pressure</div>
|
||||
<div>Schedule</div>
|
||||
<div>Material Grade</div>
|
||||
<div>Quantity</div>
|
||||
<div>Unit</div>
|
||||
<div>User Requirement</div>
|
||||
<div>User Requirements</div>
|
||||
<div>Purchase Quantity</div>
|
||||
<div>Additional Request</div>
|
||||
</div>
|
||||
|
||||
{/* 데이터 행들 */}
|
||||
<div style={{ maxHeight: '600px', overflowY: 'auto' }}>
|
||||
{/* 데이터 행들 */}
|
||||
{filteredMaterials.map((material, index) => {
|
||||
const info = parseFittingInfo(material);
|
||||
const isSelected = selectedMaterials.has(material.id);
|
||||
@@ -554,12 +651,13 @@ const FittingMaterialsView = ({
|
||||
key={material.id}
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '50px 200px 120px 100px 120px 150px 80px 80px 200px',
|
||||
gridTemplateColumns: '50px 200px 120px 100px 120px 150px 180px 120px 200px',
|
||||
gap: '16px',
|
||||
padding: '16px',
|
||||
borderBottom: index < filteredMaterials.length - 1 ? '1px solid #f1f5f9' : 'none',
|
||||
background: isSelected ? '#eff6ff' : (isPurchased ? '#fef3c7' : 'white'),
|
||||
transition: 'background 0.15s ease'
|
||||
transition: 'background 0.15s ease',
|
||||
textAlign: 'center'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (!isSelected && !isPurchased) {
|
||||
@@ -612,11 +710,17 @@ const FittingMaterialsView = ({
|
||||
<div style={{ fontSize: '14px', color: '#1f2937' }}>
|
||||
{info.grade}
|
||||
</div>
|
||||
<div style={{ fontSize: '14px', color: '#1f2937', fontWeight: '600', textAlign: 'right' }}>
|
||||
{info.quantity}
|
||||
<div style={{
|
||||
fontSize: '14px',
|
||||
color: '#1f2937',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis'
|
||||
}}>
|
||||
{material.user_requirements?.join(', ') || '-'}
|
||||
</div>
|
||||
<div style={{ fontSize: '14px', color: '#6b7280' }}>
|
||||
{info.unit}
|
||||
<div style={{ fontSize: '14px', color: '#1f2937', fontWeight: '600', textAlign: 'right' }}>
|
||||
{info.quantity} {info.unit}
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
@@ -626,7 +730,7 @@ const FittingMaterialsView = ({
|
||||
...userRequirements,
|
||||
[material.id]: e.target.value
|
||||
})}
|
||||
placeholder="Enter requirement..."
|
||||
placeholder="Enter additional request..."
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '6px 8px',
|
||||
|
||||
@@ -10,7 +10,9 @@ const FlangeMaterialsView = ({
|
||||
userRequirements,
|
||||
setUserRequirements,
|
||||
purchasedMaterials,
|
||||
onPurchasedMaterialsUpdate,
|
||||
fileId,
|
||||
jobNo,
|
||||
user,
|
||||
onNavigate
|
||||
}) => {
|
||||
@@ -199,6 +201,34 @@ const FlangeMaterialsView = ({
|
||||
}));
|
||||
|
||||
try {
|
||||
// 1. 구매신청 생성
|
||||
const allMaterialIds = selectedMaterialsData.map(m => m.id);
|
||||
const response = await api.post('/purchase-request/create', {
|
||||
file_id: fileId,
|
||||
job_no: jobNo,
|
||||
category: 'FLANGE',
|
||||
material_ids: allMaterialIds,
|
||||
materials_data: dataWithRequirements.map(m => ({
|
||||
material_id: m.id,
|
||||
description: m.original_description,
|
||||
category: m.classified_category,
|
||||
size: m.size_inch || m.size_spec,
|
||||
schedule: m.schedule,
|
||||
material_grade: m.material_grade || m.full_material_grade,
|
||||
quantity: m.quantity,
|
||||
unit: m.unit,
|
||||
user_requirement: userRequirements[m.id] || ''
|
||||
}))
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
console.log(`✅ 구매신청 완료: ${response.data.request_no}`);
|
||||
if (onPurchasedMaterialsUpdate) {
|
||||
onPurchasedMaterialsUpdate(allMaterialIds);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 서버에 엑셀 파일 저장
|
||||
await api.post('/files/save-excel', {
|
||||
file_id: fileId,
|
||||
category: 'FLANGE',
|
||||
@@ -207,21 +237,25 @@ const FlangeMaterialsView = ({
|
||||
user_id: user?.id
|
||||
});
|
||||
|
||||
// 3. 클라이언트 다운로드
|
||||
exportMaterialsToExcel(dataWithRequirements, excelFileName, {
|
||||
category: 'FLANGE',
|
||||
filename: excelFileName,
|
||||
uploadDate: new Date().toLocaleDateString()
|
||||
});
|
||||
|
||||
alert('엑셀 파일이 생성되고 서버에 저장되었습니다.');
|
||||
alert(`구매신청 ${response.data?.request_no || ''}이 생성되고 엑셀 파일이 저장되었습니다.`);
|
||||
} catch (error) {
|
||||
console.error('엑셀 저장 실패:', error);
|
||||
console.error('엑셀 저장 또는 구매신청 실패:', error);
|
||||
exportMaterialsToExcel(dataWithRequirements, excelFileName, {
|
||||
category: 'FLANGE',
|
||||
filename: excelFileName,
|
||||
uploadDate: new Date().toLocaleDateString()
|
||||
});
|
||||
alert('엑셀 파일은 다운로드되었지만 구매신청 생성에 실패했습니다.');
|
||||
}
|
||||
|
||||
setSelectedMaterials(new Set());
|
||||
};
|
||||
|
||||
const filteredMaterials = getFilteredAndSortedMaterials();
|
||||
|
||||
@@ -10,7 +10,9 @@ const PipeMaterialsView = ({
|
||||
userRequirements,
|
||||
setUserRequirements,
|
||||
purchasedMaterials,
|
||||
onPurchasedMaterialsUpdate,
|
||||
fileId,
|
||||
jobNo,
|
||||
user,
|
||||
onNavigate
|
||||
}) => {
|
||||
@@ -177,7 +179,36 @@ const PipeMaterialsView = ({
|
||||
}));
|
||||
|
||||
try {
|
||||
// 서버에 엑셀 파일 저장 요청
|
||||
// 1. 구매신청 생성
|
||||
const allMaterialIds = selectedMaterialsData.map(m => m.id);
|
||||
const response = await api.post('/purchase-request/create', {
|
||||
file_id: fileId,
|
||||
job_no: jobNo,
|
||||
category: 'PIPE',
|
||||
material_ids: allMaterialIds,
|
||||
materials_data: dataWithRequirements.map(m => ({
|
||||
material_id: m.id,
|
||||
description: m.original_description,
|
||||
category: m.classified_category,
|
||||
size: m.size_inch || m.size_spec,
|
||||
schedule: m.schedule,
|
||||
material_grade: m.material_grade || m.full_material_grade,
|
||||
quantity: m.quantity,
|
||||
unit: m.unit,
|
||||
user_requirement: userRequirements[m.id] || ''
|
||||
}))
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
console.log(`✅ 구매신청 완료: ${response.data.request_no}`);
|
||||
|
||||
// 2. 구매신청된 자재 ID를 purchasedMaterials에 추가
|
||||
if (onPurchasedMaterialsUpdate) {
|
||||
onPurchasedMaterialsUpdate(allMaterialIds);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 서버에 엑셀 파일 저장 요청
|
||||
await api.post('/files/save-excel', {
|
||||
file_id: fileId,
|
||||
category: 'PIPE',
|
||||
@@ -186,23 +217,27 @@ const PipeMaterialsView = ({
|
||||
user_id: user?.id
|
||||
});
|
||||
|
||||
// 클라이언트에서 다운로드
|
||||
// 4. 클라이언트에서 다운로드
|
||||
exportMaterialsToExcel(dataWithRequirements, excelFileName, {
|
||||
category: 'PIPE',
|
||||
filename: excelFileName,
|
||||
uploadDate: new Date().toLocaleDateString()
|
||||
});
|
||||
|
||||
alert('엑셀 파일이 생성되고 서버에 저장되었습니다.');
|
||||
alert(`구매신청 ${response.data?.request_no || ''}이 생성되고 엑셀 파일이 저장되었습니다.`);
|
||||
} catch (error) {
|
||||
console.error('엑셀 저장 실패:', error);
|
||||
console.error('엑셀 저장 또는 구매신청 실패:', error);
|
||||
// 실패해도 다운로드는 진행
|
||||
exportMaterialsToExcel(dataWithRequirements, excelFileName, {
|
||||
category: 'PIPE',
|
||||
filename: excelFileName,
|
||||
uploadDate: new Date().toLocaleDateString()
|
||||
});
|
||||
alert('엑셀 파일은 다운로드되었지만 구매신청 생성에 실패했습니다.');
|
||||
}
|
||||
|
||||
// 선택 해제
|
||||
setSelectedMaterials(new Set());
|
||||
};
|
||||
|
||||
const filteredMaterials = getFilteredAndSortedMaterials();
|
||||
|
||||
@@ -10,7 +10,9 @@ const ValveMaterialsView = ({
|
||||
userRequirements,
|
||||
setUserRequirements,
|
||||
purchasedMaterials,
|
||||
onPurchasedMaterialsUpdate,
|
||||
fileId,
|
||||
jobNo,
|
||||
user
|
||||
}) => {
|
||||
const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' });
|
||||
|
||||
@@ -167,7 +167,16 @@ const BOMManagementPage = ({
|
||||
userRequirements,
|
||||
setUserRequirements,
|
||||
purchasedMaterials,
|
||||
onPurchasedMaterialsUpdate: (materialIds) => {
|
||||
setPurchasedMaterials(prev => {
|
||||
const newSet = new Set(prev);
|
||||
materialIds.forEach(id => newSet.add(id));
|
||||
console.log(`📦 구매신청 자재 추가: 기존 ${prev.size}개 → 신규 ${newSet.size}개`);
|
||||
return newSet;
|
||||
});
|
||||
},
|
||||
fileId,
|
||||
jobNo,
|
||||
user,
|
||||
onNavigate
|
||||
};
|
||||
|
||||
@@ -43,9 +43,20 @@ const DashboardPage = ({
|
||||
|
||||
// 프로젝트 비활성화
|
||||
const handleDeactivateProject = (project) => {
|
||||
if (window.confirm(`"${project.job_name || project.job_no}" 프로젝트를 비활성화하시겠습니까?`)) {
|
||||
setInactiveProjects(prev => new Set([...prev, project.job_no]));
|
||||
if (selectedProject?.job_no === project.job_no) {
|
||||
const projectId = project.job_no || project.official_project_code || project.id;
|
||||
const projectName = project.job_name || project.project_name || projectId;
|
||||
|
||||
console.log('🔍 비활성화 요청:', { project, projectId, projectName });
|
||||
|
||||
if (window.confirm(`"${projectName}" 프로젝트를 비활성화하시겠습니까?`)) {
|
||||
setInactiveProjects(prev => {
|
||||
const newSet = new Set([...prev, projectId]);
|
||||
console.log('📦 비활성화 프로젝트 업데이트:', { prev: Array.from(prev), new: Array.from(newSet) });
|
||||
return newSet;
|
||||
});
|
||||
|
||||
const selectedProjectId = selectedProject?.job_no || selectedProject?.official_project_code || selectedProject?.id;
|
||||
if (selectedProjectId === projectId) {
|
||||
setSelectedProject(null);
|
||||
}
|
||||
setShowProjectDropdown(false);
|
||||
@@ -323,7 +334,10 @@ const DashboardPage = ({
|
||||
</div>
|
||||
) : (
|
||||
projects
|
||||
.filter(project => !inactiveProjects.has(project.job_no))
|
||||
.filter(project => {
|
||||
const projectId = project.job_no || project.official_project_code || project.id;
|
||||
return !inactiveProjects.has(projectId);
|
||||
})
|
||||
.map((project) => (
|
||||
<div
|
||||
key={project.job_no}
|
||||
|
||||
@@ -180,49 +180,112 @@ const formatMaterialForExcel = (material, includeComparison = false) => {
|
||||
}
|
||||
}
|
||||
} else if (category === 'FITTING') {
|
||||
// 피팅 상세 타입 표시 (OLET 등 풀네임)
|
||||
// 피팅 상세 타입 표시 - 프론트엔드 표시와 동일한 로직 사용
|
||||
const fittingDetails = material.fitting_details || {};
|
||||
const fittingType = fittingDetails.fitting_type || '';
|
||||
const fittingSubtype = fittingDetails.fitting_subtype || '';
|
||||
const classificationDetails = material.classification_details || {};
|
||||
const fittingTypeInfo = classificationDetails.fitting_type || {};
|
||||
|
||||
const fittingType = fittingTypeInfo.type || fittingDetails.fitting_type || '';
|
||||
const fittingSubtype = fittingTypeInfo.subtype || fittingDetails.fitting_subtype || '';
|
||||
|
||||
// 프론트엔드와 동일한 displayType 로직 사용
|
||||
let displayType = '';
|
||||
|
||||
if (fittingType === 'OLET') {
|
||||
// OLET 풀네임 표시
|
||||
switch (fittingSubtype) {
|
||||
case 'SOCKOLET':
|
||||
itemName = 'SOCK-O-LET';
|
||||
displayType = 'SOCK-O-LET';
|
||||
break;
|
||||
case 'WELDOLET':
|
||||
itemName = 'WELD-O-LET';
|
||||
displayType = 'WELD-O-LET';
|
||||
break;
|
||||
case 'ELLOLET':
|
||||
itemName = 'ELL-O-LET';
|
||||
displayType = 'ELL-O-LET';
|
||||
break;
|
||||
case 'THREADOLET':
|
||||
itemName = 'THREAD-O-LET';
|
||||
displayType = 'THREAD-O-LET';
|
||||
break;
|
||||
case 'ELBOLET':
|
||||
itemName = 'ELB-O-LET';
|
||||
displayType = 'ELB-O-LET';
|
||||
break;
|
||||
case 'NIPOLET':
|
||||
itemName = 'NIP-O-LET';
|
||||
displayType = 'NIP-O-LET';
|
||||
break;
|
||||
case 'COUPOLET':
|
||||
itemName = 'COUP-O-LET';
|
||||
displayType = 'COUP-O-LET';
|
||||
break;
|
||||
default:
|
||||
itemName = 'OLET';
|
||||
// Description에서 직접 추출
|
||||
const descUpper = cleanDescription.toUpperCase();
|
||||
if (descUpper.includes('SOCK-O-LET') || descUpper.includes('SOCKOLET')) {
|
||||
displayType = 'SOCK-O-LET';
|
||||
} else if (descUpper.includes('WELD-O-LET') || descUpper.includes('WELDOLET')) {
|
||||
displayType = 'WELD-O-LET';
|
||||
} else if (descUpper.includes('ELL-O-LET') || descUpper.includes('ELLOLET')) {
|
||||
displayType = 'ELL-O-LET';
|
||||
} else if (descUpper.includes('THREAD-O-LET') || descUpper.includes('THREADOLET')) {
|
||||
displayType = 'THREAD-O-LET';
|
||||
} else if (descUpper.includes('ELB-O-LET') || descUpper.includes('ELBOLET')) {
|
||||
displayType = 'ELB-O-LET';
|
||||
} else if (descUpper.includes('NIP-O-LET') || descUpper.includes('NIPOLET')) {
|
||||
displayType = 'NIP-O-LET';
|
||||
} else if (descUpper.includes('COUP-O-LET') || descUpper.includes('COUPOLET')) {
|
||||
displayType = 'COUP-O-LET';
|
||||
} else {
|
||||
displayType = 'OLET';
|
||||
}
|
||||
}
|
||||
} else if (fittingType === 'TEE' && fittingSubtype === 'REDUCING') {
|
||||
displayType = 'TEE REDUCING';
|
||||
} else if (fittingType === 'REDUCER' && fittingSubtype === 'CONCENTRIC') {
|
||||
displayType = 'REDUCER CONC';
|
||||
} else if (fittingType === 'REDUCER' && fittingSubtype === 'ECCENTRIC') {
|
||||
displayType = 'REDUCER ECC';
|
||||
} else if (cleanDescription.toUpperCase().includes('TEE RED')) {
|
||||
displayType = 'TEE REDUCING';
|
||||
} else if (cleanDescription.toUpperCase().includes('RED CONC')) {
|
||||
displayType = 'REDUCER CONC';
|
||||
} else if (cleanDescription.toUpperCase().includes('RED ECC')) {
|
||||
displayType = 'REDUCER ECC';
|
||||
} else if (cleanDescription.toUpperCase().includes('CAP')) {
|
||||
if (cleanDescription.includes('NPT(F)')) {
|
||||
displayType = 'CAP NPT(F)';
|
||||
} else if (cleanDescription.includes('SW')) {
|
||||
displayType = 'CAP SW';
|
||||
} else if (cleanDescription.includes('BW')) {
|
||||
displayType = 'CAP BW';
|
||||
} else {
|
||||
displayType = 'CAP';
|
||||
}
|
||||
} else if (cleanDescription.toUpperCase().includes('PLUG')) {
|
||||
if (cleanDescription.toUpperCase().includes('HEX')) {
|
||||
if (cleanDescription.includes('NPT(M)')) {
|
||||
displayType = 'HEX PLUG NPT(M)';
|
||||
} else {
|
||||
displayType = 'HEX PLUG';
|
||||
}
|
||||
} else if (cleanDescription.includes('NPT(M)')) {
|
||||
displayType = 'PLUG NPT(M)';
|
||||
} else if (cleanDescription.includes('NPT')) {
|
||||
displayType = 'PLUG NPT';
|
||||
} else {
|
||||
displayType = 'PLUG';
|
||||
}
|
||||
} else if (fittingType === 'NIPPLE') {
|
||||
const length = fittingDetails.length_mm || fittingDetails.avg_length_mm;
|
||||
let nippleType = 'NIPPLE';
|
||||
if (length) nippleType += ` ${length}mm`;
|
||||
displayType = nippleType;
|
||||
} else if (fittingType === 'ELBOW') {
|
||||
// 엘보 상세 정보 표시 (각도, 반경, 연결방식)
|
||||
// 엘보 상세 정보 표시
|
||||
let elbowDetails = [];
|
||||
|
||||
// 각도 정보
|
||||
if (fittingSubtype.includes('90DEG') || cleanDescription.includes('90')) {
|
||||
elbowDetails.push('90도');
|
||||
elbowDetails.push('90°');
|
||||
} else if (fittingSubtype.includes('45DEG') || cleanDescription.includes('45')) {
|
||||
elbowDetails.push('45도');
|
||||
} else {
|
||||
elbowDetails.push('90도'); // 기본값
|
||||
elbowDetails.push('45°');
|
||||
}
|
||||
|
||||
// 반경 정보
|
||||
@@ -232,25 +295,12 @@ const formatMaterialForExcel = (material, includeComparison = false) => {
|
||||
elbowDetails.push('SR');
|
||||
}
|
||||
|
||||
// 연결 방식
|
||||
if (cleanDescription.includes('SW')) {
|
||||
elbowDetails.push('SW');
|
||||
} else if (cleanDescription.includes('BW')) {
|
||||
elbowDetails.push('BW');
|
||||
}
|
||||
|
||||
itemName = `엘보 ${elbowDetails.join(' ')}`.trim();
|
||||
} else if (fittingType === 'TEE') {
|
||||
// 티 타입 표시
|
||||
const teeType = fittingSubtype === 'EQUAL' ? '등경' : fittingSubtype === 'REDUCING' ? '축소' : '';
|
||||
itemName = `티 ${teeType}`.trim();
|
||||
} else if (fittingType === 'REDUCER') {
|
||||
// 리듀서 타입 표시
|
||||
const reducerType = fittingSubtype === 'CONCENTRIC' ? '동심' : fittingSubtype === 'ECCENTRIC' ? '편심' : '';
|
||||
itemName = `리듀서 ${reducerType}`.trim();
|
||||
displayType = elbowDetails.length > 0 ? `ELBOW ${elbowDetails.join(' ')}` : 'ELBOW';
|
||||
} else {
|
||||
itemName = fittingType || 'FITTING';
|
||||
displayType = fittingType || 'FITTING';
|
||||
}
|
||||
|
||||
itemName = displayType;
|
||||
} else if (category === 'FLANGE') {
|
||||
// 플랜지 상세 타입 표시
|
||||
const flangeDetails = material.flange_details || {};
|
||||
@@ -674,17 +724,17 @@ const formatMaterialForExcel = (material, includeComparison = false) => {
|
||||
base['관리항목3'] = ''; // N열
|
||||
base['관리항목4'] = ''; // O열
|
||||
} else if (category === 'FITTING') {
|
||||
// 피팅 전용 컬럼 (F~O) - 타입 제거, 품목명에 포함됨
|
||||
// 피팅 전용 컬럼 (F~O) - 새로운 구조
|
||||
base['크기'] = material.size_spec || '-'; // 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['스케줄'] = schedule; // H열
|
||||
base['재질'] = grade; // I열
|
||||
base['사용자요구'] = material.user_requirements?.join(', ') || ''; // J열 (분류기에서 추출)
|
||||
base['추가요청사항'] = material.user_requirement || ''; // K열 (사용자 입력)
|
||||
base['관리항목1'] = ''; // L열
|
||||
base['관리항목2'] = ''; // M열
|
||||
base['관리항목3'] = ''; // N열
|
||||
base['관리항목4'] = ''; // O열
|
||||
} else if (category === 'FLANGE') {
|
||||
// 플랜지 타입 풀네임 매핑 (영어)
|
||||
const flangeTypeMap = {
|
||||
|
||||
Reference in New Issue
Block a user