diff --git a/backend/app/routers/purchase_request.py b/backend/app/routers/purchase_request.py index 832d7e0..c558ce9 100644 --- a/backend/app/routers/purchase_request.py +++ b/backend/app/routers/purchase_request.py @@ -1,13 +1,14 @@ """ 구매신청 관리 API """ -from fastapi import APIRouter, Depends, HTTPException, status, Body +from fastapi import APIRouter, Depends, HTTPException, status, Body, File, UploadFile, Form from fastapi.responses import FileResponse from pydantic import BaseModel from sqlalchemy import text from sqlalchemy.orm import Session from typing import Optional, List, Dict from datetime import datetime +from pathlib import Path import os import json @@ -20,7 +21,7 @@ logger = get_logger(__name__) router = APIRouter(prefix="/purchase-request", tags=["Purchase Request"]) # 엑셀 파일 저장 경로 -EXCEL_DIR = "exports" +EXCEL_DIR = "uploads/excel_exports" os.makedirs(EXCEL_DIR, exist_ok=True) class PurchaseRequestCreate(BaseModel): @@ -705,3 +706,76 @@ def create_excel_file(materials_data: List[Dict], file_path: str, request_no: st # 파일 저장 wb.save(file_path) + + +@router.post("/upload-excel") +async def upload_request_excel( + excel_file: UploadFile = File(...), + request_id: int = Form(...), + category: str = Form(...), + # current_user: dict = Depends(get_current_user), + db: Session = Depends(get_db) +): + """ + 구매신청에 대한 엑셀 파일 업로드 (BOM에서 생성한 원본 파일) + """ + try: + # 구매신청 정보 조회 + query = text(""" + SELECT request_no, job_no + FROM purchase_requests + WHERE request_id = :request_id + """) + result = db.execute(query, {"request_id": request_id}).fetchone() + + if not result: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="구매신청을 찾을 수 없습니다" + ) + + # 엑셀 저장 디렉토리 생성 + excel_dir = Path("uploads/excel_exports") + excel_dir.mkdir(parents=True, exist_ok=True) + + # 파일명 생성 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + safe_filename = f"{result.job_no}_{result.request_no}_{timestamp}.xlsx" + file_path = excel_dir / safe_filename + + # 파일 저장 + content = await excel_file.read() + with open(file_path, "wb") as f: + f.write(content) + + # 구매신청 테이블에 엑셀 파일 경로 업데이트 + update_query = text(""" + UPDATE purchase_requests + SET excel_file_path = :excel_file_path + WHERE request_id = :request_id + """) + + db.execute(update_query, { + "excel_file_path": safe_filename, + "request_id": request_id + }) + + db.commit() + + logger.info(f"엑셀 파일 업로드 완료: {safe_filename}") + + return { + "success": True, + "message": "엑셀 파일이 성공적으로 업로드되었습니다", + "file_path": safe_filename + } + + except HTTPException: + raise + except Exception as e: + db.rollback() + logger.error(f"Failed to upload excel file: {str(e)}") + raise HTTPException( + status_code=500, + detail=f"엑셀 파일 업로드 실패: {str(e)}" + ) diff --git a/backend/app/services/bolt_classifier.py b/backend/app/services/bolt_classifier.py index 35249d8..3ec18f2 100644 --- a/backend/app/services/bolt_classifier.py +++ b/backend/app/services/bolt_classifier.py @@ -706,7 +706,8 @@ def classify_bolt(dat_file: str, description: str, main_nom: str, length: Option "nominal_size_fraction": dimensions_result.get('nominal_size_fraction', main_nom), "length": dimensions_result.get('length', ''), "diameter": dimensions_result.get('diameter', ''), - "dimension_description": dimensions_result.get('dimension_description', '') + "dimension_description": dimensions_result.get('dimension_description', ''), + "bolts_per_flange": dimensions_result.get('bolts_per_flange', 1) }, "grade_strength": { @@ -966,12 +967,19 @@ def extract_bolt_dimensions(main_nom: str, description: str) -> Dict: except: nominal_size_fraction = actual_bolt_size + # 플랜지당 볼트 세트 수 추출 (예: (8), (4)) + bolts_per_flange = 1 # 기본값 + flange_bolt_pattern = re.search(r'\((\d+)\)', description) + if flange_bolt_pattern: + bolts_per_flange = int(flange_bolt_pattern.group(1)) + dimensions = { "nominal_size": actual_bolt_size, # 실제 볼트 사이즈 "nominal_size_fraction": nominal_size_fraction, # 분수 변환값 "length": "", "diameter": "", - "dimension_description": nominal_size_fraction # 분수로 표시 + "dimension_description": nominal_size_fraction, # 분수로 표시 + "bolts_per_flange": bolts_per_flange # 플랜지당 볼트 세트 수 } # 길이 정보 추출 (개선된 패턴) @@ -984,6 +992,8 @@ def extract_bolt_dimensions(main_nom: str, description: str) -> Dict: r'X\s*(\d+(?:\.\d+)?)\s*MM', # M8 X 20MM 형태 r',\s*(\d+(?:\.\d+)?)\s*LG', # ", 145.0000 LG" 형태 (PSV, LT 볼트용) r',\s*(\d+(?:\.\d+)?)\s+(?:CK|PSV|LT)', # ", 140 CK" 형태 (PSV 볼트용) + r'PSV\s+(\d+(?:\.\d+)?)', # PSV 140 형태 (PSV 볼트 전용) + r'(\d+(?:\.\d+)?)\s+PSV', # 140 PSV 형태 (PSV 볼트 전용) r'(\d+(?:\.\d+)?)\s*MM(?:\s|,|$)', # 75MM 형태 (단독) r'(\d+(?:\.\d+)?)\s*mm(?:\s|,|$)' # 75mm 형태 (단독) ] diff --git a/backend/app/services/flange_classifier.py b/backend/app/services/flange_classifier.py index 579bde4..b0ceef6 100644 --- a/backend/app/services/flange_classifier.py +++ b/backend/app/services/flange_classifier.py @@ -182,6 +182,14 @@ def classify_flange(dat_file: str, description: str, main_nom: str, dat_upper = dat_file.upper() # 1. 플랜지 키워드 확인 (재질만 있어도 통합 분류기가 이미 플랜지로 분류했으므로 진행) + # 사이트 글라스와 스트레이너는 밸브로 분류되어야 함 + if 'SIGHT GLASS' in desc_upper or 'STRAINER' in desc_upper or '사이트글라스' in desc_upper or '스트레이너' in desc_upper: + return { + "category": "VALVE", + "overall_confidence": 1.0, + "reason": "SIGHT GLASS 또는 STRAINER는 밸브로 분류" + } + flange_keywords = ['FLG', 'FLANGE', '플랜지', 'ORIFICE', 'SPECTACLE', 'PADDLE', 'SPACER'] has_flange_keyword = any(keyword in desc_upper or keyword in dat_upper for keyword in flange_keywords) diff --git a/backend/app/services/integrated_classifier.py b/backend/app/services/integrated_classifier.py index 1d66cc6..dd3118f 100644 --- a/backend/app/services/integrated_classifier.py +++ b/backend/app/services/integrated_classifier.py @@ -10,7 +10,7 @@ from .fitting_classifier import classify_fitting # Level 1: 명확한 타입 키워드 (최우선) LEVEL1_TYPE_KEYWORDS = { "BOLT": ["FLANGE BOLT", "U-BOLT", "U BOLT", "BOLT", "STUD", "NUT", "SCREW", "WASHER", "볼트", "너트", "스터드", "나사", "와셔", "유볼트"], - "VALVE": ["VALVE", "GATE", "BALL", "GLOBE", "CHECK", "BUTTERFLY", "NEEDLE", "RELIEF", "밸브", "게이트", "볼", "글로브", "체크", "버터플라이", "니들", "릴리프"], + "VALVE": ["VALVE", "GATE", "BALL", "GLOBE", "CHECK", "BUTTERFLY", "NEEDLE", "RELIEF", "SIGHT GLASS", "STRAINER", "밸브", "게이트", "볼", "글로브", "체크", "버터플라이", "니들", "릴리프", "사이트글라스", "스트레이너"], "FLANGE": ["FLG", "FLANGE", "플랜지", "프랜지", "ORIFICE", "SPECTACLE", "PADDLE", "SPACER", "BLIND", "REDUCING FLANGE", "RED FLANGE"], "PIPE": ["PIPE", "TUBE", "파이프", "배관", "SMLS", "SEAMLESS"], "FITTING": ["SOCK-O-LET", "WELD-O-LET", "ELL-O-LET", "THREAD-O-LET", "ELB-O-LET", "NIP-O-LET", "COUP-O-LET", "SOCKOLET", "WELDOLET", "ELLOLET", "THREADOLET", "ELBOLET", "NIPOLET", "COUPOLET", "OLET", "ELBOW", "ELL", "TEE", "REDUCER", "CAP", "COUPLING", "NIPPLE", "SWAGE", "PLUG", "엘보", "티", "리듀서", "캡", "니플", "커플링", "플러그", "CONC", "ECC"], @@ -110,6 +110,17 @@ def classify_material_integrated(description: str, main_nom: str = "", "reason": "스페셜 키워드 발견" } + # VALVE 카테고리 우선 확인 (SIGHT GLASS, STRAINER) + if ('SIGHT GLASS' in desc_upper or 'STRAINER' in desc_upper or + '사이트글라스' in desc_upper or '스트레이너' in desc_upper): + return { + "category": "VALVE", + "confidence": 1.0, + "evidence": ["VALVE_SPECIAL_KEYWORD"], + "classification_level": "LEVEL0_VALVE", + "reason": "SIGHT GLASS 또는 STRAINER 키워드 발견" + } + # SUPPORT 카테고리 우선 확인 (BOLT 카테고리보다 먼저) # U-BOLT, CLAMP, URETHANE BLOCK 등 if ('U-BOLT' in desc_upper or 'U BOLT' in desc_upper or '유볼트' in desc_upper or diff --git a/backend/app/services/support_classifier.py b/backend/app/services/support_classifier.py index f7f9dc3..47b53a2 100644 --- a/backend/app/services/support_classifier.py +++ b/backend/app/services/support_classifier.py @@ -108,7 +108,22 @@ def classify_support(dat_file: str, description: str, main_nom: str, # 4. 사이즈 정보 추출 size_result = extract_support_size(description, main_nom) - # 5. 최종 결과 조합 + # 5. 사용자 요구사항 추출 + user_requirements = extract_support_user_requirements(description) + + # 6. 우레탄 블럭슈 두께 정보 추출 및 Material Grade 보강 + enhanced_material_grade = material_result.get('grade', 'UNKNOWN') + if support_type_result.get("support_type") == "URETHANE_BLOCK": + # 두께 정보 추출 (40t, 27t 등) + thickness_match = re.search(r'(\d+)\s*[tT](?![oO])', description.upper()) + if thickness_match: + thickness = f"{thickness_match.group(1)}t" + if enhanced_material_grade == 'UNKNOWN' or not enhanced_material_grade: + enhanced_material_grade = thickness + elif thickness not in enhanced_material_grade: + enhanced_material_grade = f"{enhanced_material_grade} {thickness}" + + # 7. 최종 결과 조합 return { "category": "SUPPORT", @@ -118,10 +133,10 @@ def classify_support(dat_file: str, description: str, main_nom: str, "load_rating": load_result.get("load_rating", ""), "load_capacity": load_result.get("capacity", ""), - # 재질 정보 (공통 모듈) + # 재질 정보 (공통 모듈) - 우레탄 블럭슈 두께 정보 포함 "material": { "standard": material_result.get('standard', 'UNKNOWN'), - "grade": material_result.get('grade', 'UNKNOWN'), + "grade": enhanced_material_grade, "material_type": material_result.get('material_type', 'UNKNOWN'), "confidence": material_result.get('confidence', 0.0) }, @@ -129,6 +144,9 @@ def classify_support(dat_file: str, description: str, main_nom: str, # 사이즈 정보 "size_info": size_result, + # 사용자 요구사항 + "user_requirements": user_requirements, + # 전체 신뢰도 "overall_confidence": calculate_support_confidence({ "type": support_type_result.get('confidence', 0), @@ -183,6 +201,34 @@ def classify_support_type(dat_file: str, description: str) -> Dict: "evidence": ["NO_SUPPORT_TYPE_FOUND"] } +def extract_support_user_requirements(description: str) -> List[str]: + """서포트 사용자 요구사항 추출""" + + desc_upper = description.upper() + requirements = [] + + # 표면처리 관련 + if 'GALV' in desc_upper or 'GALVANIZED' in desc_upper: + requirements.append('GALVANIZED') + if 'HDG' in desc_upper or 'HOT DIP' in desc_upper: + requirements.append('HOT DIP GALVANIZED') + if 'PAINT' in desc_upper or 'PAINTED' in desc_upper: + requirements.append('PAINTED') + + # 재질 관련 + if 'SS' in desc_upper or 'STAINLESS' in desc_upper: + requirements.append('STAINLESS STEEL') + if 'CARBON' in desc_upper: + requirements.append('CARBON STEEL') + + # 특수 요구사항 + if 'FIRE SAFE' in desc_upper: + requirements.append('FIRE SAFE') + if 'SEISMIC' in desc_upper or '내진' in desc_upper: + requirements.append('SEISMIC') + + return requirements + def classify_load_rating(description: str) -> Dict: """하중 등급 분류""" diff --git a/backend/app/services/valve_classifier.py b/backend/app/services/valve_classifier.py index 7b800ff..66d3f3a 100644 --- a/backend/app/services/valve_classifier.py +++ b/backend/app/services/valve_classifier.py @@ -89,6 +89,24 @@ VALVE_TYPES = { "typical_connections": ["FLANGED", "THREADED"], "pressure_range": "150LB ~ 600LB", "special_features": ["LUBRICATED", "NON_LUBRICATED"] + }, + + "SIGHT_GLASS": { + "dat_file_patterns": ["SIGHT_", "SG_"], + "description_keywords": ["SIGHT GLASS", "SIGHT", "사이트글라스", "사이트 글라스"], + "characteristics": "유체 확인용 관찰창", + "typical_connections": ["FLANGED", "THREADED"], + "pressure_range": "150LB ~ 600LB", + "special_features": ["TRANSPARENT", "VISUAL_INSPECTION"] + }, + + "STRAINER": { + "dat_file_patterns": ["STRAINER_", "STR_"], + "description_keywords": ["STRAINER", "스트레이너", "여과기"], + "characteristics": "이물질 여과용", + "typical_connections": ["FLANGED", "THREADED"], + "pressure_range": "150LB ~ 600LB", + "special_features": ["MESH_FILTER", "Y_TYPE", "BASKET_TYPE"] } } @@ -212,8 +230,13 @@ def classify_valve(dat_file: str, description: str, main_nom: str, length: float desc_upper = description.upper() dat_upper = dat_file.upper() - # 1. 밸브 키워드 확인 (재질만 있어도 통합 분류기가 이미 밸브로 분류했으므로 진행) - valve_keywords = ['VALVE', 'GATE', 'BALL', 'GLOBE', 'CHECK', 'BUTTERFLY', 'NEEDLE', 'RELIEF', 'SOLENOID', '밸브', '게이트', '볼', '글로브', '체크', '버터플라이', '니들', '릴리프', '솔레노이드'] + # 1. 사이트 글라스와 스트레이너 우선 확인 + if 'SIGHT GLASS' in desc_upper or 'STRAINER' in desc_upper or '사이트글라스' in desc_upper or '스트레이너' in desc_upper: + # 사이트 글라스와 스트레이너는 항상 밸브로 분류 + pass + + # 밸브 키워드 확인 (재질만 있어도 통합 분류기가 이미 밸브로 분류했으므로 진행) + valve_keywords = ['VALVE', 'GATE', 'BALL', 'GLOBE', 'CHECK', 'BUTTERFLY', 'NEEDLE', 'RELIEF', 'SOLENOID', 'SIGHT GLASS', 'STRAINER', '밸브', '게이트', '볼', '글로브', '체크', '버터플라이', '니들', '릴리프', '솔레노이드', '사이트글라스', '스트레이너'] has_valve_keyword = any(keyword in desc_upper or keyword in dat_upper for keyword in valve_keywords) # 밸브 재질 확인 (A216, A217, A351, A352) diff --git a/backend/exports/PR-20251016-001.json b/backend/exports/PR-20251016-001.json new file mode 100644 index 0000000..ff0ed4f --- /dev/null +++ b/backend/exports/PR-20251016-001.json @@ -0,0 +1,168 @@ +{ + "request_no": "PR-20251016-001", + "job_no": "1", + "created_at": "2025-10-16T05:40:46.947440", + "materials": [ + { + "material_id": 3543, + "description": "PIPE, SMLS, SCH 40S, ASTM A312 TP304", + "category": "PIPE", + "size": "1/2\"", + "material_grade": "ASTM A312 TP304", + "quantity": 11, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 3551, + "description": "PIPE, SMLS, SCH 80, ASTM A106 B", + "category": "PIPE", + "size": "3/4\"", + "material_grade": "ASTM A106 B", + "quantity": 92, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 3555, + "description": "PIPE, SMLS, SCH 40S, ASTM A312 TP304", + "category": "PIPE", + "size": "1\"", + "material_grade": "ASTM A312 TP304", + "quantity": 23, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 3565, + "description": "PIPE, SMLS, SCH 80, ASTM A106 B", + "category": "PIPE", + "size": "1\"", + "material_grade": "ASTM A106 B", + "quantity": 139, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 3574, + "description": "PIPE, SMLS, SCH 40S, ASTM A312 TP304", + "category": "PIPE", + "size": "1 1/2\"", + "material_grade": "ASTM A312 TP304", + "quantity": 14, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 3588, + "description": "PIPE, SMLS, SCH 80, ASTM A106 B", + "category": "PIPE", + "size": "1 1/2\"", + "material_grade": "ASTM A106 B", + "quantity": 98, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 3844, + "description": "PIPE, SMLS, SCH 80, ASTM A106 B", + "category": "PIPE", + "size": "1/2\"", + "material_grade": "ASTM A106 B", + "quantity": 82, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 3926, + "description": "PIPE, SMLS, SCH 40S, ASTM A312 TP304", + "category": "PIPE", + "size": "10\"", + "material_grade": "ASTM A312 TP304", + "quantity": 4, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 3930, + "description": "PIPE, SMLS, SCH 40S, ASTM A312 TP304", + "category": "PIPE", + "size": "12\"", + "material_grade": "ASTM A312 TP304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 3931, + "description": "PIPE, SMLS, SCH 40, ASTM A106 B", + "category": "PIPE", + "size": "2\"", + "material_grade": "ASTM A106 B", + "quantity": 50, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 3981, + "description": "PIPE, SMLS, SCH 40S, ASTM A312 TP304", + "category": "PIPE", + "size": "2\"", + "material_grade": "ASTM A312 TP304", + "quantity": 9, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 3990, + "description": "PIPE, SMLS, SCH 40, ASTM A106 B", + "category": "PIPE", + "size": "3\"", + "material_grade": "ASTM A106 B", + "quantity": 25, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 3998, + "description": "PIPE, SMLS, SCH 40S, ASTM A312 TP304", + "category": "PIPE", + "size": "3\"", + "material_grade": "ASTM A312 TP304", + "quantity": 8, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4023, + "description": "PIPE, SMLS, SCH 40S, ASTM A312 TP304", + "category": "PIPE", + "size": "3/4\"", + "material_grade": "ASTM A312 TP304", + "quantity": 15, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4126, + "description": "PIPE, SMLS, SCH 40, ASTM A106 B", + "category": "PIPE", + "size": "4\"", + "material_grade": "ASTM A106 B", + "quantity": 12, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4138, + "description": "PIPE, SMLS, SCH 40, ASTM A106 B", + "category": "PIPE", + "size": "6\"", + "material_grade": "ASTM A106 B", + "quantity": 13, + "unit": "EA", + "user_requirement": "" + } + ], + "grouped_materials": [] +} \ No newline at end of file diff --git a/backend/exports/PR-20251016-001.xlsx b/backend/exports/PR-20251016-001.xlsx new file mode 100644 index 0000000..cfb4ee0 Binary files /dev/null and b/backend/exports/PR-20251016-001.xlsx differ diff --git a/backend/exports/PR-20251016-002.json b/backend/exports/PR-20251016-002.json new file mode 100644 index 0000000..c502c34 --- /dev/null +++ b/backend/exports/PR-20251016-002.json @@ -0,0 +1,778 @@ +{ + "request_no": "PR-20251016-002", + "job_no": "1", + "created_at": "2025-10-16T05:44:08.264221", + "materials": [ + { + "material_id": 3540, + "description": "HALF NIPPLE, SMLS, SCH 80S, ASTM A312 TP304 SW X NPT", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A312 TP304", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 3542, + "description": "HALF NIPPLE, SMLS, SCH 80S, ASTM A312 TP304 SW X NPT", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A312 TP304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 3682, + "description": "HALF NIPPLE, SMLS, SCH 160, ASTM A106 B SW * NPT", + "category": "FITTING", + "size": "1\"", + "material_grade": "ASTM A106 B", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 3831, + "description": "HALF NIPPLE, SMLS, SCH 160, ASTM A106 B SW X NPT", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A106 B", + "quantity": 4, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 3835, + "description": "HALF NIPPLE, SMLS, SCH 160, ASTM A106 B SW X NPT", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A106 B", + "quantity": 3, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 3838, + "description": "HALF NIPPLE, SMLS, SCH 160, ASTM A106 B SW X NPT", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A106 B", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 3840, + "description": "NIPPLE, SMLS, SCH 160, ASTM A106 B", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A106 B", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4151, + "description": "90 LR ELL, SMLS, SCH 40S, ASTM A403 WP304", + "category": "FITTING", + "size": "10\"", + "material_grade": "ASTM A403 WP304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4152, + "description": "90 LR ELL, SMLS, SCH 40, ASTM A234 WPB", + "category": "FITTING", + "size": "2\"", + "material_grade": "ASTM A234 WPB", + "quantity": 25, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4177, + "description": "90 LR ELL, SMLS, SCH 40S, ASTM A403 WP304", + "category": "FITTING", + "size": "2\"", + "material_grade": "ASTM A403 WP304", + "quantity": 6, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4183, + "description": "90 LR ELL, SMLS, SCH 40, ASTM A234 WPB", + "category": "FITTING", + "size": "3\"", + "material_grade": "ASTM A234 WPB", + "quantity": 12, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4195, + "description": "90 LR ELL, SMLS, SCH 40S, ASTM A403 WP304", + "category": "FITTING", + "size": "3\"", + "material_grade": "ASTM A403 WP304", + "quantity": 4, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4199, + "description": "90 LR ELL, SMLS, SCH 40, ASTM A234 WPB", + "category": "FITTING", + "size": "4\"", + "material_grade": "ASTM A234 WPB", + "quantity": 7, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4206, + "description": "90 SR ELL, SMLS, SCH 40, ASTM A234 WPB", + "category": "FITTING", + "size": "4\"", + "material_grade": "ASTM A234 WPB", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4207, + "description": "90 LR ELL, SMLS, SCH 40, ASTM A234 WPB", + "category": "FITTING", + "size": "6\"", + "material_grade": "ASTM A234 WPB", + "quantity": 7, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4214, + "description": "45 ELL, SMLS, SCH 40, ASTM A234 WPB", + "category": "FITTING", + "size": "6\"", + "material_grade": "ASTM A234 WPB", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4216, + "description": "TEE, SMLS, SCH 40, ASTM A234 WPB", + "category": "FITTING", + "size": "2\"", + "material_grade": "ASTM A234 WPB", + "quantity": 4, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4220, + "description": "TEE, SMLS, SCH 40S, ASTM A403 WP304", + "category": "FITTING", + "size": "2\"", + "material_grade": "ASTM A403 WP304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4221, + "description": "TEE, SMLS, SCH 40, ASTM A234 WPB", + "category": "FITTING", + "size": "3\"", + "material_grade": "ASTM A234 WPB", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4222, + "description": "TEE, SMLS, SCH 40S, ASTM A403 WP304", + "category": "FITTING", + "size": "3\"", + "material_grade": "ASTM A403 WP304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4223, + "description": "TEE RED, SMLS, SCH 40 X SCH 80, ASTM A234 WPB", + "category": "FITTING", + "size": "2\" x 1 1/2\"", + "material_grade": "ASTM A234 WPB", + "quantity": 3, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4226, + "description": "TEE RED, SMLS, SCH 40S X SCH 40S, ASTM A403 WP304", + "category": "FITTING", + "size": "2\" x 1 1/2\"", + "material_grade": "ASTM A403 WP304", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4228, + "description": "TEE RED, SMLS, SCH 40 X SCH 80, ASTM A234 WPB", + "category": "FITTING", + "size": "3\" x 1 1/2\"", + "material_grade": "ASTM A234 WPB", + "quantity": 3, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4231, + "description": "TEE RED, SMLS, SCH 40 X SCH 80, ASTM A234 WPB", + "category": "FITTING", + "size": "4\" x 1 1/2\"", + "material_grade": "ASTM A234 WPB", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4233, + "description": "RED CONC, SMLS, SCH 80 X SCH 80, ASTM A234 WPB", + "category": "FITTING", + "size": "1 1/2\" x 1\"", + "material_grade": "ASTM A234 WPB", + "quantity": 5, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4238, + "description": "RED CONC, SMLS, SCH 80 X SCH 80, ASTM A234 WPB", + "category": "FITTING", + "size": "1 1/2\" x 3/4\"", + "material_grade": "ASTM A234 WPB", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4240, + "description": "RED CONC, SMLS, SCH 80 X SCH 80, ASTM A234 WPB", + "category": "FITTING", + "size": "1\" x 3/4\"", + "material_grade": "ASTM A234 WPB", + "quantity": 5, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4245, + "description": "RED CONC, SMLS, SCH 40S X SCH 40S, ASTM A403 WP304", + "category": "FITTING", + "size": "12\" x 10\"", + "material_grade": "ASTM A403 WP304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4246, + "description": "RED CONC, SMLS, SCH 40 X SCH 80, ASTM A234 WPB", + "category": "FITTING", + "size": "2\" x 1 1/2\"", + "material_grade": "ASTM A234 WPB", + "quantity": 6, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4252, + "description": "RED CONC, SMLS, SCH 40S X SCH 40S, ASTM A403 WP304", + "category": "FITTING", + "size": "2\" x 1 1/2\"", + "material_grade": "ASTM A403 WP304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4253, + "description": "RED CONC, SMLS, SCH 40 X SCH 80, ASTM A234 WPB", + "category": "FITTING", + "size": "2\" x 1\"", + "material_grade": "ASTM A234 WPB", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4254, + "description": "RED CONC, SMLS, SCH 40 X SCH 80, ASTM A234 WPB", + "category": "FITTING", + "size": "3\" x 1 1/2\"", + "material_grade": "ASTM A234 WPB", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4256, + "description": "RED CONC, SMLS, SCH 40S X SCH 40S, ASTM A403 WP304", + "category": "FITTING", + "size": "3\" x 1\"", + "material_grade": "ASTM A403 WP304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4257, + "description": "RED CONC, SMLS, SCH 40 X SCH 40, ASTM A234 WPB", + "category": "FITTING", + "size": "3\" x 2\"", + "material_grade": "ASTM A234 WPB", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4258, + "description": "RED CONC, SMLS, SCH 40S X SCH 40S, ASTM A403 WP304", + "category": "FITTING", + "size": "3\" x 2\"", + "material_grade": "ASTM A403 WP304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 4259, + "description": "RED CONC, SMLS, SCH 80 X SCH 80, ASTM A234 WPB", + "category": "FITTING", + "size": "3/4\" x 1/2\"", + "material_grade": "ASTM A234 WPB", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5136, + "description": "90 ELL, SW, 3000LB, ASTM A182 F304", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A182 F304", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5138, + "description": "90 ELL, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1\"", + "material_grade": "ASTM A105", + "quantity": 57, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5142, + "description": "90 ELL, SW, 3000LB, ASTM A182 F304", + "category": "FITTING", + "size": "1\"", + "material_grade": "ASTM A182 F304", + "quantity": 9, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5146, + "description": "90 ELL, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1 1/2\"", + "material_grade": "ASTM A105", + "quantity": 32, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5178, + "description": "90 ELL, SW, 3000LB, ASTM A182 F304", + "category": "FITTING", + "size": "1 1/2\"", + "material_grade": "ASTM A182 F304", + "quantity": 9, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5245, + "description": "90 ELL, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A105", + "quantity": 32, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5277, + "description": "90 ELL, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "3/4\"", + "material_grade": "ASTM A105", + "quantity": 24, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5301, + "description": "TEE, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1 1/2\"", + "material_grade": "ASTM A105", + "quantity": 7, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5308, + "description": "TEE, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1\"", + "material_grade": "ASTM A105", + "quantity": 15, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5323, + "description": "TEE, SW, 3000LB, ASTM A182 F304", + "category": "FITTING", + "size": "1\"", + "material_grade": "ASTM A182 F304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5324, + "description": "TEE, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A105", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5326, + "description": "TEE RED, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1 1/2\" x 1\"", + "material_grade": "ASTM A105", + "quantity": 5, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5331, + "description": "TEE RED, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1 1/2\" x 1/2\"", + "material_grade": "ASTM A105", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5333, + "description": "TEE RED, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1 1/2\" x 3/4\"", + "material_grade": "ASTM A105", + "quantity": 6, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5339, + "description": "TEE RED, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1\" x 1/2\"", + "material_grade": "ASTM A105", + "quantity": 7, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5346, + "description": "TEE RED, SW, 3000LB, ASTM A182 F304", + "category": "FITTING", + "size": "1\" x 1/2\"", + "material_grade": "ASTM A182 F304", + "quantity": 6, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5349, + "description": "TEE RED, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1\" x 3/4\"", + "material_grade": "ASTM A105", + "quantity": 3, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5355, + "description": "TEE RED, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "2\" x 1\"", + "material_grade": "ASTM A105", + "quantity": 4, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5359, + "description": "TEE RED, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "3/4\" x 1/2\"", + "material_grade": "ASTM A105", + "quantity": 5, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5364, + "description": "CAP, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1 1/2\"", + "material_grade": "ASTM A105", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5365, + "description": "SOLID HEX. PLUG, NPT(M), 3000LB, ASTM A105", + "category": "FITTING", + "size": "1 1/2\"", + "material_grade": "ASTM A105", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5366, + "description": "CAP, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1\"", + "material_grade": "ASTM A105", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5367, + "description": "CAP, SW, 3000LB, ASTM A182 F304", + "category": "FITTING", + "size": "1\"", + "material_grade": "ASTM A182 F304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5368, + "description": "SOLID HEX. PLUG, NPT(M), 3000LB, ASTM A105", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A105", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5370, + "description": "SOLID HEX. PLUG, NPT(M), 3000LB, ASTM A182 F304", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A182 F304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5371, + "description": "CAP, SMLS, SCH 40, BW, ASTM A234 WPB", + "category": "FITTING", + "size": "2\"", + "material_grade": "ASTM A234 WPB", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5372, + "description": "CAP, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "3/4\"", + "material_grade": "ASTM A105", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5373, + "description": "SOLID HEX. PLUG, NPT(M), 3000LB, ASTM A105", + "category": "FITTING", + "size": "3/4\"", + "material_grade": "ASTM A105", + "quantity": 36, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5409, + "description": "SOLID HEX. PLUG, NPT(M), 3000LB, ASTM A182 F304", + "category": "FITTING", + "size": "3/4\"", + "material_grade": "ASTM A182 F304", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5426, + "description": "SOCK O LET, SW, 3000LB, ASTM A182 F304", + "category": "FITTING", + "size": "10\" x 1 1/2\"", + "material_grade": "ASTM A182 F304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5427, + "description": "SOCK O LET, SW, 3000LB, ASTM A182 F304", + "category": "FITTING", + "size": "10\" x 1\"", + "material_grade": "ASTM A182 F304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5428, + "description": "SOCK O LET, SW, 3000LB, ASTM A182 F304", + "category": "FITTING", + "size": "10\" x 3/4\"", + "material_grade": "ASTM A182 F304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5429, + "description": "SOCK O LET, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "2\" x 1/2\"", + "material_grade": "ASTM A105", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5431, + "description": "ELL O LET, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "2\" x 3/4\"", + "material_grade": "ASTM A105", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5432, + "description": "ELL O LET, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "3\" x 3/4\"", + "material_grade": "ASTM A105", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5433, + "description": "SOCK O LET, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "3\" x 3/4\"", + "material_grade": "ASTM A105", + "quantity": 9, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5442, + "description": "SOCK O LET, SW, 3000LB, ASTM A182 F304", + "category": "FITTING", + "size": "3\" x 3/4\"", + "material_grade": "ASTM A182 F304", + "quantity": 3, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5445, + "description": "SOCK O LET, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "3\" x 1\"", + "material_grade": "ASTM A105", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5446, + "description": "SOCK O LET, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "4\" x 3/4\"", + "material_grade": "ASTM A105", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5447, + "description": "SOCK O LET, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "6\" x 1 1/2\"", + "material_grade": "ASTM A105", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 5449, + "description": "SOCK O LET, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "6\" x 3/4\"", + "material_grade": "ASTM A105", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + } + ], + "grouped_materials": [] +} \ No newline at end of file diff --git a/backend/exports/PR-20251016-002.xlsx b/backend/exports/PR-20251016-002.xlsx new file mode 100644 index 0000000..0e9673b Binary files /dev/null and b/backend/exports/PR-20251016-002.xlsx differ diff --git a/backend/exports/PR-20251016-003.json b/backend/exports/PR-20251016-003.json new file mode 100644 index 0000000..e9042f3 --- /dev/null +++ b/backend/exports/PR-20251016-003.json @@ -0,0 +1,168 @@ +{ + "request_no": "PR-20251016-003", + "job_no": "2", + "created_at": "2025-10-16T06:01:25.896639", + "materials": [ + { + "material_id": 7082, + "description": "PIPE, SMLS, SCH 40S, ASTM A312 TP304", + "category": "PIPE", + "size": "1/2\"", + "material_grade": "ASTM A312 TP304", + "quantity": 11, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 7090, + "description": "PIPE, SMLS, SCH 80, ASTM A106 B", + "category": "PIPE", + "size": "3/4\"", + "material_grade": "ASTM A106 B", + "quantity": 92, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 7094, + "description": "PIPE, SMLS, SCH 40S, ASTM A312 TP304", + "category": "PIPE", + "size": "1\"", + "material_grade": "ASTM A312 TP304", + "quantity": 23, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 7104, + "description": "PIPE, SMLS, SCH 80, ASTM A106 B", + "category": "PIPE", + "size": "1\"", + "material_grade": "ASTM A106 B", + "quantity": 139, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 7113, + "description": "PIPE, SMLS, SCH 40S, ASTM A312 TP304", + "category": "PIPE", + "size": "1 1/2\"", + "material_grade": "ASTM A312 TP304", + "quantity": 14, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 7127, + "description": "PIPE, SMLS, SCH 80, ASTM A106 B", + "category": "PIPE", + "size": "1 1/2\"", + "material_grade": "ASTM A106 B", + "quantity": 98, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 7383, + "description": "PIPE, SMLS, SCH 80, ASTM A106 B", + "category": "PIPE", + "size": "1/2\"", + "material_grade": "ASTM A106 B", + "quantity": 82, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 7465, + "description": "PIPE, SMLS, SCH 40S, ASTM A312 TP304", + "category": "PIPE", + "size": "10\"", + "material_grade": "ASTM A312 TP304", + "quantity": 4, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 7469, + "description": "PIPE, SMLS, SCH 40S, ASTM A312 TP304", + "category": "PIPE", + "size": "12\"", + "material_grade": "ASTM A312 TP304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 7470, + "description": "PIPE, SMLS, SCH 40, ASTM A106 B", + "category": "PIPE", + "size": "2\"", + "material_grade": "ASTM A106 B", + "quantity": 50, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 7520, + "description": "PIPE, SMLS, SCH 40S, ASTM A312 TP304", + "category": "PIPE", + "size": "2\"", + "material_grade": "ASTM A312 TP304", + "quantity": 9, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 7529, + "description": "PIPE, SMLS, SCH 40, ASTM A106 B", + "category": "PIPE", + "size": "3\"", + "material_grade": "ASTM A106 B", + "quantity": 25, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 7537, + "description": "PIPE, SMLS, SCH 40S, ASTM A312 TP304", + "category": "PIPE", + "size": "3\"", + "material_grade": "ASTM A312 TP304", + "quantity": 8, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 7562, + "description": "PIPE, SMLS, SCH 40S, ASTM A312 TP304", + "category": "PIPE", + "size": "3/4\"", + "material_grade": "ASTM A312 TP304", + "quantity": 15, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 7665, + "description": "PIPE, SMLS, SCH 40, ASTM A106 B", + "category": "PIPE", + "size": "4\"", + "material_grade": "ASTM A106 B", + "quantity": 12, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 7677, + "description": "PIPE, SMLS, SCH 40, ASTM A106 B", + "category": "PIPE", + "size": "6\"", + "material_grade": "ASTM A106 B", + "quantity": 13, + "unit": "EA", + "user_requirement": "" + } + ], + "grouped_materials": [] +} \ No newline at end of file diff --git a/backend/exports/PR-20251016-003.xlsx b/backend/exports/PR-20251016-003.xlsx new file mode 100644 index 0000000..ec15812 Binary files /dev/null and b/backend/exports/PR-20251016-003.xlsx differ diff --git a/backend/exports/PR-20251016-004.json b/backend/exports/PR-20251016-004.json new file mode 100644 index 0000000..5df6bde --- /dev/null +++ b/backend/exports/PR-20251016-004.json @@ -0,0 +1,408 @@ +{ + "request_no": "PR-20251016-004", + "job_no": "5", + "created_at": "2025-10-16T05:24:45.921468", + "materials": [ + { + "material_id": 118834, + "description": "FLG WELD NECK RF SCH 40S, 150LB, ASTM A182 F304", + "category": "FLANGE", + "size": "10\"", + "material_grade": "ASTM A182 F304", + "quantity": 5, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 118839, + "description": "FLG WELD NECK RF SCH 40S, 150LB, ASTM A182 F304", + "category": "FLANGE", + "size": "12\"", + "material_grade": "ASTM A182 F304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 118840, + "description": "FLG WELD NECK RF SCH 40, 150LB, ASTM A105", + "category": "FLANGE", + "size": "2\"", + "material_grade": "ASTM A105", + "quantity": 36, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 118876, + "description": "FLG WELD NECK RF SCH 40, 300LB, ASTM A105", + "category": "FLANGE", + "size": "2\"", + "material_grade": "ASTM A105", + "quantity": 6, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 118882, + "description": "FLG WELD NECK RF SCH 40S, 150LB, ASTM A182 F304", + "category": "FLANGE", + "size": "2\"", + "material_grade": "ASTM A182 F304", + "quantity": 7, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 118890, + "description": "FLG WELD NECK RF SCH 40, 600LB, ASTM A105", + "category": "FLANGE", + "size": "3\"", + "material_grade": "ASTM A105", + "quantity": 8, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 118895, + "description": "FLG WELD NECK RF SCH 40S, 600LB, ASTM A182 F304", + "category": "FLANGE", + "size": "3\"", + "material_grade": "ASTM A182 F304", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 118897, + "description": "FLG WELD NECK RF SCH 40, 150LB, ASTM A105", + "category": "FLANGE", + "size": "3\"", + "material_grade": "ASTM A105", + "quantity": 12, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 118909, + "description": "FLG WELD NECK RF SCH 40, 300LB, ASTM A105", + "category": "FLANGE", + "size": "3\"", + "material_grade": "ASTM A105", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 118914, + "description": "FLG WELD NECK RF SCH 40S, 150LB, ASTM A182 F304", + "category": "FLANGE", + "size": "3\"", + "material_grade": "ASTM A182 F304", + "quantity": 9, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 118923, + "description": "FLG WELD NECK RF SCH 40, 300LB, ASTM A105", + "category": "FLANGE", + "size": "4\"", + "material_grade": "ASTM A105", + "quantity": 3, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 118926, + "description": "FLG WELD NECK RF SCH 40, 150LB, ASTM A105", + "category": "FLANGE", + "size": "6\"", + "material_grade": "ASTM A105", + "quantity": 10, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 118936, + "description": "FLG SWRF SCH 40S, 150LB, ASTM A182 F304", + "category": "FLANGE", + "size": "1/2\"", + "material_grade": "ASTM A182 F304", + "quantity": 14, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 118948, + "description": "FLG SWRF SCH 80, 150LB, ASTM A105", + "category": "FLANGE", + "size": "3/4\"", + "material_grade": "ASTM A105", + "quantity": 15, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 118952, + "description": "FLG SWRF SCH 40S, 150LB, ASTM A182 F304", + "category": "FLANGE", + "size": "1\"", + "material_grade": "ASTM A182 F304", + "quantity": 9, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 118953, + "description": "FLG SWRF SCH 80, 150LB, ASTM A105", + "category": "FLANGE", + "size": "1\"", + "material_grade": "ASTM A105", + "quantity": 66, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 118957, + "description": "FLG SWRF SCH 80, 600LB, ASTM A105", + "category": "FLANGE", + "size": "1\"", + "material_grade": "ASTM A105", + "quantity": 6, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 118959, + "description": "FLG SWRF SCH 40S, 150LB, ASTM A182 F304", + "category": "FLANGE", + "size": "1 1/2\"", + "material_grade": "ASTM A182 F304", + "quantity": 8, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 118967, + "description": "FLG SWRF SCH 80, 150LB, ASTM A105", + "category": "FLANGE", + "size": "1 1/2\"", + "material_grade": "ASTM A105", + "quantity": 40, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 119003, + "description": "FLG SWRF SCH 80, 600LB, ASTM A105", + "category": "FLANGE", + "size": "1 1/2\"", + "material_grade": "ASTM A105", + "quantity": 5, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 119008, + "description": "RED. FLG SWRF SCH 40S, 150LB, ASTM A182 F304", + "category": "FLANGE", + "size": "1 1/2\" x 3/4\"", + "material_grade": "ASTM A182 F304", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 119010, + "description": "RED. FLG SWRF SCH 80, 150LB, ASTM A105", + "category": "FLANGE", + "size": "1 1/2\" x 3/4\"", + "material_grade": "ASTM A105", + "quantity": 4, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 119014, + "description": "RED. FLG SWRF SCH 80, 600LB, ASTM A105", + "category": "FLANGE", + "size": "1 1/2\" x 3/4\"", + "material_grade": "ASTM A105", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 119024, + "description": "FLG SWRF SCH 40S, 600LB, ASTM A182 F304", + "category": "FLANGE", + "size": "1\"", + "material_grade": "ASTM A182 F304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 119092, + "description": "RED. FLG SWRF SCH 80, 150LB, ASTM A105", + "category": "FLANGE", + "size": "1\" x 3/4\"", + "material_grade": "ASTM A105", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 119097, + "description": "FLG SWRF SCH 80, 300LB, ASTM A105", + "category": "FLANGE", + "size": "1 1/2\"", + "material_grade": "ASTM A105", + "quantity": 3, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 119102, + "description": "FLG SWRF SCH 80, 150LB, ASTM A105", + "category": "FLANGE", + "size": "1/2\"", + "material_grade": "ASTM A105", + "quantity": 57, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 119159, + "description": "FLG SWRF SCH 40S, 150LB, ASTM A182 F304", + "category": "FLANGE", + "size": "3/4\"", + "material_grade": "ASTM A182 F304", + "quantity": 7, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 119166, + "description": "FLG SWRF SCH 40S, 300LB, ASTM A182 F304", + "category": "FLANGE", + "size": "3/4\"", + "material_grade": "ASTM A182 F304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 119167, + "description": "FLG SWRF SCH 40S, 600LB, ASTM A182 F304", + "category": "FLANGE", + "size": "3/4\"", + "material_grade": "ASTM A182 F304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 119179, + "description": "FLG SWRF SCH 80, 300LB, ASTM A105", + "category": "FLANGE", + "size": "3/4\"", + "material_grade": "ASTM A105", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 119181, + "description": "FLG SWRF SCH 80, 600LB, ASTM A105", + "category": "FLANGE", + "size": "3/4\"", + "material_grade": "ASTM A105", + "quantity": 5, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 119985, + "description": "ORIFICE, 150LB", + "category": "FLANGE", + "size": "10\"", + "material_grade": "-", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 119987, + "description": "WOOD ORIFICE, 300LB", + "category": "FLANGE", + "size": "10\"", + "material_grade": "-", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 119989, + "description": "WOOD ORIFICE, 600LB", + "category": "FLANGE", + "size": "3\"", + "material_grade": "-", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 119990, + "description": "WOOD ORIFICE, 300LB", + "category": "FLANGE", + "size": "4\"", + "material_grade": "-", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 119992, + "description": "WOOD ORIFICE, 300LB", + "category": "FLANGE", + "size": "5\"", + "material_grade": "-", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 119993, + "description": "WOOD ORIFICE, 600LB", + "category": "FLANGE", + "size": "5\"", + "material_grade": "-", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 119994, + "description": "WOOD ORIFICE, 150LB", + "category": "FLANGE", + "size": "6\"", + "material_grade": "-", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 119996, + "description": "WOOD ORIFICE, 300LB", + "category": "FLANGE", + "size": "8\"", + "material_grade": "-", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + } + ], + "grouped_materials": [] +} \ No newline at end of file diff --git a/backend/exports/PR-20251016-004.xlsx b/backend/exports/PR-20251016-004.xlsx new file mode 100644 index 0000000..b5a1fde Binary files /dev/null and b/backend/exports/PR-20251016-004.xlsx differ diff --git a/frontend/src/components/bom/materials/BoltMaterialsView.jsx b/frontend/src/components/bom/materials/BoltMaterialsView.jsx index f1006fb..d900e28 100644 --- a/frontend/src/components/bom/materials/BoltMaterialsView.jsx +++ b/frontend/src/components/bom/materials/BoltMaterialsView.jsx @@ -10,6 +10,8 @@ const BoltMaterialsView = ({ userRequirements, setUserRequirements, purchasedMaterials, + onPurchasedMaterialsUpdate, + jobNo, fileId, user }) => { @@ -45,11 +47,24 @@ const BoltMaterialsView = ({ const parseBoltInfo = (material) => { const qty = Math.round(material.quantity || 0); - const safetyQty = Math.ceil(qty * 1.05); // 5% 여유율 - const purchaseQty = Math.ceil(safetyQty / 4) * 4; // 4의 배수 - // 볼트 상세 정보 우선 사용 + // 플랜지당 볼트 세트 수 추출 (예: (8), (4)) const boltDetails = material.bolt_details || {}; + let boltsPerFlange = boltDetails.dimensions?.bolts_per_flange || 1; + + // 백엔드에서 정보가 없으면 원본 설명에서 직접 추출 + if (boltsPerFlange === 1) { + const description = material.original_description || ''; + const flangePattern = description.match(/\((\d+)\)/); + if (flangePattern) { + boltsPerFlange = parseInt(flangePattern[1]); + } + } + + // 실제 필요한 볼트 수 = 플랜지 수 × 플랜지당 볼트 세트 수 + const totalBoltsNeeded = qty * boltsPerFlange; + const safetyQty = Math.ceil(totalBoltsNeeded * 1.05); // 5% 여유율 + const purchaseQty = Math.ceil(safetyQty / 4) * 4; // 4의 배수 // 길이 정보 (bolt_details 우선, 없으면 원본 설명에서 추출) let boltLength = '-'; @@ -113,19 +128,32 @@ const BoltMaterialsView = ({ } } - // 추가요구사항 추출 (ELEC.GALV 등) - const additionalReq = extractBoltAdditionalRequirements(material.original_description || ''); + // 압력 등급 추출 (150LB 등) + let boltPressure = '-'; + const description = material.original_description || ''; + const pressureMatch = description.match(/(\d+)\s*LB/i); + if (pressureMatch) { + boltPressure = `${pressureMatch[1]}LB`; + } + + // User Requirements 추출 (ELEC.GALV 등) + const userRequirements = extractBoltAdditionalRequirements(material.original_description || ''); + + // Purchase Quantity (Quantity + Unit 통합) - 플랜지당 볼트 세트 수 정보 포함 + const purchaseQuantity = boltsPerFlange > 1 + ? `${purchaseQty} SETS (${boltsPerFlange}/flange)` + : `${purchaseQty} SETS`; return { type: 'BOLT', subtype: boltSubtype, size: material.size_spec || material.main_nom || '-', - pressure: '-', // 볼트는 압력 등급 없음 + pressure: boltPressure, // 압력 등급 (150LB 등) schedule: boltLength, // 길이 정보 grade: boltGrade, - additionalReq: additionalReq, // 추가요구사항 - quantity: purchaseQty, - unit: 'SETS' + userRequirements: userRequirements, // User Requirements (ELEC.GALV 등) + additionalReq: '-', // 추가요구사항 (사용자 입력) + purchaseQuantity: purchaseQuantity // 구매수량 (통합) }; }; @@ -228,29 +256,83 @@ const BoltMaterialsView = ({ })); try { - await api.post('/files/save-excel', { + console.log('🔄 볼트 엑셀 내보내기 시작 - 새로운 방식'); + + // 1. 먼저 클라이언트에서 엑셀 파일 생성 + console.log('📊 엑셀 Blob 생성 중...', dataWithRequirements.length, '개 자료'); + const excelBlob = await createExcelBlob(dataWithRequirements, excelFileName, { + category: 'BOLT', + filename: excelFileName, + uploadDate: new Date().toLocaleDateString() + }); + console.log('✅ 엑셀 Blob 생성 완료:', excelBlob.size, 'bytes'); + + // 2. 구매신청 생성 + const allMaterialIds = selectedMaterialsData.map(m => m.id); + const response = await api.post('/purchase-request/create', { file_id: fileId, + job_no: jobNo, category: 'BOLT', - materials: dataWithRequirements, - filename: excelFileName, - user_id: user?.id + 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] || '' + })) }); - exportMaterialsToExcel(dataWithRequirements, excelFileName, { - category: 'BOLT', - filename: excelFileName, - uploadDate: new Date().toLocaleDateString() - }); + if (response.data.success) { + console.log(`✅ 구매신청 완료: ${response.data.request_no}, request_id: ${response.data.request_id}`); + + // 3. 엑셀 파일을 서버에 업로드 + const formData = new FormData(); + formData.append('excel_file', excelBlob, excelFileName); + formData.append('request_id', response.data.request_id); + formData.append('category', 'BOLT'); + + console.log('📤 엑셀 파일 서버 업로드 중...'); + await api.post('/purchase-request/upload-excel', formData, { + headers: { 'Content-Type': 'multipart/form-data' } + }); + console.log('✅ 엑셀 파일 서버 업로드 완료'); - alert('엑셀 파일이 생성되고 서버에 저장되었습니다.'); + // 4. 구매된 자재 목록 업데이트 (비활성화) + onPurchasedMaterialsUpdate(allMaterialIds); + console.log('✅ 구매된 자재 목록 업데이트 완료'); + + // 5. 클라이언트에 파일 다운로드 + const url = window.URL.createObjectURL(excelBlob); + const link = document.createElement('a'); + link.href = url; + link.download = excelFileName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + window.URL.revokeObjectURL(url); + + alert(`구매신청 ${response.data?.request_no || ''}이 생성되고 엑셀 파일이 저장되었습니다.`); + } else { + throw new Error(response.data?.message || '구매신청 생성 실패'); + } } catch (error) { - console.error('엑셀 저장 실패:', error); + console.error('엑셀 저장 또는 구매신청 실패:', error); + // 실패 시에도 클라이언트 다운로드는 진행 exportMaterialsToExcel(dataWithRequirements, excelFileName, { category: 'BOLT', filename: excelFileName, uploadDate: new Date().toLocaleDateString() }); + alert('엑셀 파일은 다운로드되었지만 구매신청 생성에 실패했습니다.'); } + + // 선택 해제 + setSelectedMaterials(new Set()); }; const filteredMaterials = getFilteredAndSortedMaterials(); @@ -317,21 +399,24 @@ const BoltMaterialsView = ({