볼트 분류 기능 대폭 개선
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Failing after 2m11s

- 실제 볼트 사이즈 추출: 설명의 첫 번째 숫자를 실제 볼트 직경으로 사용
- 분수 표기 변환: 0.625 → 5/8, 0.75 → 3/4 등 현장 친화적 표기
- 특수 용도 볼트 분류: PSV(압력안전밸브), LT(저온용), CK(체크밸브), ORI(오리피스)
- 표면처리 정보 추출: ELEC.GALV, HOT DIP GALV 등 코팅 정보
- 복합 재질 규격 파싱: ASTM A193/A194 GR B7/2H 정확 분류
- 특수 용도별 색상 구분: PSV 빨강, LT 주황, CK 파랑, ORI 보라
- 프론트엔드 표시 개선: 분수 사이즈, 특수 용도 현황 별도 섹션
- inch 기호 제거: 깔끔한 분수 표시로 현장 가독성 향상
This commit is contained in:
Hyungi Ahn
2025-07-29 14:34:33 +09:00
parent fc925974bb
commit 48f8f634d1
9 changed files with 574 additions and 35 deletions

View File

@@ -363,18 +363,53 @@ const MaterialsPage = () => {
isLength: false
};
} else if (category === 'BOLT') {
// BOLT: 타입 + 재질 + 사이즈 + 길이
// BOLT: 타입 + 재질 + 사이즈 + 길이 (분수 표기 및 특수 용도 포함)
const material_spec = material.material_grade || '';
const main_nom = material.main_nom || '';
const bolt_type = material.bolt_details?.bolt_type || 'BOLT';
const material_standard = material.bolt_details?.material_standard || '';
const material_grade = material.bolt_details?.material_grade || '';
const thread_type = material.bolt_details?.thread_type || '';
const diameter = material.bolt_details?.diameter || main_nom;
// 실제 볼트 직경 (백엔드에서 추출된 값 우선 사용)
const diameter = material.bolt_details?.nominal_size ||
material.bolt_details?.diameter ||
main_nom;
const length = material.bolt_details?.length || '';
const pressure_rating = material.bolt_details?.pressure_rating || '';
const coating_type = material.bolt_details?.coating_type || '';
// 분수 사이즈 변환 (0.625 → 5/8, " 기호 제거)
const convertToFraction = (decimal) => {
const fractions = {
'0.125': '1/8', '0.1875': '3/16', '0.25': '1/4', '0.3125': '5/16',
'0.375': '3/8', '0.4375': '7/16', '0.5': '1/2', '0.5625': '9/16',
'0.625': '5/8', '0.6875': '11/16', '0.75': '3/4', '0.8125': '13/16',
'0.875': '7/8', '0.9375': '15/16', '1.0': '1', '1.125': '1-1/8',
'1.25': '1-1/4', '1.375': '1-3/8', '1.5': '1-1/2', '1.625': '1-5/8',
'1.75': '1-3/4', '1.875': '1-7/8', '2.0': '2'
};
return fractions[decimal] || decimal;
};
const size_fraction = convertToFraction(diameter);
const display_size = size_fraction !== diameter ? size_fraction : diameter;
// 특수 용도 확인 (PSV, LT, CK, ORI)
const description = material.original_description?.toUpperCase() || '';
const special_applications = [];
if (description.includes('PSV') || description.includes('PRESSURE SAFETY VALVE')) {
special_applications.push('PSV');
}
if (description.includes('LT') || description.includes('LOW TEMP') || description.includes('저온용')) {
special_applications.push('LT');
}
if (description.includes('CK') || description.includes('CHECK VALVE')) {
special_applications.push('CK');
}
if (description.includes('ORI') || description.includes('ORIFICE') || description.includes('오리피스')) {
special_applications.push('ORI');
}
// 볼트 스펙 생성
const bolt_spec_parts = [];
@@ -393,9 +428,14 @@ const MaterialsPage = () => {
bolt_spec_parts.push(material_spec);
}
// 나사 규격 (M12, 1/2" 등)
// 나사 규격 (분수 표기로)
if (diameter) {
bolt_spec_parts.push(diameter);
bolt_spec_parts.push(display_size);
}
// 특수 용도 표시
if (special_applications.length > 0) {
bolt_spec_parts.push(`[${special_applications.join(', ')}용]`);
}
// 코팅 타입 (ELECTRO_GALVANIZED 등)
@@ -405,7 +445,8 @@ const MaterialsPage = () => {
const full_bolt_spec = bolt_spec_parts.join(', ');
specKey = `${category}|${full_bolt_spec}|${diameter}|${length}|${coating_type}|${pressure_rating}`;
// 특수 용도와 관계없이 사이즈+길이로 합산 (구매는 동일하므로)
specKey = `${category}|${full_bolt_spec}|${diameter}|${length}|${coating_type}`;
specData = {
category: 'BOLT',
bolt_type,
@@ -414,10 +455,13 @@ const MaterialsPage = () => {
material_standard,
material_grade,
diameter,
size_fraction,
display_size,
length,
coating_type,
pressure_rating,
size_display: diameter,
special_applications,
size_display: display_size,
main_nom: diameter,
unit: 'EA',
isLength: false
@@ -840,8 +884,8 @@ const MaterialsPage = () => {
<TableCell><strong>재질</strong></TableCell>
<TableCell><strong>사이즈</strong></TableCell>
<TableCell><strong>길이</strong></TableCell>
<TableCell><strong>특수용도</strong></TableCell>
<TableCell><strong>코팅</strong></TableCell>
<TableCell><strong>압력등급</strong></TableCell>
<TableCell align="right"><strong>수량</strong></TableCell>
</>
)}
@@ -966,8 +1010,8 @@ const MaterialsPage = () => {
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{spec.diameter ? (spec.diameter.includes('"') ? spec.diameter : spec.diameter.replace('0.5', '1/2"').replace('0.75', '3/4"').replace('1.0', '1"').replace('1.5', '1 1/2"')) : 'Unknown'}
<Typography variant="body2" sx={{ fontWeight: 'bold', color: 'primary.main' }}>
{spec.display_size || spec.size_display || 'Unknown'}
</Typography>
</TableCell>
<TableCell>
@@ -976,13 +1020,32 @@ const MaterialsPage = () => {
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{spec.coating_type ? spec.coating_type.replace('_', ' ') : 'N/A'}
</Typography>
{spec.special_applications && spec.special_applications.length > 0 ? (
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap' }}>
{spec.special_applications.map((app) => (
<Chip
key={app}
label={app}
size="small"
color={
app === 'PSV' ? 'error' :
app === 'LT' ? 'warning' :
app === 'CK' ? 'info' :
app === 'ORI' ? 'secondary' : 'default'
}
variant="outlined"
/>
))}
</Box>
) : (
<Typography variant="body2" color="success.main" sx={{ fontStyle: 'italic' }}>
일반
</Typography>
)}
</TableCell>
<TableCell>
<Typography variant="body2">
{spec.pressure_rating || 'N/A'}
{spec.coating_type ? spec.coating_type.replace('_', ' ') : 'N/A'}
</Typography>
</TableCell>
<TableCell align="right">

View File

@@ -159,6 +159,60 @@ const PurchaseConfirmationPage = () => {
);
};
const formatBoltInfo = (item) => {
if (item.category !== 'BOLT') return null;
// 특수 용도 볼트 정보 (백엔드에서 제공되어야 함)
const specialApplications = item.special_applications || {};
const psvCount = specialApplications.PSV || 0;
const ltCount = specialApplications.LT || 0;
const ckCount = specialApplications.CK || 0;
const oriCount = specialApplications.ORI || 0;
return (
<Box sx={{ mt: 1 }}>
<Typography variant="caption" color="textSecondary">
분수 사이즈: {(item.size_fraction || item.size_spec || '').replace(/"/g, '')} |
표면처리: {item.surface_treatment || '없음'}
</Typography>
{/* 특수 용도 볼트 정보 */}
<Box sx={{ mt: 1, p: 1, bgcolor: 'info.50', borderRadius: 1 }}>
<Typography variant="caption" fontWeight="bold" color="info.main">
특수 용도 볼트 현황:
</Typography>
<Grid container spacing={1} sx={{ mt: 0.5 }}>
<Grid item xs={3}>
<Typography variant="caption" color={psvCount > 0 ? "error.main" : "textSecondary"}>
PSV용: {psvCount}
</Typography>
</Grid>
<Grid item xs={3}>
<Typography variant="caption" color={ltCount > 0 ? "warning.main" : "textSecondary"}>
저온용: {ltCount}
</Typography>
</Grid>
<Grid item xs={3}>
<Typography variant="caption" color={ckCount > 0 ? "info.main" : "textSecondary"}>
체크밸브용: {ckCount}
</Typography>
</Grid>
<Grid item xs={3}>
<Typography variant="caption" color={oriCount > 0 ? "secondary.main" : "textSecondary"}>
오리피스용: {oriCount}
</Typography>
</Grid>
</Grid>
{(psvCount + ltCount + ckCount + oriCount) === 0 && (
<Typography variant="caption" color="success.main" sx={{ fontStyle: 'italic' }}>
특수 용도 볼트 없음 (일반 볼트만 포함)
</Typography>
)}
</Box>
</Box>
);
};
return (
<Box sx={{ p: 3 }}>
{/* 헤더 */}
@@ -235,6 +289,7 @@ const PurchaseConfirmationPage = () => {
{item.bom_quantity} {item.unit}
</Typography>
{formatPipeInfo(item)}
{formatBoltInfo(item)}
</Grid>
{/* 구매 수량 */}