파이프 길이 계산 및 엑셀 내보내기 버그 수정
- 자재 비교에서 파이프 길이 합산 로직 수정 - 프론트엔드에서 혼란스러운 '평균단위' 표시 제거 - 파이프 변경사항에 실제 이전/현재 총길이 표시 - 엑셀 내보내기에서 '초기화되지 않은 변수' 오류 수정 - 리비전 비교에서 파이프 길이 변화 계산 개선
This commit is contained in:
@@ -28,11 +28,13 @@ import {
|
||||
import {
|
||||
ArrowBack,
|
||||
Refresh,
|
||||
History
|
||||
History,
|
||||
Download
|
||||
} from '@mui/icons-material';
|
||||
|
||||
import MaterialComparisonResult from '../components/MaterialComparisonResult';
|
||||
import { compareMaterialRevisions, confirmMaterialPurchase } from '../api';
|
||||
import { compareMaterialRevisions, confirmMaterialPurchase, api } from '../api';
|
||||
import { exportComparisonToExcel } from '../utils/excelExport';
|
||||
|
||||
const MaterialComparisonPage = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -69,6 +71,20 @@ const MaterialComparisonPage = () => {
|
||||
previousRevision,
|
||||
filename
|
||||
});
|
||||
|
||||
// 🚨 테스트: MaterialsPage API 직접 호출해서 길이 정보 확인
|
||||
try {
|
||||
const testResult = await api.get('/files/materials', {
|
||||
params: { job_no: jobNo, revision: currentRevision, limit: 10 }
|
||||
});
|
||||
const pipeData = testResult.data.materials?.filter(m => m.classified_category === 'PIPE');
|
||||
console.log('🧪 MaterialsPage API 테스트 (길이 있는지 확인):', pipeData);
|
||||
if (pipeData && pipeData.length > 0) {
|
||||
console.log('🧪 첫 번째 파이프 상세:', JSON.stringify(pipeData[0], null, 2));
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('🧪 MaterialsPage API 테스트 실패:', e);
|
||||
}
|
||||
|
||||
const result = await compareMaterialRevisions(
|
||||
jobNo,
|
||||
@@ -78,6 +94,7 @@ const MaterialComparisonPage = () => {
|
||||
);
|
||||
|
||||
console.log('✅ 비교 결과 성공:', result);
|
||||
console.log('🔍 전체 데이터 구조:', JSON.stringify(result.data || result, null, 2));
|
||||
setComparisonResult(result.data || result);
|
||||
|
||||
} catch (err) {
|
||||
@@ -135,6 +152,24 @@ const MaterialComparisonPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleExportToExcel = () => {
|
||||
if (!comparisonResult) {
|
||||
alert('내보낼 비교 데이터가 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
const additionalInfo = {
|
||||
jobNo: jobNo,
|
||||
currentRevision: currentRevision,
|
||||
previousRevision: previousRevision,
|
||||
filename: filename
|
||||
};
|
||||
|
||||
const baseFilename = `리비전비교_${jobNo}_${currentRevision}_vs_${previousRevision}`;
|
||||
|
||||
exportComparisonToExcel(comparisonResult, baseFilename, additionalInfo);
|
||||
};
|
||||
|
||||
const renderComparisonResults = () => {
|
||||
const { summary, new_items = [], modified_items = [], removed_items = [] } = comparisonResult;
|
||||
|
||||
@@ -218,7 +253,7 @@ const MaterialComparisonPage = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const renderMaterialTable = (items, type) => {
|
||||
const renderMaterialTable = (items, type) => {
|
||||
if (items.length === 0) {
|
||||
return (
|
||||
<Alert severity="info">
|
||||
@@ -229,6 +264,8 @@ const MaterialComparisonPage = () => {
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`🔍 ${type} 테이블 렌더링:`, items); // 디버깅용
|
||||
|
||||
return (
|
||||
<TableContainer component={Paper} variant="outlined">
|
||||
<Table size="small">
|
||||
@@ -247,46 +284,108 @@ const MaterialComparisonPage = () => {
|
||||
)}
|
||||
{type !== 'modified' && <TableCell align="center">수량</TableCell>}
|
||||
<TableCell>단위</TableCell>
|
||||
<TableCell>길이(mm)</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{items.map((item, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell>
|
||||
<Chip
|
||||
label={item.category}
|
||||
size="small"
|
||||
color={type === 'new' ? 'primary' : type === 'modified' ? 'warning' : 'error'}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>{item.description}</TableCell>
|
||||
<TableCell>{item.size_spec || '-'}</TableCell>
|
||||
<TableCell>{item.material_grade || '-'}</TableCell>
|
||||
{type === 'modified' && (
|
||||
<>
|
||||
<TableCell align="center">{item.previous_quantity}</TableCell>
|
||||
<TableCell align="center">{item.current_quantity}</TableCell>
|
||||
<TableCell align="center">
|
||||
{items.map((item, index) => {
|
||||
console.log(`🔍 항목 ${index}:`, item); // 각 항목 확인
|
||||
|
||||
// 파이프인 경우 길이 정보 표시
|
||||
console.log(`🔧 길이 확인 - ${item.category}:`, item.pipe_details); // 디버깅
|
||||
console.log(`🔧 전체 아이템:`, item); // 전체 구조 확인
|
||||
|
||||
let lengthInfo = '-';
|
||||
if (item.category === 'PIPE' && item.pipe_details?.length_mm && item.pipe_details.length_mm > 0) {
|
||||
const avgUnitLength = item.pipe_details.length_mm;
|
||||
const currentTotalLength = item.pipe_details.total_length_mm || (item.quantity || 0) * avgUnitLength;
|
||||
|
||||
if (type === 'modified') {
|
||||
// 변경된 파이프: 백엔드에서 계산된 실제 길이 사용
|
||||
let prevTotalLength, lengthChange;
|
||||
|
||||
if (item.previous_pipe_details && item.previous_pipe_details.total_length_mm) {
|
||||
// 백엔드에서 실제 이전 총길이를 제공한 경우
|
||||
prevTotalLength = item.previous_pipe_details.total_length_mm;
|
||||
lengthChange = currentTotalLength - prevTotalLength;
|
||||
} else {
|
||||
// 백업: 비율 계산
|
||||
const prevRatio = (item.previous_quantity || 0) / (item.current_quantity || item.quantity || 1);
|
||||
prevTotalLength = currentTotalLength * prevRatio;
|
||||
lengthChange = currentTotalLength - prevTotalLength;
|
||||
}
|
||||
|
||||
lengthInfo = (
|
||||
<Box>
|
||||
<Typography variant="body2">
|
||||
이전: {Math.round(prevTotalLength).toLocaleString()}mm → 현재: {Math.round(currentTotalLength).toLocaleString()}mm
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight="bold"
|
||||
color={item.quantity_change > 0 ? 'success.main' : 'error.main'}
|
||||
fontWeight="bold"
|
||||
color={lengthChange > 0 ? 'success.main' : lengthChange < 0 ? 'error.main' : 'text.primary'}
|
||||
>
|
||||
{item.quantity_change > 0 ? '+' : ''}{item.quantity_change}
|
||||
변화: {lengthChange > 0 ? '+' : ''}{Math.round(lengthChange).toLocaleString()}mm
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
} else {
|
||||
// 신규/삭제된 파이프: 실제 총길이 사용
|
||||
lengthInfo = (
|
||||
<Box>
|
||||
<Typography variant="body2" fontWeight="bold">
|
||||
총 길이: {Math.round(currentTotalLength).toLocaleString()}mm
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
} else if (item.category === 'PIPE') {
|
||||
lengthInfo = '길이 정보 없음';
|
||||
}
|
||||
|
||||
return (
|
||||
<TableRow key={index}>
|
||||
<TableCell>
|
||||
<Chip
|
||||
label={item.category}
|
||||
size="small"
|
||||
color={type === 'new' ? 'primary' : type === 'modified' ? 'warning' : 'error'}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>{item.description}</TableCell>
|
||||
<TableCell>{item.size_spec || '-'}</TableCell>
|
||||
<TableCell>{item.material_grade || '-'}</TableCell>
|
||||
{type === 'modified' && (
|
||||
<>
|
||||
<TableCell align="center">{item.previous_quantity}</TableCell>
|
||||
<TableCell align="center">{item.current_quantity}</TableCell>
|
||||
<TableCell align="center">
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight="bold"
|
||||
color={item.quantity_change > 0 ? 'success.main' : 'error.main'}
|
||||
>
|
||||
{item.quantity_change > 0 ? '+' : ''}{item.quantity_change}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</>
|
||||
)}
|
||||
{type !== 'modified' && (
|
||||
<TableCell align="center">
|
||||
<Typography variant="body2" fontWeight="bold">
|
||||
{item.quantity}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</>
|
||||
)}
|
||||
{type !== 'modified' && (
|
||||
)}
|
||||
<TableCell>{item.unit || 'EA'}</TableCell>
|
||||
<TableCell align="center">
|
||||
<Typography variant="body2" fontWeight="bold">
|
||||
{item.quantity}
|
||||
<Typography variant="body2" color={lengthInfo !== '-' ? 'primary.main' : 'text.secondary'}>
|
||||
{lengthInfo}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell>{item.unit || 'EA'}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
@@ -341,6 +440,15 @@ const MaterialComparisonPage = () => {
|
||||
>
|
||||
새로고침
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
startIcon={<Download />}
|
||||
onClick={handleExportToExcel}
|
||||
disabled={!comparisonResult}
|
||||
>
|
||||
엑셀 내보내기
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<ArrowBack />}
|
||||
|
||||
@@ -25,8 +25,9 @@ import {
|
||||
} from '@mui/material';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import ShoppingCart from '@mui/icons-material/ShoppingCart';
|
||||
import { Compare as CompareIcon } from '@mui/icons-material';
|
||||
import { Compare as CompareIcon, Download } from '@mui/icons-material';
|
||||
import { api, fetchFiles } from '../api';
|
||||
import { exportMaterialsToExcel } from '../utils/excelExport';
|
||||
|
||||
const MaterialsPage = () => {
|
||||
const [materials, setMaterials] = useState([]);
|
||||
@@ -580,6 +581,25 @@ const MaterialsPage = () => {
|
||||
return colors[category] || 'default';
|
||||
};
|
||||
|
||||
// 엑셀 내보내기 함수
|
||||
const handleExportToExcel = () => {
|
||||
if (materials.length === 0) {
|
||||
alert('내보낼 자재 데이터가 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
const additionalInfo = {
|
||||
filename: fileName,
|
||||
jobNo: jobNo,
|
||||
revision: currentRevision,
|
||||
uploadDate: new Date().toLocaleDateString()
|
||||
};
|
||||
|
||||
const baseFilename = `자재목록_${jobNo}_${currentRevision}`;
|
||||
|
||||
exportMaterialsToExcel(materials, baseFilename, additionalInfo);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box display="flex" justifyContent="center" alignItems="center" minHeight="400px">
|
||||
@@ -645,6 +665,16 @@ const MaterialsPage = () => {
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
startIcon={<Download />}
|
||||
onClick={handleExportToExcel}
|
||||
disabled={materials.length === 0}
|
||||
>
|
||||
엑셀 내보내기
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
color="success"
|
||||
|
||||
Reference in New Issue
Block a user