자재별 구매 수량 계산 시스템 구현

- 파이프: 6,000mm 단위 + 절단여유분 2mm/조각 계산
- 볼트/너트: +5% 후 4의 배수로 올림
- 가스켓: 5의 배수로 올림
- 피팅/계기/밸브: BOM 수량 그대로
- MaterialsPage에 '필요 수량' 칼럼 추가
- 엑셀 내보내기에 구매 수량 정보 포함
- 리비전 비교시 구매 수량 변화량도 계산
This commit is contained in:
Hyungi Ahn
2025-07-23 10:41:50 +09:00
parent 5fa0ac4202
commit 905344681f
4 changed files with 235 additions and 1 deletions

View File

@@ -0,0 +1,151 @@
/**
* 자재 카테고리별 구매 수량 계산 유틸리티
*/
/**
* 파이프 구매 수량 계산
* @param {number} lengthMm - 파이프 총 길이 (mm)
* @param {number} quantity - BOM 수량 (개수)
* @returns {object} 구매 계산 결과
*/
export const calculatePipePurchase = (lengthMm, quantity) => {
if (!lengthMm || lengthMm <= 0 || !quantity || quantity <= 0) {
return {
purchaseQuantity: 0,
standardLength: 6000,
cutLength: 0,
calculation: '길이 정보 없음'
};
}
// 절단 여유분: 조각마다 2mm 추가
const cutLength = lengthMm + (quantity * 2);
// 6,000mm 단위로 올림 계산
const pipeCount = Math.ceil(cutLength / 6000);
return {
purchaseQuantity: pipeCount,
standardLength: 6000,
cutLength: cutLength,
calculation: `${lengthMm}mm + ${quantity * 2}mm(여유분) = ${cutLength}mm → ${pipeCount}`
};
};
/**
* 볼트/너트 구매 수량 계산
* @param {number} bomQuantity - BOM 수량
* @returns {object} 구매 계산 결과
*/
export const calculateBoltPurchase = (bomQuantity) => {
if (!bomQuantity || bomQuantity <= 0) {
return {
purchaseQuantity: 0,
calculation: '수량 정보 없음'
};
}
// +5% 여유분 후 4의 배수로 올림
const withMargin = bomQuantity * 1.05;
const purchaseQuantity = Math.ceil(withMargin / 4) * 4;
return {
purchaseQuantity: purchaseQuantity,
marginQuantity: Math.round(withMargin * 10) / 10, // 소수점 1자리
calculation: `${bomQuantity} × 1.05 = ${Math.round(withMargin * 10) / 10}${purchaseQuantity} SETS`
};
};
/**
* 가스켓 구매 수량 계산
* @param {number} bomQuantity - BOM 수량
* @returns {object} 구매 계산 결과
*/
export const calculateGasketPurchase = (bomQuantity) => {
if (!bomQuantity || bomQuantity <= 0) {
return {
purchaseQuantity: 0,
calculation: '수량 정보 없음'
};
}
// 5의 배수로 올림
const purchaseQuantity = Math.ceil(bomQuantity / 5) * 5;
return {
purchaseQuantity: purchaseQuantity,
calculation: `${bomQuantity}${purchaseQuantity} EA (5의 배수)`
};
};
/**
* 피팅/계기/밸브 구매 수량 계산
* @param {number} bomQuantity - BOM 수량
* @returns {object} 구매 계산 결과
*/
export const calculateStandardPurchase = (bomQuantity) => {
return {
purchaseQuantity: bomQuantity || 0,
calculation: `${bomQuantity || 0} EA (BOM 수량 그대로)`
};
};
/**
* 자재 카테고리별 구매 수량 계산 (통합 함수)
* @param {object} material - 자재 정보
* @returns {object} 구매 계산 결과
*/
export const calculatePurchaseQuantity = (material) => {
const category = material.classified_category || material.category || '';
const bomQuantity = material.quantity || 0;
switch (category.toUpperCase()) {
case 'PIPE':
// 파이프의 경우 길이 정보 필요
const lengthMm = material.pipe_details?.length_mm || 0;
const totalLength = material.pipe_details?.total_length_mm || (lengthMm * bomQuantity);
return {
...calculatePipePurchase(totalLength, bomQuantity),
category: 'PIPE',
unit: '본'
};
case 'BOLT':
case 'NUT':
return {
...calculateBoltPurchase(bomQuantity),
category: 'BOLT',
unit: 'SETS'
};
case 'GASKET':
return {
...calculateGasketPurchase(bomQuantity),
category: 'GASKET',
unit: 'EA'
};
case 'FITTING':
case 'INSTRUMENT':
case 'VALVE':
case 'FLANGE':
default:
return {
...calculateStandardPurchase(bomQuantity),
category: category || 'STANDARD',
unit: material.unit || 'EA'
};
}
};
/**
* 자재 목록에 대한 구매 수량 계산 (일괄 처리)
* @param {Array} materials - 자재 목록
* @returns {Array} 구매 계산 결과가 포함된 자재 목록
*/
export const calculateBulkPurchase = (materials) => {
return materials.map(material => ({
...material,
purchaseInfo: calculatePurchaseQuantity(material)
}));
};