feat: 구매 수량 계산 시스템 구현
🧮 구매 수량 계산 로직: - PIPE: 절단 손실(3mm/절단) + 6M 단위 올림 계산 - 일반 자재: 여유율 + 최소 주문 수량 적용 - 자재별 차별화된 여유율 (VALVE 50%, BOLT 20% 등) 🛒 구매 관리 API: - /purchase/items/calculate: 실시간 구매 수량 계산 - /purchase/items/save: 구매 품목 DB 저장 - /purchase/revision-diff: 리비전간 차이 계산 - /purchase/orders/create: 구매 주문 생성 🧪 테스트 검증: - PIPE 절단 손실 계산: 25,000mm → 5본 (정확) - 여유율 적용: VALVE 2개 → 3개 (50% 예비) - 최소 주문: BOLT 24개 → 50개 (박스 단위) 📱 프론트엔드: - PurchaseConfirmationPage 라우팅 추가 - 구매확정 버튼 → 구매 페이지 이동
This commit is contained in:
110
test_purchase_calculator.py
Normal file
110
test_purchase_calculator.py
Normal file
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
구매 수량 계산기 테스트
|
||||
특히 파이프 절단 손실 + 6M 단위 계산 테스트
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.append('backend')
|
||||
|
||||
def test_pipe_calculation():
|
||||
"""파이프 절단 손실 + 6M 단위 계산 테스트"""
|
||||
|
||||
from app.services.purchase_calculator import calculate_pipe_purchase_quantity
|
||||
|
||||
print("🔧 PIPE 구매 수량 계산 테스트\n")
|
||||
|
||||
# 테스트 케이스들
|
||||
test_cases = [
|
||||
{
|
||||
"name": "25,000mm 필요 (10회 절단)",
|
||||
"materials": [
|
||||
{"length_mm": 3000, "original_description": "PIPE 4\" SCH40 - 3M", "quantity": 1},
|
||||
{"length_mm": 2500, "original_description": "PIPE 4\" SCH40 - 2.5M", "quantity": 1},
|
||||
{"length_mm": 1800, "original_description": "PIPE 4\" SCH40 - 1.8M", "quantity": 1},
|
||||
{"length_mm": 4200, "original_description": "PIPE 4\" SCH40 - 4.2M", "quantity": 1},
|
||||
{"length_mm": 2100, "original_description": "PIPE 4\" SCH40 - 2.1M", "quantity": 1},
|
||||
{"length_mm": 1500, "original_description": "PIPE 4\" SCH40 - 1.5M", "quantity": 1},
|
||||
{"length_mm": 3800, "original_description": "PIPE 4\" SCH40 - 3.8M", "quantity": 1},
|
||||
{"length_mm": 2200, "original_description": "PIPE 4\" SCH40 - 2.2M", "quantity": 1},
|
||||
{"length_mm": 1900, "original_description": "PIPE 4\" SCH40 - 1.9M", "quantity": 1},
|
||||
{"length_mm": 2000, "original_description": "PIPE 4\" SCH40 - 2M", "quantity": 1}
|
||||
],
|
||||
"expected_pipes": 5
|
||||
},
|
||||
{
|
||||
"name": "5,900mm 필요 (3회 절단)",
|
||||
"materials": [
|
||||
{"length_mm": 2000, "original_description": "PIPE 6\" SCH40 - 2M", "quantity": 1},
|
||||
{"length_mm": 1900, "original_description": "PIPE 6\" SCH40 - 1.9M", "quantity": 1},
|
||||
{"length_mm": 2000, "original_description": "PIPE 6\" SCH40 - 2M", "quantity": 1}
|
||||
],
|
||||
"expected_pipes": 1
|
||||
},
|
||||
{
|
||||
"name": "12,000mm 정확히 (4회 절단)",
|
||||
"materials": [
|
||||
{"length_mm": 3000, "original_description": "PIPE 8\" SCH40 - 3M", "quantity": 4}
|
||||
],
|
||||
"expected_pipes": 2
|
||||
}
|
||||
]
|
||||
|
||||
for i, test_case in enumerate(test_cases, 1):
|
||||
print(f"📋 테스트 {i}: {test_case['name']}")
|
||||
|
||||
result = calculate_pipe_purchase_quantity(test_case["materials"])
|
||||
|
||||
print(f" 🎯 BOM 총 길이: {result['bom_quantity']:,}mm")
|
||||
print(f" ✂️ 절단 횟수: {result['cutting_count']}회")
|
||||
print(f" 📏 절단 손실: {result['cutting_loss']}mm (각 절단마다 3mm)")
|
||||
print(f" 🔢 총 필요량: {result['required_length']:,}mm")
|
||||
print(f" 📦 구매 파이프: {result['pipes_count']}본 (각 6M)")
|
||||
print(f" 💰 구매 총량: {result['calculated_qty']:,}mm")
|
||||
print(f" ♻️ 여유분: {result['waste_length']:,}mm")
|
||||
print(f" 📊 활용률: {result['utilization_rate']:.1f}%")
|
||||
|
||||
# 결과 확인
|
||||
if result['pipes_count'] == test_case['expected_pipes']:
|
||||
print(f" ✅ 성공: 예상 {test_case['expected_pipes']}본 = 결과 {result['pipes_count']}본")
|
||||
else:
|
||||
print(f" ❌ 실패: 예상 {test_case['expected_pipes']}본 ≠ 결과 {result['pipes_count']}본")
|
||||
|
||||
print()
|
||||
|
||||
def test_standard_calculation():
|
||||
"""일반 자재 구매 수량 계산 테스트"""
|
||||
|
||||
from app.services.purchase_calculator import calculate_standard_purchase_quantity
|
||||
|
||||
print("🔧 일반 자재 구매 수량 계산 테스트\n")
|
||||
|
||||
test_cases = [
|
||||
{"category": "VALVE", "bom_qty": 2, "expected_factor": 1.5, "desc": "밸브 2개 (50% 예비품)"},
|
||||
{"category": "BOLT", "bom_qty": 24, "expected_min": 50, "desc": "볼트 24개 (박스 단위 50개)"},
|
||||
{"category": "FITTING", "bom_qty": 5, "expected_factor": 1.1, "desc": "피팅 5개 (10% 여유)"},
|
||||
{"category": "GASKET", "bom_qty": 3, "expected_factor": 1.25, "desc": "가스켓 3개 (25% 교체 주기)"},
|
||||
{"category": "INSTRUMENT", "bom_qty": 1, "expected_factor": 1.0, "desc": "계기 1개 (정확한 수량)"}
|
||||
]
|
||||
|
||||
for i, test_case in enumerate(test_cases, 1):
|
||||
print(f"📋 테스트 {i}: {test_case['desc']}")
|
||||
|
||||
result = calculate_standard_purchase_quantity(
|
||||
test_case["category"],
|
||||
test_case["bom_qty"]
|
||||
)
|
||||
|
||||
print(f" 🎯 BOM 수량: {result['bom_quantity']}")
|
||||
print(f" 📈 여유율: {result['safety_factor']:.2f} ({(result['safety_factor']-1)*100:.0f}%)")
|
||||
print(f" 🔢 여유 적용: {result['safety_qty']:.1f}")
|
||||
print(f" 📦 최소 주문: {result['min_order_qty']}")
|
||||
print(f" 💰 최종 구매: {result['calculated_qty']}")
|
||||
print(f" ♻️ 여유분: {result['waste_quantity']}")
|
||||
print(f" 📊 활용률: {result['utilization_rate']:.1f}%")
|
||||
print()
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_pipe_calculation()
|
||||
test_standard_calculation()
|
||||
Reference in New Issue
Block a user