From b10bd8d01c4cfffc856540e80e7638a11c48c419 Mon Sep 17 00:00:00 2001 From: hyungi Date: Wed, 15 Oct 2025 13:54:46 +0900 Subject: [PATCH 1/3] =?UTF-8?q?temp:=20=ED=98=84=EC=9E=AC=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EC=82=AC=ED=95=AD=20=EC=9E=84=EC=8B=9C=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.jsx | 6 +----- frontend/src/api.js | 3 +-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 6745dbb..d6d98ba 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -649,11 +649,7 @@ function App() { )) ) : ( - <> - - - - + )} diff --git a/frontend/src/api.js b/frontend/src/api.js index 9f6d396..9819f4f 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -3,8 +3,7 @@ import { logApiError } from './utils/errorLogger'; // 환경변수에서 API URL을 읽음 (Vite 기준) // 프로덕션에서는 nginx 프록시를 통해 /api 경로 사용 -const API_BASE_URL = import.meta.env.VITE_API_URL || - (import.meta.env.DEV ? 'http://localhost:18000' : '/api'); +const API_BASE_URL = '/api'; console.log('API Base URL:', API_BASE_URL); console.log('Environment:', import.meta.env.MODE); From e799aae71be933c38ba1e02263e75dff2534fbdf Mon Sep 17 00:00:00 2001 From: hyungi Date: Wed, 15 Oct 2025 13:55:25 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=EC=98=A4=EB=8A=98=206=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EC=9E=91=EC=97=85=20=EB=82=B4=EC=9A=A9=20=EB=B3=B5?= =?UTF-8?q?=EA=B5=AC=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 엑셀 내보내기 개선: 납기일 P열 이동, 관리항목 4개로 축소 - J24-001 더미 프로젝트 옵션 제거 - CORS 오류 해결: API URL /api로 통일 - BOM 페이지 수정사항 포함 - 트랜잭션 오류 해결 시도 --- backend/app/routers/purchase_request.py | 18 +- backend/exports/PR-20251014-001.json | 619 +++++++++++++++++++--- docker-compose.proxy.yml | 2 +- docker-compose.yml | 6 +- frontend/src/App.jsx | 6 +- frontend/src/pages/NewMaterialsPage.jsx | 6 +- frontend/src/pages/SystemSettingsPage.jsx | 4 +- 7 files changed, 568 insertions(+), 93 deletions(-) diff --git a/backend/app/routers/purchase_request.py b/backend/app/routers/purchase_request.py index 39d0d92..51299a8 100644 --- a/backend/app/routers/purchase_request.py +++ b/backend/app/routers/purchase_request.py @@ -34,7 +34,7 @@ class PurchaseRequestCreate(BaseModel): @router.post("/create") async def create_purchase_request( request_data: PurchaseRequestCreate, - # current_user: dict = Depends(get_current_user), + current_user: dict = Depends(get_current_user), db: Session = Depends(get_db) ): """ @@ -81,7 +81,7 @@ async def create_purchase_request( material_count, excel_file_path, requested_by ) VALUES ( :request_no, :file_id, :job_no, :category, - :material_count, :excel_path, :requested_by + :material_count, :excel_file_path, :requested_by ) RETURNING request_id """) @@ -91,8 +91,8 @@ async def create_purchase_request( "job_no": request_data.job_no, "category": request_data.category, "material_count": len(request_data.material_ids), - "excel_path": excel_filename, # 엑셀 파일명 저장 (JSON 대신) - "requested_by": 1 # current_user.get("user_id") + "excel_file_path": excel_filename, # 엑셀 파일명 저장 (JSON 대신) + "requested_by": current_user.get("user_id") }) request_id = result.fetchone().request_id @@ -163,7 +163,7 @@ async def create_purchase_request( db.rollback() logger.error(f"Failed to create purchase request: {str(e)}") raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + status_code=500, detail=f"구매신청 생성 실패: {str(e)}" ) @@ -194,7 +194,7 @@ async def get_purchase_requests( u.name as requested_by, f.original_filename, j.job_name, - COUNT(pri.item_id) as item_count + COUNT(pri.id) as item_count FROM purchase_requests pr LEFT JOIN users u ON pr.requested_by = u.user_id LEFT JOIN files f ON pr.file_id = f.id @@ -244,7 +244,7 @@ async def get_purchase_requests( except Exception as e: logger.error(f"Failed to get purchase requests: {str(e)}") raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + status_code=500, detail=f"구매신청 목록 조회 실패: {str(e)}" ) @@ -400,7 +400,7 @@ async def get_request_materials( except Exception as e: logger.error(f"Failed to get request materials: {str(e)}") raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + status_code=500, detail=f"구매신청 자재 조회 실패: {str(e)}" ) @@ -451,7 +451,7 @@ async def download_request_excel( except Exception as e: logger.error(f"Failed to download request excel: {str(e)}") raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + status_code=500, detail=f"엑셀 다운로드 실패: {str(e)}" ) diff --git a/backend/exports/PR-20251014-001.json b/backend/exports/PR-20251014-001.json index 7889920..3b7cb69 100644 --- a/backend/exports/PR-20251014-001.json +++ b/backend/exports/PR-20251014-001.json @@ -1,100 +1,575 @@ { "request_no": "PR-20251014-001", "job_no": "테스트용", - "created_at": "2025-10-14T06:47:25.065166", + "created_at": "2025-10-14T22:16:10.998006", "materials": [ { - "material_id": 2013, - "description": "SIGHT GLASS, FLG, 150LB", - "category": "FLANGE", - "size": "1\"", - "material_grade": "SS", - "quantity": 6, - "unit": "EA", - "user_requirement": "" - }, - { - "material_id": 2019, - "description": "SIGHT GLASS, FLG, 150LB", - "category": "FLANGE", + "material_id": 60768, + "description": "PIPE, SMLS, SCH 40S, ASTM A312 TP304", + "category": "PIPE", "size": "1/2\"", - "material_grade": "SS", - "quantity": 5, + "material_grade": "ASTM A312 TP304", + "quantity": 11, "unit": "EA", "user_requirement": "" }, { - "material_id": 2024, - "description": "SIGHT GLASS, FLG, 150LB", - "category": "FLANGE", - "size": "2\"", - "material_grade": "SS", - "quantity": 2, - "unit": "EA", - "user_requirement": "" - }, - { - "material_id": 2027, - "description": "STRAINER, FLG, 150LB", - "category": "FLANGE", - "size": "2\"", - "material_grade": "-", - "quantity": 1, + "material_id": 60776, + "description": "PIPE, SMLS, SCH 80, ASTM A106 B", + "category": "PIPE", + "size": "3/4\"", + "material_grade": "ASTM A106 B", + "quantity": 92, "unit": "EA", "user_requirement": "" } ], "grouped_materials": [ { - "group_key": "SIGHT GLASS, FLG, 150LB|1\"|undefined|SS", + "group_key": "PIPE, SMLS, SCH 40S, ASTM A312 TP304|1/2\"|undefined|ASTM A312 TP304", "material_ids": [ - 2013 + 60768 ], - "description": "SIGHT GLASS, FLG, 150LB", - "category": "FLANGE", - "size": "1\"", - "material_grade": "SS", - "quantity": 6, - "unit": "EA", - "user_requirement": "" - }, - { - "group_key": "SIGHT GLASS, FLG, 150LB|1/2\"|undefined|SS", - "material_ids": [ - 2019 - ], - "description": "SIGHT GLASS, FLG, 150LB", - "category": "FLANGE", + "description": "PIPE, SMLS, SCH 40S, ASTM A312 TP304", + "category": "PIPE", "size": "1/2\"", - "material_grade": "SS", - "quantity": 5, - "unit": "EA", + "material_grade": "ASTM A312 TP304", + "quantity": 11, + "unit": "m", + "total_length": 1395.1, + "pipe_lengths": [ + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 155, + "quantity": 1, + "totalLength": 155 + }, + { + "length": 155, + "quantity": 1, + "totalLength": 155 + }, + { + "length": 200, + "quantity": 1, + "totalLength": 200 + }, + { + "length": 245.1, + "quantity": 1, + "totalLength": 245.1 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + } + ], "user_requirement": "" }, { - "group_key": "SIGHT GLASS, FLG, 150LB|2\"|undefined|SS", + "group_key": "PIPE, SMLS, SCH 80, ASTM A106 B|3/4\"|undefined|ASTM A106 B", "material_ids": [ - 2024 + 60776 ], - "description": "SIGHT GLASS, FLG, 150LB", - "category": "FLANGE", - "size": "2\"", - "material_grade": "SS", - "quantity": 2, - "unit": "EA", - "user_requirement": "" - }, - { - "group_key": "STRAINER, FLG, 150LB|2\"|undefined|-", - "material_ids": [ - 2027 + "description": "PIPE, SMLS, SCH 80, ASTM A106 B", + "category": "PIPE", + "size": "3/4\"", + "material_grade": "ASTM A106 B", + "quantity": 92, + "unit": "m", + "total_length": 7920.2, + "pipe_lengths": [ + { + "length": 60, + "quantity": 1, + "totalLength": 60 + }, + { + "length": 60, + "quantity": 1, + "totalLength": 60 + }, + { + "length": 60, + "quantity": 1, + "totalLength": 60 + }, + { + "length": 60, + "quantity": 1, + "totalLength": 60 + }, + { + "length": 43.3, + "quantity": 1, + "totalLength": 43.3 + }, + { + "length": 43.3, + "quantity": 1, + "totalLength": 43.3 + }, + { + "length": 43.3, + "quantity": 1, + "totalLength": 43.3 + }, + { + "length": 43.3, + "quantity": 1, + "totalLength": 43.3 + }, + { + "length": 43.3, + "quantity": 1, + "totalLength": 43.3 + }, + { + "length": 43.3, + "quantity": 1, + "totalLength": 43.3 + }, + { + "length": 50, + "quantity": 1, + "totalLength": 50 + }, + { + "length": 50, + "quantity": 1, + "totalLength": 50 + }, + { + "length": 50, + "quantity": 1, + "totalLength": 50 + }, + { + "length": 50, + "quantity": 1, + "totalLength": 50 + }, + { + "length": 50, + "quantity": 1, + "totalLength": 50 + }, + { + "length": 50, + "quantity": 1, + "totalLength": 50 + }, + { + "length": 50, + "quantity": 1, + "totalLength": 50 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 70, + "quantity": 1, + "totalLength": 70 + }, + { + "length": 76.2, + "quantity": 1, + "totalLength": 76.2 + }, + { + "length": 76.2, + "quantity": 1, + "totalLength": 76.2 + }, + { + "length": 76.2, + "quantity": 1, + "totalLength": 76.2 + }, + { + "length": 76.2, + "quantity": 1, + "totalLength": 76.2 + }, + { + "length": 76.2, + "quantity": 1, + "totalLength": 76.2 + }, + { + "length": 76.2, + "quantity": 1, + "totalLength": 76.2 + }, + { + "length": 77.6, + "quantity": 1, + "totalLength": 77.6 + }, + { + "length": 77.6, + "quantity": 1, + "totalLength": 77.6 + }, + { + "length": 77.6, + "quantity": 1, + "totalLength": 77.6 + }, + { + "length": 77.6, + "quantity": 1, + "totalLength": 77.6 + }, + { + "length": 77.6, + "quantity": 1, + "totalLength": 77.6 + }, + { + "length": 77.6, + "quantity": 1, + "totalLength": 77.6 + }, + { + "length": 80, + "quantity": 1, + "totalLength": 80 + }, + { + "length": 80, + "quantity": 1, + "totalLength": 80 + }, + { + "length": 80, + "quantity": 1, + "totalLength": 80 + }, + { + "length": 80, + "quantity": 1, + "totalLength": 80 + }, + { + "length": 80, + "quantity": 1, + "totalLength": 80 + }, + { + "length": 80, + "quantity": 1, + "totalLength": 80 + }, + { + "length": 80, + "quantity": 1, + "totalLength": 80 + }, + { + "length": 80, + "quantity": 1, + "totalLength": 80 + }, + { + "length": 80, + "quantity": 1, + "totalLength": 80 + }, + { + "length": 88.6, + "quantity": 1, + "totalLength": 88.6 + }, + { + "length": 88.6, + "quantity": 1, + "totalLength": 88.6 + }, + { + "length": 98.4, + "quantity": 1, + "totalLength": 98.4 + }, + { + "length": 98.4, + "quantity": 1, + "totalLength": 98.4 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 100, + "quantity": 1, + "totalLength": 100 + }, + { + "length": 120, + "quantity": 1, + "totalLength": 120 + }, + { + "length": 120, + "quantity": 1, + "totalLength": 120 + }, + { + "length": 150, + "quantity": 1, + "totalLength": 150 + }, + { + "length": 150, + "quantity": 1, + "totalLength": 150 + }, + { + "length": 150, + "quantity": 1, + "totalLength": 150 + }, + { + "length": 150, + "quantity": 1, + "totalLength": 150 + }, + { + "length": 150, + "quantity": 1, + "totalLength": 150 + }, + { + "length": 223.6, + "quantity": 1, + "totalLength": 223.6 + } ], - "description": "STRAINER, FLG, 150LB", - "category": "FLANGE", - "size": "2\"", - "material_grade": "-", - "quantity": 1, - "unit": "EA", "user_requirement": "" } ] diff --git a/docker-compose.proxy.yml b/docker-compose.proxy.yml index b46bbf5..921b60f 100644 --- a/docker-compose.proxy.yml +++ b/docker-compose.proxy.yml @@ -4,7 +4,7 @@ services: container_name: tk-mp-nginx-proxy restart: unless-stopped ports: - - "80:80" + - "8808:80" volumes: - ./nginx-proxy.conf:/etc/nginx/conf.d/default.conf networks: diff --git a/docker-compose.yml b/docker-compose.yml index 4af1f7b..db032d4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -78,13 +78,13 @@ services: context: ./frontend dockerfile: Dockerfile args: - - VITE_API_URL=${VITE_API_URL:-http://localhost:18000} + - VITE_API_URL=${VITE_API_URL:-/api} container_name: tk-mp-frontend restart: unless-stopped ports: - - "${FRONTEND_PORT:-13000}:5173" + - "${FRONTEND_PORT:-13000}:3000" environment: - - VITE_API_URL=${VITE_API_URL:-http://localhost:18000} + - VITE_API_URL=${VITE_API_URL:-/api} depends_on: - backend networks: diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index d6d98ba..e193af9 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -229,9 +229,11 @@ function App() { console.log('getAdminFeatures - Current user:', user); console.log('getAdminFeatures - User role:', user?.role); console.log('getAdminFeatures - Pending count:', pendingSignupCount); + console.log('getAdminFeatures - Role check result:', user?.role === 'system' || user?.role === 'admin'); - // 시스템 관리자 기능 (admin role이 시스템 관리자) - if (user?.role === 'admin') { + // 시스템 관리자 기능 (system role이 최고 권한) + if (user?.role === 'system' || user?.role === 'admin') { + console.log('✅ 시스템 관리자 기능 추가 중...'); features.push( { id: 'user-management', diff --git a/frontend/src/pages/NewMaterialsPage.jsx b/frontend/src/pages/NewMaterialsPage.jsx index 2b027a6..980dbd9 100644 --- a/frontend/src/pages/NewMaterialsPage.jsx +++ b/frontend/src/pages/NewMaterialsPage.jsx @@ -1386,11 +1386,9 @@ const NewMaterialsPage = ({ const timestamp = new Date().toISOString().split('T')[0]; const fileName = `${jobNo}_${selectedCategory}_${timestamp}.xlsx`; - // BOM 페이지에서 사용하는 엑셀 내보내기 함수 사용 - await exportCategoryToExcel( - selectedCategory, + // 기존 엑셀 내보내기 함수 사용 + await exportMaterialsToExcel( dataWithRequirements, - jobNo, fileName, userRequirements ); diff --git a/frontend/src/pages/SystemSettingsPage.jsx b/frontend/src/pages/SystemSettingsPage.jsx index b437edd..c40a628 100644 --- a/frontend/src/pages/SystemSettingsPage.jsx +++ b/frontend/src/pages/SystemSettingsPage.jsx @@ -159,8 +159,8 @@ const SystemSettingsPage = ({ onNavigate, user }) => { } }; - // 관리자 권한 확인 - if (user?.role !== 'admin') { + // 관리자 권한 확인 (system이 최고 권한) + if (user?.role !== 'admin' && user?.role !== 'system') { return (

접근 권한이 없습니다

From b9442928dafc72f6b6562058be21725e33ff8461 Mon Sep 17 00:00:00 2001 From: hyungi Date: Wed, 15 Oct 2025 17:43:10 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=94=A7=20=EC=9E=90=EC=9E=AC=20?= =?UTF-8?q?=EB=B6=84=EB=A5=98=EA=B8=B0=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20?= =?UTF-8?q?ELL=20=ED=82=A4=EC=9B=8C=EB=93=9C=20=EB=B6=84=EB=A5=98=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0=20(=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=ED=95=84=EC=9A=94=20-=20=EC=95=88=EB=90=98?= =?UTF-8?q?=EB=8A=94=EA=B1=B0=20=EA=B0=99=EC=9D=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 분류기 개선사항: 1. ELL-O-LET vs 일반 엘보 분류 개선 - OLET 우선순위 확인 로직 추가 - ELL 키워드 충돌 문제 해결 2. 엘보 서브타입 강화 - 90DEG_LONG_RADIUS, 90DEG_SHORT_RADIUS 등 조합형 추가 - 더 구체적인 키워드 패턴 지원 3. 레듀스 플랜지 분류 개선 - REDUCING FLANGE가 FITTING이 아닌 FLANGE로 분류되도록 수정 - 특별 우선순위 로직 추가 4. 90 ELL SW 분류 문제 해결 - fitting_keywords에 ELL 키워드 추가 - ELBOW description_keywords에 ELL, 90 ELL, 45 ELL 추가 기술적 개선: - 키워드 우선순위 체계 강화 - 구체적인 패턴 매칭 개선 - 분류 신뢰도 향상 플랜지 카테고리 개선: - 타입 풀네임 표시 (WN → WELD NECK FLANGE) - 끝단처리 별도 컬럼 추가 (RF → RAISED FACE) - 엑셀 내보내기 구조 개선 (P열 납기일, 관리항목 4개) --- backend/app/database.py | 12 +- backend/app/routers/purchase_request.py | 127 +- backend/app/services/fitting_classifier.py | 49 +- backend/app/services/integrated_classifier.py | 39 +- backend/exports/PR-20251015-001.json | 1744 +++++++++++++++++ backend/exports/PR-20251015-001.xlsx | Bin 0 -> 9533 bytes backend/exports/PR-20251015-002.json | 745 +++++++ backend/exports/PR-20251015-002.xlsx | Bin 0 -> 7275 bytes frontend/src/App.jsx | 2 +- frontend/src/pages/NewMaterialsPage.jsx | 123 +- frontend/src/utils/excelExport.js | 438 ++++- 11 files changed, 3107 insertions(+), 172 deletions(-) create mode 100644 backend/exports/PR-20251015-001.json create mode 100644 backend/exports/PR-20251015-001.xlsx create mode 100644 backend/exports/PR-20251015-002.json create mode 100644 backend/exports/PR-20251015-002.xlsx diff --git a/backend/app/database.py b/backend/app/database.py index 0435325..91cbad7 100644 --- a/backend/app/database.py +++ b/backend/app/database.py @@ -9,8 +9,16 @@ DATABASE_URL = os.getenv( "postgresql://tkmp_user:tkmp_password_2025@postgres:5432/tk_mp_bom" ) -# SQLAlchemy 엔진 생성 -engine = create_engine(DATABASE_URL) +# SQLAlchemy 엔진 생성 (UTF-8 인코딩 설정) +engine = create_engine( + DATABASE_URL, + connect_args={ + "client_encoding": "utf8", + "options": "-c client_encoding=utf8 -c timezone=UTC" + }, + pool_pre_ping=True, + echo=False +) # 세션 팩토리 생성 SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) diff --git a/backend/app/routers/purchase_request.py b/backend/app/routers/purchase_request.py index 51299a8..a94962f 100644 --- a/backend/app/routers/purchase_request.py +++ b/backend/app/routers/purchase_request.py @@ -194,7 +194,7 @@ async def get_purchase_requests( u.name as requested_by, f.original_filename, j.job_name, - COUNT(pri.id) as item_count + COUNT(pri.item_id) as item_count FROM purchase_requests pr LEFT JOIN users u ON pr.requested_by = u.user_id LEFT JOIN files f ON pr.file_id = f.id @@ -271,9 +271,13 @@ async def get_request_materials( if info_result and info_result.excel_file_path: json_path = os.path.join(EXCEL_DIR, info_result.excel_file_path) if os.path.exists(json_path): - with open(json_path, 'r', encoding='utf-8') as f: - data = json.load(f) - grouped_materials = data.get("grouped_materials", []) + try: + with open(json_path, 'r', encoding='utf-8', errors='ignore') as f: + data = json.load(f) + grouped_materials = data.get("grouped_materials", []) + except Exception as e: + print(f"⚠️ JSON 파일 읽기 오류 (무시): {e}") + grouped_materials = [] # 개별 자재 정보 조회 (기존 코드) query = text(""" @@ -316,76 +320,106 @@ async def get_request_materials( ORDER BY m.classified_category, m.original_description """) + # 🎯 데이터베이스 쿼리 실행 results = db.execute(query, {"request_id": request_id}).fetchall() materials = [] - for row in results: - # quantity를 정수로 변환 (소수점 제거) - qty = row.requested_quantity or row.original_quantity + + # 🎯 안전한 문자열 변환 함수 + def safe_str(value): + if value is None: + return '' try: - qty_int = int(float(qty)) if qty else 0 - except (ValueError, TypeError): + if isinstance(value, bytes): + return value.decode('utf-8', errors='ignore') + return str(value) + except Exception: + return str(value) if value else '' + + for row in results: + try: + # quantity를 정수로 변환 (소수점 제거) + qty = row.requested_quantity or row.original_quantity + try: + qty_int = int(float(qty)) if qty else 0 + except (ValueError, TypeError): + qty_int = 0 + + # 안전한 문자열 변환 + original_description = safe_str(row.original_description) + size_spec = safe_str(row.size_spec) + material_grade = safe_str(row.material_grade) + full_material_grade = safe_str(row.full_material_grade) + user_requirement = safe_str(row.user_requirement) + + except Exception as e: + # 오류 발생 시 기본값 사용 qty_int = 0 + original_description = '' + size_spec = '' + material_grade = '' + full_material_grade = '' + user_requirement = '' # BOM 페이지와 동일한 형식으로 데이터 구성 material_dict = { "item_id": row.item_id, "material_id": row.material_id, "id": row.material_id, - "original_description": row.original_description, - "classified_category": row.classified_category, - "size_spec": row.size_spec, - "size_inch": row.main_nom, - "main_nom": row.main_nom, - "red_nom": row.red_nom, - "schedule": row.schedule, - "material_grade": row.material_grade, - "full_material_grade": row.full_material_grade, + "original_description": original_description, + "classified_category": safe_str(row.classified_category), + "size_spec": size_spec, + "size_inch": safe_str(row.main_nom), + "main_nom": safe_str(row.main_nom), + "red_nom": safe_str(row.red_nom), + "schedule": safe_str(row.schedule), + "material_grade": material_grade, + "full_material_grade": full_material_grade, "quantity": qty_int, - "unit": row.requested_unit or row.original_unit, - "user_requirement": row.user_requirement, + "unit": safe_str(row.requested_unit or row.original_unit), + "user_requirement": user_requirement, "is_ordered": row.is_ordered, "is_received": row.is_received, - "classification_details": row.classification_details + "classification_details": safe_str(row.classification_details) } - # 카테고리별 상세 정보 추가 + # 카테고리별 상세 정보 추가 (안전한 문자열 처리) if row.classified_category == 'PIPE' and row.manufacturing_method: material_dict["pipe_details"] = { - "manufacturing_method": row.manufacturing_method, - "schedule": row.pipe_schedule, - "material_spec": row.material_spec, - "end_preparation": row.end_preparation, + "manufacturing_method": safe_str(row.manufacturing_method), + "schedule": safe_str(row.pipe_schedule), + "material_spec": safe_str(row.material_spec), + "end_preparation": safe_str(row.end_preparation), "length_mm": row.length_mm } elif row.classified_category == 'FITTING' and row.fitting_type: material_dict["fitting_details"] = { - "fitting_type": row.fitting_type, - "fitting_subtype": row.fitting_subtype, - "connection_method": row.fitting_connection, - "pressure_rating": row.fitting_pressure, - "schedule": row.fitting_schedule + "fitting_type": safe_str(row.fitting_type), + "fitting_subtype": safe_str(row.fitting_subtype), + "connection_method": safe_str(row.fitting_connection), + "pressure_rating": safe_str(row.fitting_pressure), + "schedule": safe_str(row.fitting_schedule) } elif row.classified_category == 'FLANGE' and row.flange_type: material_dict["flange_details"] = { - "flange_type": row.flange_type, - "facing_type": row.facing_type, - "pressure_rating": row.flange_pressure + "flange_type": safe_str(row.flange_type), + "facing_type": safe_str(row.facing_type), + "pressure_rating": safe_str(row.flange_pressure) } elif row.classified_category == 'GASKET' and row.gasket_type: material_dict["gasket_details"] = { - "gasket_type": row.gasket_type, - "gasket_subtype": row.gasket_subtype, - "material_type": row.gasket_material, - "filler_material": row.filler_material, - "pressure_rating": row.gasket_pressure, - "thickness": row.gasket_thickness + "gasket_type": safe_str(row.gasket_type), + "gasket_subtype": safe_str(row.gasket_subtype), + "material_type": safe_str(row.gasket_material), + "filler_material": safe_str(row.filler_material), + "pressure_rating": safe_str(row.gasket_pressure), + "thickness": safe_str(row.gasket_thickness) } elif row.classified_category == 'BOLT' and row.bolt_type: material_dict["bolt_details"] = { - "bolt_type": row.bolt_type, - "material_standard": row.bolt_material, - "length": row.bolt_length + "bolt_type": safe_str(row.bolt_type), + "material_standard": safe_str(row.bolt_material), + "length": safe_str(row.bolt_length) } materials.append(material_dict) @@ -521,10 +555,11 @@ def create_excel_file(materials_data: List[Dict], file_path: str, request_no: st ws = wb.create_sheet(title=category) - # 헤더 정의 + # 헤더 정의 (P열에 납기일, 관리항목 통일) headers = ['TAGNO', '품목명', '수량', '통화구분', '단가', '크기', '압력등급', '스케줄', - '재질', '상세내역', '사용자요구', '관리항목1', '관리항목7', '관리항목8', - '관리항목9', '관리항목10', '납기일(YYYY-MM-DD)'] + '재질', '상세내역', '사용자요구', '관리항목1', '관리항목2', '관리항목3', + '관리항목4', '납기일(YYYY-MM-DD)', '관리항목5', '관리항목6', '관리항목7', + '관리항목8', '관리항목9', '관리항목10'] # 헤더 작성 for col, header in enumerate(headers, 1): diff --git a/backend/app/services/fitting_classifier.py b/backend/app/services/fitting_classifier.py index 7c192df..6e75112 100644 --- a/backend/app/services/fitting_classifier.py +++ b/backend/app/services/fitting_classifier.py @@ -11,8 +11,12 @@ from .material_classifier import classify_material, get_manufacturing_method_fro FITTING_TYPES = { "ELBOW": { "dat_file_patterns": ["90L_", "45L_", "ELL_", "ELBOW_"], - "description_keywords": ["ELBOW", "ELL", "엘보"], + "description_keywords": ["ELBOW", "ELL", "엘보", "90 ELBOW", "45 ELBOW", "LR ELBOW", "SR ELBOW", "90 ELL", "45 ELL"], "subtypes": { + "90DEG_LONG_RADIUS": ["90 LR", "90° LR", "90DEG LR", "90도 장반경", "90 LONG RADIUS", "LR 90"], + "90DEG_SHORT_RADIUS": ["90 SR", "90° SR", "90DEG SR", "90도 단반경", "90 SHORT RADIUS", "SR 90"], + "45DEG_LONG_RADIUS": ["45 LR", "45° LR", "45DEG LR", "45도 장반경", "45 LONG RADIUS", "LR 45"], + "45DEG_SHORT_RADIUS": ["45 SR", "45° SR", "45DEG SR", "45도 단반경", "45 SHORT RADIUS", "SR 45"], "90DEG": ["90", "90°", "90DEG", "90도"], "45DEG": ["45", "45°", "45DEG", "45도"], "LONG_RADIUS": ["LR", "LONG RADIUS", "장반경"], @@ -98,11 +102,12 @@ FITTING_TYPES = { }, "OLET": { - "dat_file_patterns": ["SOL_", "WOL_", "TOL_", "OLET_", "SOCK-O-LET", "WELD-O-LET"], - "description_keywords": ["OLET", "올렛", "O-LET", "SOCK-O-LET", "WELD-O-LET", "SOCKOLET", "WELDOLET", "THREAD-O-LET", "THREADOLET", "SOCKLET", "SOCKET"], + "dat_file_patterns": ["SOL_", "WOL_", "TOL_", "EOL_", "NOL_", "COL_", "OLET_", "SOCK-O-LET", "WELD-O-LET", "ELL-O-LET", "THREAD-O-LET", "ELB-O-LET", "NIP-O-LET", "COUP-O-LET"], + "description_keywords": ["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", "올렛", "O-LET", "SOCKLET"], "subtypes": { "SOCKOLET": ["SOCK-O-LET", "SOCKOLET", "SOL", "SOCK O-LET", "SOCKET-O-LET", "SOCKLET"], "WELDOLET": ["WELD-O-LET", "WELDOLET", "WOL", "WELD O-LET", "WELDING-O-LET"], + "ELLOLET": ["ELL-O-LET", "ELLOLET", "EOL", "ELL O-LET", "ELBOW-O-LET"], "THREADOLET": ["THREAD-O-LET", "THREADOLET", "TOL", "THREADED-O-LET"], "ELBOLET": ["ELB-O-LET", "ELBOLET", "EOL", "ELBOW-O-LET"], "NIPOLET": ["NIP-O-LET", "NIPOLET", "NOL", "NIPPLE-O-LET"], @@ -203,7 +208,11 @@ def classify_fitting(dat_file: str, description: str, main_nom: str, dat_upper = dat_file.upper() # 1. 피팅 키워드 확인 (재질만 있어도 통합 분류기가 이미 피팅으로 분류했으므로 진행) - fitting_keywords = ['ELBOW', 'ELL', 'TEE', 'REDUCER', 'RED', 'CAP', 'NIPPLE', 'SWAGE', 'OLET', 'COUPLING', 'PLUG', 'SOCKLET', 'SOCKET', '엘보', '티', '리듀서', '캡', '니플', '스웨지', '올렛', '커플링', '플러그', 'SOCK-O-LET', 'WELD-O-LET', 'SOCKOLET', 'WELDOLET'] + # OLET 키워드를 우선 확인하여 정확한 분류 수행 + olet_keywords = ['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', 'O-LET', 'SOCKLET'] + has_olet_keyword = any(keyword in desc_upper or keyword in dat_upper for keyword in olet_keywords) + + fitting_keywords = ['ELBOW', 'ELL', 'TEE', 'REDUCER', 'RED', 'CAP', 'NIPPLE', 'SWAGE', 'COUPLING', 'PLUG', '엘보', '티', '리듀서', '캡', '니플', '스웨지', '올렛', '커플링', '플러그'] + olet_keywords has_fitting_keyword = any(keyword in desc_upper or keyword in dat_upper for keyword in fitting_keywords) # 피팅 재질 확인 (A234, A403, A420) @@ -301,6 +310,14 @@ def classify_fitting(dat_file: str, description: str, main_nom: str, "fitting_type": fitting_type_result.get('confidence', 0), "connection": connection_result.get('confidence', 0), "pressure": pressure_result.get('confidence', 0) + }), + + # 통합분류기 호환성을 위한 confidence 필드 + "confidence": calculate_fitting_confidence({ + "material": material_result.get('confidence', 0), + "fitting_type": fitting_type_result.get('confidence', 0), + "connection": connection_result.get('confidence', 0), + "pressure": pressure_result.get('confidence', 0) }) } @@ -428,12 +445,28 @@ def classify_fitting_type(dat_file: str, description: str, dat_upper = dat_file.upper() desc_upper = description.upper() - # 0. 사이즈 패턴 분석으로 TEE vs REDUCER 구분 (최우선) + # 0. OLET 우선 확인 (ELL과의 혼동 방지) + olet_specific_keywords = ['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', 'O-LET', 'SOCKLET'] + for keyword in olet_specific_keywords: + if keyword in desc_upper or keyword in dat_upper: + subtype_result = classify_fitting_subtype( + "OLET", desc_upper, main_nom, red_nom, FITTING_TYPES["OLET"] + ) + return { + "type": "OLET", + "subtype": subtype_result["subtype"], + "confidence": 0.95, + "evidence": [f"OLET_PRIORITY_KEYWORD: {keyword}"], + "subtype_confidence": subtype_result["confidence"], + "requires_two_sizes": FITTING_TYPES["OLET"].get("requires_two_sizes", False) + } + + # 1. 사이즈 패턴 분석으로 TEE vs REDUCER 구분 size_pattern_result = analyze_size_pattern_for_fitting_type(desc_upper, main_nom, red_nom) if size_pattern_result.get("confidence", 0) > 0.85: return size_pattern_result - # 1. DAT_FILE 패턴으로 1차 분류 (가장 신뢰도 높음) + # 2. DAT_FILE 패턴으로 1차 분류 (가장 신뢰도 높음) for fitting_type, type_data in FITTING_TYPES.items(): for pattern in type_data["dat_file_patterns"]: if pattern in dat_upper: @@ -450,7 +483,7 @@ def classify_fitting_type(dat_file: str, description: str, "requires_two_sizes": type_data.get("requires_two_sizes", False) } - # 2. DESCRIPTION 키워드로 2차 분류 + # 3. DESCRIPTION 키워드로 2차 분류 for fitting_type, type_data in FITTING_TYPES.items(): for keyword in type_data["description_keywords"]: if keyword in desc_upper: @@ -467,7 +500,7 @@ def classify_fitting_type(dat_file: str, description: str, "requires_two_sizes": type_data.get("requires_two_sizes", False) } - # 3. 분류 실패 + # 4. 분류 실패 return { "type": "UNKNOWN", "subtype": "UNKNOWN", diff --git a/backend/app/services/integrated_classifier.py b/backend/app/services/integrated_classifier.py index fafd339..6c9ce44 100644 --- a/backend/app/services/integrated_classifier.py +++ b/backend/app/services/integrated_classifier.py @@ -11,9 +11,9 @@ from .fitting_classifier import classify_fitting LEVEL1_TYPE_KEYWORDS = { "BOLT": ["FLANGE BOLT", "U-BOLT", "U BOLT", "BOLT", "STUD", "NUT", "SCREW", "WASHER", "볼트", "너트", "스터드", "나사", "와셔", "유볼트"], "VALVE": ["VALVE", "GATE", "BALL", "GLOBE", "CHECK", "BUTTERFLY", "NEEDLE", "RELIEF", "밸브", "게이트", "볼", "글로브", "체크", "버터플라이", "니들", "릴리프"], - "FLANGE": ["FLG", "FLANGE", "플랜지", "프랜지", "ORIFICE", "SPECTACLE", "PADDLE", "SPACER", "BLIND"], + "FLANGE": ["FLG", "FLANGE", "플랜지", "프랜지", "ORIFICE", "SPECTACLE", "PADDLE", "SPACER", "BLIND", "REDUCING FLANGE", "RED FLANGE"], "PIPE": ["PIPE", "TUBE", "파이프", "배관", "SMLS", "SEAMLESS"], - "FITTING": ["ELBOW", "ELL", "TEE", "REDUCER", "RED", "CAP", "COUPLING", "NIPPLE", "SWAGE", "OLET", "PLUG", "엘보", "티", "리듀서", "캡", "니플", "커플링", "플러그", "CONC", "ECC", "SOCK-O-LET", "WELD-O-LET", "SOCKOLET", "WELDOLET", "THREADOLET"], + "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", "RED", "CAP", "COUPLING", "NIPPLE", "SWAGE", "PLUG", "엘보", "티", "리듀서", "캡", "니플", "커플링", "플러그", "CONC", "ECC"], "GASKET": ["GASKET", "GASK", "가스켓", "SWG", "SPIRAL"], "INSTRUMENT": ["GAUGE", "TRANSMITTER", "SENSOR", "THERMOMETER", "계기", "게이지", "트랜스미터", "센서"], "SUPPORT": ["URETHANE BLOCK", "URETHANE", "BLOCK SHOE", "CLAMP", "SUPPORT", "HANGER", "SPRING", "우레탄", "블록", "클램프", "서포트", "행거", "스프링"] @@ -128,24 +128,29 @@ def classify_material_integrated(description: str, main_nom: str = "", # 1단계: Level 1 키워드로 타입 식별 detected_types = [] - for material_type, keywords in LEVEL1_TYPE_KEYWORDS.items(): - type_found = False - # 긴 키워드부터 확인 (FLANGE BOLT가 FLANGE보다 먼저 매칭되도록) - sorted_keywords = sorted(keywords, key=len, reverse=True) - for keyword in sorted_keywords: - # 전체 문자열에서 찾기 - if keyword in desc_upper: - detected_types.append((material_type, keyword)) - type_found = True - break - # 각 부분에서도 정확히 매칭되는지 확인 - for part in desc_parts: - if keyword == part or keyword in part: + + # 특별 우선순위: REDUCING FLANGE 먼저 확인 + if "REDUCING FLANGE" in desc_upper or "RED FLANGE" in desc_upper: + detected_types.append(("FLANGE", "REDUCING FLANGE")) + else: + for material_type, keywords in LEVEL1_TYPE_KEYWORDS.items(): + type_found = False + # 긴 키워드부터 확인 (FLANGE BOLT가 FLANGE보다 먼저 매칭되도록) + sorted_keywords = sorted(keywords, key=len, reverse=True) + for keyword in sorted_keywords: + # 전체 문자열에서 찾기 + if keyword in desc_upper: detected_types.append((material_type, keyword)) type_found = True break - if type_found: - break + # 각 부분에서도 정확히 매칭되는지 확인 + for part in desc_parts: + if keyword == part or keyword in part: + detected_types.append((material_type, keyword)) + type_found = True + break + if type_found: + break # 2단계: 복수 타입 감지 시 Level 2로 구체화 if len(detected_types) > 1: diff --git a/backend/exports/PR-20251015-001.json b/backend/exports/PR-20251015-001.json new file mode 100644 index 0000000..a1dcfa5 --- /dev/null +++ b/backend/exports/PR-20251015-001.json @@ -0,0 +1,1744 @@ +{ + "request_no": "PR-20251015-001", + "job_no": "TKG-20000P", + "created_at": "2025-10-15T04:57:30.133259", + "materials": [ + { + "material_id": 75644, + "description": "HALF NIPPLE, SMLS, SCH 80S, ASTM A312 TP304 SW X NPT", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A312 TP304", + "quantity": 3, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 75646, + "description": "HALF NIPPLE, SMLS, SCH 80S, ASTM A312 TP304 SW X NPT", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A312 TP304", + "quantity": 3, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 75786, + "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": 75935, + "description": "HALF NIPPLE, SMLS, SCH 160, ASTM A106 B SW X NPT", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A106 B", + "quantity": 9, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 75939, + "description": "HALF NIPPLE, SMLS, SCH 160, ASTM A106 B SW X NPT", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A106 B", + "quantity": 9, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 75942, + "description": "HALF NIPPLE, SMLS, SCH 160, ASTM A106 B SW X NPT", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A106 B", + "quantity": 9, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 75944, + "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": 76255, + "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": 76256, + "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": 76281, + "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": 76287, + "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": 76299, + "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": 76303, + "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": 76310, + "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": 76311, + "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": 76318, + "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": 76320, + "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": 76324, + "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": 76325, + "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": 76326, + "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": 76327, + "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": 76330, + "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": 76332, + "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": 76335, + "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": 76337, + "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": 76342, + "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": 76344, + "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": 76349, + "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": 76350, + "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": 76356, + "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": 76357, + "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": 76358, + "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": 76360, + "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": 76361, + "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": 76362, + "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": 76363, + "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": 77240, + "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": 77242, + "description": "90 ELL, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1\"", + "material_grade": "ASTM A105", + "quantity": 57, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 77246, + "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": 77250, + "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": 77282, + "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": 77349, + "description": "90 ELL, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A105", + "quantity": 32, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 77381, + "description": "90 ELL, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "3/4\"", + "material_grade": "ASTM A105", + "quantity": 24, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 77405, + "description": "TEE, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1 1/2\"", + "material_grade": "ASTM A105", + "quantity": 7, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 77412, + "description": "TEE, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1\"", + "material_grade": "ASTM A105", + "quantity": 15, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 77427, + "description": "TEE, SW, 3000LB, ASTM A182 F304", + "category": "FITTING", + "size": "1\"", + "material_grade": "ASTM A182 F304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 77428, + "description": "TEE, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A105", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 77430, + "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": 77435, + "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": 77437, + "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": 77443, + "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": 77450, + "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": 77453, + "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": 77459, + "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": 77463, + "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": 77468, + "description": "CAP, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1 1/2\"", + "material_grade": "ASTM A105", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 77469, + "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": 77470, + "description": "CAP, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1\"", + "material_grade": "ASTM A105", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 77471, + "description": "CAP, SW, 3000LB, ASTM A182 F304", + "category": "FITTING", + "size": "1\"", + "material_grade": "ASTM A182 F304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 77472, + "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": 77474, + "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": 77475, + "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": 77476, + "description": "CAP, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "3/4\"", + "material_grade": "ASTM A105", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 77477, + "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": 77513, + "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": 77530, + "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": 77531, + "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": 77532, + "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": 77533, + "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": 77535, + "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": 77536, + "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": 77537, + "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": 77546, + "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": 77549, + "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": 77550, + "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": 77551, + "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": 77553, + "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": [ + { + "group_key": "HALF NIPPLE, SMLS, SCH 80S, ASTM A312 TP304 SW X NPT|1/2\"|undefined|ASTM A312 TP304", + "material_ids": [ + 75644, + 75646 + ], + "description": "HALF NIPPLE, SMLS, SCH 80S, ASTM A312 TP304 SW X NPT", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A312 TP304", + "quantity": 3, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "HALF NIPPLE, SMLS, SCH 160, ASTM A106 B SW * NPT|1\"|undefined|ASTM A106 B", + "material_ids": [ + 75786 + ], + "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": "" + }, + { + "group_key": "HALF NIPPLE, SMLS, SCH 160, ASTM A106 B SW X NPT|1/2\"|undefined|ASTM A106 B", + "material_ids": [ + 75935, + 75939, + 75942 + ], + "description": "HALF NIPPLE, SMLS, SCH 160, ASTM A106 B SW X NPT", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A106 B", + "quantity": 9, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "NIPPLE, SMLS, SCH 160, ASTM A106 B|1/2\"|undefined|ASTM A106 B", + "material_ids": [ + 75944 + ], + "description": "NIPPLE, SMLS, SCH 160, ASTM A106 B", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A106 B", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "90 LR ELL, SMLS, SCH 40S, ASTM A403 WP304|10\"|undefined|ASTM A403 WP304", + "material_ids": [ + 76255 + ], + "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": "" + }, + { + "group_key": "90 LR ELL, SMLS, SCH 40, ASTM A234 WPB|2\"|undefined|ASTM A234 WPB", + "material_ids": [ + 76256 + ], + "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": "" + }, + { + "group_key": "90 LR ELL, SMLS, SCH 40S, ASTM A403 WP304|2\"|undefined|ASTM A403 WP304", + "material_ids": [ + 76281 + ], + "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": "" + }, + { + "group_key": "90 LR ELL, SMLS, SCH 40, ASTM A234 WPB|3\"|undefined|ASTM A234 WPB", + "material_ids": [ + 76287 + ], + "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": "" + }, + { + "group_key": "90 LR ELL, SMLS, SCH 40S, ASTM A403 WP304|3\"|undefined|ASTM A403 WP304", + "material_ids": [ + 76299 + ], + "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": "" + }, + { + "group_key": "90 LR ELL, SMLS, SCH 40, ASTM A234 WPB|4\"|undefined|ASTM A234 WPB", + "material_ids": [ + 76303 + ], + "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": "" + }, + { + "group_key": "90 SR ELL, SMLS, SCH 40, ASTM A234 WPB|4\"|undefined|ASTM A234 WPB", + "material_ids": [ + 76310 + ], + "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": "" + }, + { + "group_key": "90 LR ELL, SMLS, SCH 40, ASTM A234 WPB|6\"|undefined|ASTM A234 WPB", + "material_ids": [ + 76311 + ], + "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": "" + }, + { + "group_key": "45 ELL, SMLS, SCH 40, ASTM A234 WPB|6\"|undefined|ASTM A234 WPB", + "material_ids": [ + 76318 + ], + "description": "45 ELL, SMLS, SCH 40, ASTM A234 WPB", + "category": "FITTING", + "size": "6\"", + "material_grade": "ASTM A234 WPB", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "TEE, SMLS, SCH 40, ASTM A234 WPB|2\"|undefined|ASTM A234 WPB", + "material_ids": [ + 76320 + ], + "description": "TEE, SMLS, SCH 40, ASTM A234 WPB", + "category": "FITTING", + "size": "2\"", + "material_grade": "ASTM A234 WPB", + "quantity": 4, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "TEE, SMLS, SCH 40S, ASTM A403 WP304|2\"|undefined|ASTM A403 WP304", + "material_ids": [ + 76324 + ], + "description": "TEE, SMLS, SCH 40S, ASTM A403 WP304", + "category": "FITTING", + "size": "2\"", + "material_grade": "ASTM A403 WP304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "TEE, SMLS, SCH 40, ASTM A234 WPB|3\"|undefined|ASTM A234 WPB", + "material_ids": [ + 76325 + ], + "description": "TEE, SMLS, SCH 40, ASTM A234 WPB", + "category": "FITTING", + "size": "3\"", + "material_grade": "ASTM A234 WPB", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "TEE, SMLS, SCH 40S, ASTM A403 WP304|3\"|undefined|ASTM A403 WP304", + "material_ids": [ + 76326 + ], + "description": "TEE, SMLS, SCH 40S, ASTM A403 WP304", + "category": "FITTING", + "size": "3\"", + "material_grade": "ASTM A403 WP304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "TEE RED, SMLS, SCH 40 X SCH 80, ASTM A234 WPB|2\" x 1 1/2\"|undefined|ASTM A234 WPB", + "material_ids": [ + 76327 + ], + "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": "" + }, + { + "group_key": "TEE RED, SMLS, SCH 40S X SCH 40S, ASTM A403 WP304|2\" x 1 1/2\"|undefined|ASTM A403 WP304", + "material_ids": [ + 76330 + ], + "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": "" + }, + { + "group_key": "TEE RED, SMLS, SCH 40 X SCH 80, ASTM A234 WPB|3\" x 1 1/2\"|undefined|ASTM A234 WPB", + "material_ids": [ + 76332 + ], + "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": "" + }, + { + "group_key": "TEE RED, SMLS, SCH 40 X SCH 80, ASTM A234 WPB|4\" x 1 1/2\"|undefined|ASTM A234 WPB", + "material_ids": [ + 76335 + ], + "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": "" + }, + { + "group_key": "RED CONC, SMLS, SCH 80 X SCH 80, ASTM A234 WPB|1 1/2\" x 1\"|undefined|ASTM A234 WPB", + "material_ids": [ + 76337 + ], + "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": "" + }, + { + "group_key": "RED CONC, SMLS, SCH 80 X SCH 80, ASTM A234 WPB|1 1/2\" x 3/4\"|undefined|ASTM A234 WPB", + "material_ids": [ + 76342 + ], + "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": "" + }, + { + "group_key": "RED CONC, SMLS, SCH 80 X SCH 80, ASTM A234 WPB|1\" x 3/4\"|undefined|ASTM A234 WPB", + "material_ids": [ + 76344 + ], + "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": "" + }, + { + "group_key": "RED CONC, SMLS, SCH 40S X SCH 40S, ASTM A403 WP304|12\" x 10\"|undefined|ASTM A403 WP304", + "material_ids": [ + 76349 + ], + "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": "" + }, + { + "group_key": "RED CONC, SMLS, SCH 40 X SCH 80, ASTM A234 WPB|2\" x 1 1/2\"|undefined|ASTM A234 WPB", + "material_ids": [ + 76350 + ], + "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": "" + }, + { + "group_key": "RED CONC, SMLS, SCH 40S X SCH 40S, ASTM A403 WP304|2\" x 1 1/2\"|undefined|ASTM A403 WP304", + "material_ids": [ + 76356 + ], + "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": "" + }, + { + "group_key": "RED CONC, SMLS, SCH 40 X SCH 80, ASTM A234 WPB|2\" x 1\"|undefined|ASTM A234 WPB", + "material_ids": [ + 76357 + ], + "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": "" + }, + { + "group_key": "RED CONC, SMLS, SCH 40 X SCH 80, ASTM A234 WPB|3\" x 1 1/2\"|undefined|ASTM A234 WPB", + "material_ids": [ + 76358 + ], + "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": "" + }, + { + "group_key": "RED CONC, SMLS, SCH 40S X SCH 40S, ASTM A403 WP304|3\" x 1\"|undefined|ASTM A403 WP304", + "material_ids": [ + 76360 + ], + "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": "" + }, + { + "group_key": "RED CONC, SMLS, SCH 40 X SCH 40, ASTM A234 WPB|3\" x 2\"|undefined|ASTM A234 WPB", + "material_ids": [ + 76361 + ], + "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": "" + }, + { + "group_key": "RED CONC, SMLS, SCH 40S X SCH 40S, ASTM A403 WP304|3\" x 2\"|undefined|ASTM A403 WP304", + "material_ids": [ + 76362 + ], + "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": "" + }, + { + "group_key": "RED CONC, SMLS, SCH 80 X SCH 80, ASTM A234 WPB|3/4\" x 1/2\"|undefined|ASTM A234 WPB", + "material_ids": [ + 76363 + ], + "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": "" + }, + { + "group_key": "90 ELL, SW, 3000LB, ASTM A182 F304|1/2\"|undefined|ASTM A182 F304", + "material_ids": [ + 77240 + ], + "description": "90 ELL, SW, 3000LB, ASTM A182 F304", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A182 F304", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "90 ELL, SW, 3000LB, ASTM A105|1\"|undefined|ASTM A105", + "material_ids": [ + 77242 + ], + "description": "90 ELL, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1\"", + "material_grade": "ASTM A105", + "quantity": 57, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "90 ELL, SW, 3000LB, ASTM A182 F304|1\"|undefined|ASTM A182 F304", + "material_ids": [ + 77246 + ], + "description": "90 ELL, SW, 3000LB, ASTM A182 F304", + "category": "FITTING", + "size": "1\"", + "material_grade": "ASTM A182 F304", + "quantity": 9, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "90 ELL, SW, 3000LB, ASTM A105|1 1/2\"|undefined|ASTM A105", + "material_ids": [ + 77250 + ], + "description": "90 ELL, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1 1/2\"", + "material_grade": "ASTM A105", + "quantity": 32, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "90 ELL, SW, 3000LB, ASTM A182 F304|1 1/2\"|undefined|ASTM A182 F304", + "material_ids": [ + 77282 + ], + "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": "" + }, + { + "group_key": "90 ELL, SW, 3000LB, ASTM A105|1/2\"|undefined|ASTM A105", + "material_ids": [ + 77349 + ], + "description": "90 ELL, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A105", + "quantity": 32, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "90 ELL, SW, 3000LB, ASTM A105|3/4\"|undefined|ASTM A105", + "material_ids": [ + 77381 + ], + "description": "90 ELL, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "3/4\"", + "material_grade": "ASTM A105", + "quantity": 24, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "TEE, SW, 3000LB, ASTM A105|1 1/2\"|undefined|ASTM A105", + "material_ids": [ + 77405 + ], + "description": "TEE, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1 1/2\"", + "material_grade": "ASTM A105", + "quantity": 7, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "TEE, SW, 3000LB, ASTM A105|1\"|undefined|ASTM A105", + "material_ids": [ + 77412 + ], + "description": "TEE, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1\"", + "material_grade": "ASTM A105", + "quantity": 15, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "TEE, SW, 3000LB, ASTM A182 F304|1\"|undefined|ASTM A182 F304", + "material_ids": [ + 77427 + ], + "description": "TEE, SW, 3000LB, ASTM A182 F304", + "category": "FITTING", + "size": "1\"", + "material_grade": "ASTM A182 F304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "TEE, SW, 3000LB, ASTM A105|1/2\"|undefined|ASTM A105", + "material_ids": [ + 77428 + ], + "description": "TEE, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A105", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "TEE RED, SW, 3000LB, ASTM A105|1 1/2\" x 1\"|undefined|ASTM A105", + "material_ids": [ + 77430 + ], + "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": "" + }, + { + "group_key": "TEE RED, SW, 3000LB, ASTM A105|1 1/2\" x 1/2\"|undefined|ASTM A105", + "material_ids": [ + 77435 + ], + "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": "" + }, + { + "group_key": "TEE RED, SW, 3000LB, ASTM A105|1 1/2\" x 3/4\"|undefined|ASTM A105", + "material_ids": [ + 77437 + ], + "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": "" + }, + { + "group_key": "TEE RED, SW, 3000LB, ASTM A105|1\" x 1/2\"|undefined|ASTM A105", + "material_ids": [ + 77443 + ], + "description": "TEE RED, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1\" x 1/2\"", + "material_grade": "ASTM A105", + "quantity": 7, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "TEE RED, SW, 3000LB, ASTM A182 F304|1\" x 1/2\"|undefined|ASTM A182 F304", + "material_ids": [ + 77450 + ], + "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": "" + }, + { + "group_key": "TEE RED, SW, 3000LB, ASTM A105|1\" x 3/4\"|undefined|ASTM A105", + "material_ids": [ + 77453 + ], + "description": "TEE RED, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1\" x 3/4\"", + "material_grade": "ASTM A105", + "quantity": 3, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "TEE RED, SW, 3000LB, ASTM A105|2\" x 1\"|undefined|ASTM A105", + "material_ids": [ + 77459 + ], + "description": "TEE RED, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "2\" x 1\"", + "material_grade": "ASTM A105", + "quantity": 4, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "TEE RED, SW, 3000LB, ASTM A105|3/4\" x 1/2\"|undefined|ASTM A105", + "material_ids": [ + 77463 + ], + "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": "" + }, + { + "group_key": "CAP, SW, 3000LB, ASTM A105|1 1/2\"|undefined|ASTM A105", + "material_ids": [ + 77468 + ], + "description": "CAP, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1 1/2\"", + "material_grade": "ASTM A105", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "SOLID HEX. PLUG, NPT(M), 3000LB, ASTM A105|1 1/2\"|undefined|ASTM A105", + "material_ids": [ + 77469 + ], + "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": "" + }, + { + "group_key": "CAP, SW, 3000LB, ASTM A105|1\"|undefined|ASTM A105", + "material_ids": [ + 77470 + ], + "description": "CAP, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "1\"", + "material_grade": "ASTM A105", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "CAP, SW, 3000LB, ASTM A182 F304|1\"|undefined|ASTM A182 F304", + "material_ids": [ + 77471 + ], + "description": "CAP, SW, 3000LB, ASTM A182 F304", + "category": "FITTING", + "size": "1\"", + "material_grade": "ASTM A182 F304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "SOLID HEX. PLUG, NPT(M), 3000LB, ASTM A105|1/2\"|undefined|ASTM A105", + "material_ids": [ + 77472 + ], + "description": "SOLID HEX. PLUG, NPT(M), 3000LB, ASTM A105", + "category": "FITTING", + "size": "1/2\"", + "material_grade": "ASTM A105", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "SOLID HEX. PLUG, NPT(M), 3000LB, ASTM A182 F304|1/2\"|undefined|ASTM A182 F304", + "material_ids": [ + 77474 + ], + "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": "" + }, + { + "group_key": "CAP, SMLS, SCH 40, BW, ASTM A234 WPB|2\"|undefined|ASTM A234 WPB", + "material_ids": [ + 77475 + ], + "description": "CAP, SMLS, SCH 40, BW, ASTM A234 WPB", + "category": "FITTING", + "size": "2\"", + "material_grade": "ASTM A234 WPB", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "CAP, SW, 3000LB, ASTM A105|3/4\"|undefined|ASTM A105", + "material_ids": [ + 77476 + ], + "description": "CAP, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "3/4\"", + "material_grade": "ASTM A105", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "SOLID HEX. PLUG, NPT(M), 3000LB, ASTM A105|3/4\"|undefined|ASTM A105", + "material_ids": [ + 77477 + ], + "description": "SOLID HEX. PLUG, NPT(M), 3000LB, ASTM A105", + "category": "FITTING", + "size": "3/4\"", + "material_grade": "ASTM A105", + "quantity": 36, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "SOLID HEX. PLUG, NPT(M), 3000LB, ASTM A182 F304|3/4\"|undefined|ASTM A182 F304", + "material_ids": [ + 77513 + ], + "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": "" + }, + { + "group_key": "SOCK O LET, SW, 3000LB, ASTM A182 F304|10\" x 1 1/2\"|undefined|ASTM A182 F304", + "material_ids": [ + 77530 + ], + "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": "" + }, + { + "group_key": "SOCK O LET, SW, 3000LB, ASTM A182 F304|10\" x 1\"|undefined|ASTM A182 F304", + "material_ids": [ + 77531 + ], + "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": "" + }, + { + "group_key": "SOCK O LET, SW, 3000LB, ASTM A182 F304|10\" x 3/4\"|undefined|ASTM A182 F304", + "material_ids": [ + 77532 + ], + "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": "" + }, + { + "group_key": "SOCK O LET, SW, 3000LB, ASTM A105|2\" x 1/2\"|undefined|ASTM A105", + "material_ids": [ + 77533 + ], + "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": "" + }, + { + "group_key": "ELL O LET, SW, 3000LB, ASTM A105|2\" x 3/4\"|undefined|ASTM A105", + "material_ids": [ + 77535 + ], + "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": "" + }, + { + "group_key": "ELL O LET, SW, 3000LB, ASTM A105|3\" x 3/4\"|undefined|ASTM A105", + "material_ids": [ + 77536 + ], + "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": "" + }, + { + "group_key": "SOCK O LET, SW, 3000LB, ASTM A105|3\" x 3/4\"|undefined|ASTM A105", + "material_ids": [ + 77537 + ], + "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": "" + }, + { + "group_key": "SOCK O LET, SW, 3000LB, ASTM A182 F304|3\" x 3/4\"|undefined|ASTM A182 F304", + "material_ids": [ + 77546 + ], + "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": "" + }, + { + "group_key": "SOCK O LET, SW, 3000LB, ASTM A105|3\" x 1\"|undefined|ASTM A105", + "material_ids": [ + 77549 + ], + "description": "SOCK O LET, SW, 3000LB, ASTM A105", + "category": "FITTING", + "size": "3\" x 1\"", + "material_grade": "ASTM A105", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "SOCK O LET, SW, 3000LB, ASTM A105|4\" x 3/4\"|undefined|ASTM A105", + "material_ids": [ + 77550 + ], + "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": "" + }, + { + "group_key": "SOCK O LET, SW, 3000LB, ASTM A105|6\" x 1 1/2\"|undefined|ASTM A105", + "material_ids": [ + 77551 + ], + "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": "" + }, + { + "group_key": "SOCK O LET, SW, 3000LB, ASTM A105|6\" x 3/4\"|undefined|ASTM A105", + "material_ids": [ + 77553 + ], + "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": "" + } + ] +} \ No newline at end of file diff --git a/backend/exports/PR-20251015-001.xlsx b/backend/exports/PR-20251015-001.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..102a4e0db5b27b4192c5d28ecf0d133c9d6c1aa4 GIT binary patch literal 9533 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 literal 0 HcmV?d00001 diff --git a/backend/exports/PR-20251015-002.json b/backend/exports/PR-20251015-002.json new file mode 100644 index 0000000..2195c36 --- /dev/null +++ b/backend/exports/PR-20251015-002.json @@ -0,0 +1,745 @@ +{ + "request_no": "PR-20251015-002", + "job_no": "TKG-20000P", + "created_at": "2025-10-15T05:53:13.449375", + "materials": [ + { + "material_id": 76366, + "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": 76371, + "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": 76372, + "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": 76408, + "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": 76414, + "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": 76422, + "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": 76427, + "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": 76429, + "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": 76441, + "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": 76446, + "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": 76455, + "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": 76458, + "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": 76468, + "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": 76480, + "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": 76484, + "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": 76485, + "description": "FLG SWRF SCH 80, 150LB, ASTM A105", + "category": "FLANGE", + "size": "1\"", + "material_grade": "ASTM A105", + "quantity": 66, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 76489, + "description": "FLG SWRF SCH 80, 600LB, ASTM A105", + "category": "FLANGE", + "size": "1\"", + "material_grade": "ASTM A105", + "quantity": 6, + "unit": "EA", + "user_requirement": "" + }, + { + "material_id": 76491, + "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": 76499, + "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": 76535, + "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": 76540, + "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": 76542, + "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": 76546, + "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": 76556, + "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": 76624, + "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": 76629, + "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": 76634, + "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": 76691, + "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": 76698, + "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": 76699, + "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": 76711, + "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": 76713, + "description": "FLG SWRF SCH 80, 600LB, ASTM A105", + "category": "FLANGE", + "size": "3/4\"", + "material_grade": "ASTM A105", + "quantity": 5, + "unit": "EA", + "user_requirement": "" + } + ], + "grouped_materials": [ + { + "group_key": "FLG WELD NECK RF SCH 40S, 150LB, ASTM A182 F304|10\"|undefined|ASTM A182 F304", + "material_ids": [ + 76366 + ], + "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": "" + }, + { + "group_key": "FLG WELD NECK RF SCH 40S, 150LB, ASTM A182 F304|12\"|undefined|ASTM A182 F304", + "material_ids": [ + 76371 + ], + "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": "" + }, + { + "group_key": "FLG WELD NECK RF SCH 40, 150LB, ASTM A105|2\"|undefined|ASTM A105", + "material_ids": [ + 76372 + ], + "description": "FLG WELD NECK RF SCH 40, 150LB, ASTM A105", + "category": "FLANGE", + "size": "2\"", + "material_grade": "ASTM A105", + "quantity": 36, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "FLG WELD NECK RF SCH 40, 300LB, ASTM A105|2\"|undefined|ASTM A105", + "material_ids": [ + 76408 + ], + "description": "FLG WELD NECK RF SCH 40, 300LB, ASTM A105", + "category": "FLANGE", + "size": "2\"", + "material_grade": "ASTM A105", + "quantity": 6, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "FLG WELD NECK RF SCH 40S, 150LB, ASTM A182 F304|2\"|undefined|ASTM A182 F304", + "material_ids": [ + 76414 + ], + "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": "" + }, + { + "group_key": "FLG WELD NECK RF SCH 40, 600LB, ASTM A105|3\"|undefined|ASTM A105", + "material_ids": [ + 76422 + ], + "description": "FLG WELD NECK RF SCH 40, 600LB, ASTM A105", + "category": "FLANGE", + "size": "3\"", + "material_grade": "ASTM A105", + "quantity": 8, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "FLG WELD NECK RF SCH 40S, 600LB, ASTM A182 F304|3\"|undefined|ASTM A182 F304", + "material_ids": [ + 76427 + ], + "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": "" + }, + { + "group_key": "FLG WELD NECK RF SCH 40, 150LB, ASTM A105|3\"|undefined|ASTM A105", + "material_ids": [ + 76429 + ], + "description": "FLG WELD NECK RF SCH 40, 150LB, ASTM A105", + "category": "FLANGE", + "size": "3\"", + "material_grade": "ASTM A105", + "quantity": 12, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "FLG WELD NECK RF SCH 40, 300LB, ASTM A105|3\"|undefined|ASTM A105", + "material_ids": [ + 76441 + ], + "description": "FLG WELD NECK RF SCH 40, 300LB, ASTM A105", + "category": "FLANGE", + "size": "3\"", + "material_grade": "ASTM A105", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "FLG WELD NECK RF SCH 40S, 150LB, ASTM A182 F304|3\"|undefined|ASTM A182 F304", + "material_ids": [ + 76446 + ], + "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": "" + }, + { + "group_key": "FLG WELD NECK RF SCH 40, 300LB, ASTM A105|4\"|undefined|ASTM A105", + "material_ids": [ + 76455 + ], + "description": "FLG WELD NECK RF SCH 40, 300LB, ASTM A105", + "category": "FLANGE", + "size": "4\"", + "material_grade": "ASTM A105", + "quantity": 3, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "FLG WELD NECK RF SCH 40, 150LB, ASTM A105|6\"|undefined|ASTM A105", + "material_ids": [ + 76458 + ], + "description": "FLG WELD NECK RF SCH 40, 150LB, ASTM A105", + "category": "FLANGE", + "size": "6\"", + "material_grade": "ASTM A105", + "quantity": 10, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "FLG SWRF SCH 40S, 150LB, ASTM A182 F304|1/2\"|undefined|ASTM A182 F304", + "material_ids": [ + 76468 + ], + "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": "" + }, + { + "group_key": "FLG SWRF SCH 80, 150LB, ASTM A105|3/4\"|undefined|ASTM A105", + "material_ids": [ + 76480 + ], + "description": "FLG SWRF SCH 80, 150LB, ASTM A105", + "category": "FLANGE", + "size": "3/4\"", + "material_grade": "ASTM A105", + "quantity": 15, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "FLG SWRF SCH 40S, 150LB, ASTM A182 F304|1\"|undefined|ASTM A182 F304", + "material_ids": [ + 76484 + ], + "description": "FLG SWRF SCH 40S, 150LB, ASTM A182 F304", + "category": "FLANGE", + "size": "1\"", + "material_grade": "ASTM A182 F304", + "quantity": 9, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "FLG SWRF SCH 80, 150LB, ASTM A105|1\"|undefined|ASTM A105", + "material_ids": [ + 76485 + ], + "description": "FLG SWRF SCH 80, 150LB, ASTM A105", + "category": "FLANGE", + "size": "1\"", + "material_grade": "ASTM A105", + "quantity": 66, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "FLG SWRF SCH 80, 600LB, ASTM A105|1\"|undefined|ASTM A105", + "material_ids": [ + 76489 + ], + "description": "FLG SWRF SCH 80, 600LB, ASTM A105", + "category": "FLANGE", + "size": "1\"", + "material_grade": "ASTM A105", + "quantity": 6, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "FLG SWRF SCH 40S, 150LB, ASTM A182 F304|1 1/2\"|undefined|ASTM A182 F304", + "material_ids": [ + 76491 + ], + "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": "" + }, + { + "group_key": "FLG SWRF SCH 80, 150LB, ASTM A105|1 1/2\"|undefined|ASTM A105", + "material_ids": [ + 76499 + ], + "description": "FLG SWRF SCH 80, 150LB, ASTM A105", + "category": "FLANGE", + "size": "1 1/2\"", + "material_grade": "ASTM A105", + "quantity": 40, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "FLG SWRF SCH 80, 600LB, ASTM A105|1 1/2\"|undefined|ASTM A105", + "material_ids": [ + 76535 + ], + "description": "FLG SWRF SCH 80, 600LB, ASTM A105", + "category": "FLANGE", + "size": "1 1/2\"", + "material_grade": "ASTM A105", + "quantity": 5, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "RED. FLG SWRF SCH 40S, 150LB, ASTM A182 F304|1 1/2\" x 3/4\"|undefined|ASTM A182 F304", + "material_ids": [ + 76540 + ], + "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": "" + }, + { + "group_key": "RED. FLG SWRF SCH 80, 150LB, ASTM A105|1 1/2\" x 3/4\"|undefined|ASTM A105", + "material_ids": [ + 76542 + ], + "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": "" + }, + { + "group_key": "RED. FLG SWRF SCH 80, 600LB, ASTM A105|1 1/2\" x 3/4\"|undefined|ASTM A105", + "material_ids": [ + 76546 + ], + "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": "" + }, + { + "group_key": "FLG SWRF SCH 40S, 600LB, ASTM A182 F304|1\"|undefined|ASTM A182 F304", + "material_ids": [ + 76556 + ], + "description": "FLG SWRF SCH 40S, 600LB, ASTM A182 F304", + "category": "FLANGE", + "size": "1\"", + "material_grade": "ASTM A182 F304", + "quantity": 1, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "RED. FLG SWRF SCH 80, 150LB, ASTM A105|1\" x 3/4\"|undefined|ASTM A105", + "material_ids": [ + 76624 + ], + "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": "" + }, + { + "group_key": "FLG SWRF SCH 80, 300LB, ASTM A105|1 1/2\"|undefined|ASTM A105", + "material_ids": [ + 76629 + ], + "description": "FLG SWRF SCH 80, 300LB, ASTM A105", + "category": "FLANGE", + "size": "1 1/2\"", + "material_grade": "ASTM A105", + "quantity": 3, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "FLG SWRF SCH 80, 150LB, ASTM A105|1/2\"|undefined|ASTM A105", + "material_ids": [ + 76634 + ], + "description": "FLG SWRF SCH 80, 150LB, ASTM A105", + "category": "FLANGE", + "size": "1/2\"", + "material_grade": "ASTM A105", + "quantity": 57, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "FLG SWRF SCH 40S, 150LB, ASTM A182 F304|3/4\"|undefined|ASTM A182 F304", + "material_ids": [ + 76691 + ], + "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": "" + }, + { + "group_key": "FLG SWRF SCH 40S, 300LB, ASTM A182 F304|3/4\"|undefined|ASTM A182 F304", + "material_ids": [ + 76698 + ], + "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": "" + }, + { + "group_key": "FLG SWRF SCH 40S, 600LB, ASTM A182 F304|3/4\"|undefined|ASTM A182 F304", + "material_ids": [ + 76699 + ], + "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": "" + }, + { + "group_key": "FLG SWRF SCH 80, 300LB, ASTM A105|3/4\"|undefined|ASTM A105", + "material_ids": [ + 76711 + ], + "description": "FLG SWRF SCH 80, 300LB, ASTM A105", + "category": "FLANGE", + "size": "3/4\"", + "material_grade": "ASTM A105", + "quantity": 2, + "unit": "EA", + "user_requirement": "" + }, + { + "group_key": "FLG SWRF SCH 80, 600LB, ASTM A105|3/4\"|undefined|ASTM A105", + "material_ids": [ + 76713 + ], + "description": "FLG SWRF SCH 80, 600LB, ASTM A105", + "category": "FLANGE", + "size": "3/4\"", + "material_grade": "ASTM A105", + "quantity": 5, + "unit": "EA", + "user_requirement": "" + } + ] +} \ No newline at end of file diff --git a/backend/exports/PR-20251015-002.xlsx b/backend/exports/PR-20251015-002.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..3eb8388e6eb79e73ded928a91bc3172301971a16 GIT binary patch literal 7275 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 literal 0 HcmV?d00001 diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index e193af9..6492c61 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -939,7 +939,7 @@ function App() {
- )} {/* adminFeatures 조건문 닫기 */} + )} ); diff --git a/frontend/src/pages/NewMaterialsPage.jsx b/frontend/src/pages/NewMaterialsPage.jsx index 980dbd9..7b7759d 100644 --- a/frontend/src/pages/NewMaterialsPage.jsx +++ b/frontend/src/pages/NewMaterialsPage.jsx @@ -638,6 +638,38 @@ const NewMaterialsPage = ({ // 스웨이지: 타입 명시 const swageType = fittingSubtype || ''; displayType = `SWAGE ${swageType}`.trim(); + } else if (fittingType === 'OLET') { + // OLET: 풀네임으로 표시 + const oletSubtype = fittingSubtype || ''; + let oletDisplayName = ''; + + switch (oletSubtype) { + case 'SOCKOLET': + oletDisplayName = 'SOCK-O-LET'; + break; + case 'WELDOLET': + oletDisplayName = 'WELD-O-LET'; + break; + case 'ELLOLET': + oletDisplayName = 'ELL-O-LET'; + break; + case 'THREADOLET': + oletDisplayName = 'THREAD-O-LET'; + break; + case 'ELBOLET': + oletDisplayName = 'ELB-O-LET'; + break; + case 'NIPOLET': + oletDisplayName = 'NIP-O-LET'; + break; + case 'COUPOLET': + oletDisplayName = 'COUP-O-LET'; + break; + default: + oletDisplayName = 'OLET'; + } + + displayType = oletDisplayName; } else if (!displayType) { // 기타 피팅 타입 displayType = fittingType || 'FITTING'; @@ -738,9 +770,83 @@ const NewMaterialsPage = ({ }; } else if (category === 'FLANGE') { const description = material.original_description || ''; + const flangeDetails = material.flange_details || {}; - // 백엔드에서 개선된 플랜지 타입 제공 (WN RF, SO FF 등) - const displayType = material.flange_details?.flange_type || '-'; + // 플랜지 타입 풀네임 매핑 (영어) + const flangeTypeMap = { + 'WN': 'WELD NECK FLANGE', + 'WELD_NECK': 'WELD NECK FLANGE', + 'SO': 'SLIP ON FLANGE', + 'SLIP_ON': 'SLIP ON FLANGE', + 'SW': 'SOCKET WELD FLANGE', + 'SOCKET_WELD': 'SOCKET WELD FLANGE', + 'BL': 'BLIND FLANGE', + 'BLIND': 'BLIND FLANGE', + 'RED': 'REDUCING FLANGE', + 'REDUCING': 'REDUCING FLANGE', + 'ORIFICE': 'ORIFICE FLANGE', + 'SPECTACLE': 'SPECTACLE BLIND', + 'PADDLE': 'PADDLE BLIND', + 'SPACER': 'SPACER' + }; + + // 끝단처리 풀네임 매핑 (영어) + const facingTypeMap = { + 'RF': 'RAISED FACE', + 'RAISED_FACE': 'RAISED FACE', + 'FF': 'FULL FACE', + 'FULL_FACE': 'FULL FACE', + 'RTJ': 'RING JOINT', + 'RING_JOINT': 'RING JOINT', + 'MALE': 'MALE', + 'FEMALE': 'FEMALE' + }; + + // 백엔드에서 제공된 타입 정보 + const rawFlangeType = flangeDetails.flange_type || ''; + const rawFacingType = flangeDetails.facing_type || ''; + + // 풀네임으로 변환 + let displayType = flangeTypeMap[rawFlangeType] || rawFlangeType || '-'; + let facingType = facingTypeMap[rawFacingType] || rawFacingType || '-'; + + // 백엔드 데이터가 없으면 description에서 추출 + if (displayType === '-') { + const upperDesc = description.toUpperCase(); + if (upperDesc.includes('WN') || upperDesc.includes('WELD NECK')) { + displayType = 'WELD NECK FLANGE'; + } else if (upperDesc.includes('SO') || upperDesc.includes('SLIP ON')) { + displayType = 'SLIP ON FLANGE'; + } else if (upperDesc.includes('SW') || upperDesc.includes('SOCKET')) { + displayType = 'SOCKET WELD FLANGE'; + } else if (upperDesc.includes('BLIND') || upperDesc.includes('BL')) { + displayType = 'BLIND FLANGE'; + } else if (upperDesc.includes('REDUCING') || upperDesc.includes('RED')) { + displayType = 'REDUCING FLANGE'; + } else if (upperDesc.includes('ORIFICE')) { + displayType = 'ORIFICE FLANGE'; + } else if (upperDesc.includes('SPECTACLE')) { + displayType = 'SPECTACLE BLIND'; + } else if (upperDesc.includes('PADDLE')) { + displayType = 'PADDLE BLIND'; + } else if (upperDesc.includes('SPACER')) { + displayType = 'SPACER'; + } else { + displayType = 'FLANGE'; + } + } + + // 끝단처리 정보가 없으면 description에서 추출 + if (facingType === '-') { + const upperDesc = description.toUpperCase(); + if (upperDesc.includes(' RF') || upperDesc.includes('RAISED')) { + facingType = 'RAISED FACE'; + } else if (upperDesc.includes(' FF') || upperDesc.includes('FULL FACE')) { + facingType = 'FULL FACE'; + } else if (upperDesc.includes('RTJ') || upperDesc.includes('RING')) { + facingType = 'RING JOINT'; + } + } // 원본 설명에서 스케줄 추출 let schedule = '-'; @@ -756,11 +862,12 @@ const NewMaterialsPage = ({ return { type: 'FLANGE', - subtype: displayType, // 백엔드에서 개선된 타입 정보 제공 (WN RF, SO FF 등) + subtype: displayType, // 풀네임 플랜지 타입 + facing: facingType, // 새로 추가: 끝단처리 정보 size: material.size_spec || '-', - pressure: material.flange_details?.pressure_rating || '-', + pressure: flangeDetails.pressure_rating || '-', schedule: schedule, - grade: material.full_material_grade || material.material_grade || '-', // 전체 재질명 우선 사용 + grade: material.full_material_grade || material.material_grade || '-', quantity: Math.round(material.quantity || 0), unit: '개', isFlange: true // 플랜지 구분용 플래그 @@ -1734,6 +1841,7 @@ const NewMaterialsPage = ({
선택
종류 타입 + 끝단처리 크기
압력(파운드)
스케줄 @@ -2262,6 +2370,11 @@ const NewMaterialsPage = ({ {info.subtype} + {/* 끝단처리 (플랜지 전용) */} +
+ {info.facing || '-'} +
+ {/* 크기 */}
{info.size} diff --git a/frontend/src/utils/excelExport.js b/frontend/src/utils/excelExport.js index 87fa7b9..7412caf 100644 --- a/frontend/src/utils/excelExport.js +++ b/frontend/src/utils/excelExport.js @@ -156,79 +156,247 @@ const formatMaterialForExcel = (material, includeComparison = false) => { let gasketMaterial = ''; let gasketThickness = ''; if (category === 'PIPE') { - itemName = material.pipe_details?.manufacturing_method || 'PIPE'; - } else if (category === 'FITTING') { - itemName = material.fitting_details?.fitting_type || 'FITTING'; - } else if (category === 'FLANGE') { - // 플랜지는 품목명만 간단하게 (상세내역에 타입 정보) - itemName = 'FLANGE'; + // 파이프 상세 타입 표시 개선 + const pipeDetails = material.pipe_details || {}; + const manufacturingMethod = pipeDetails.manufacturing_method || ''; + const endPreparation = pipeDetails.end_preparation || ''; - // 특수 플랜지는 구분 - const desc = cleanDescription.toUpperCase(); - if (desc.includes('ORIFICE')) { - itemName = 'ORIFICE FLANGE'; - } else if (desc.includes('SPECTACLE')) { - itemName = 'SPECTACLE BLIND'; - } else if (desc.includes('PADDLE')) { - itemName = 'PADDLE BLIND'; - } else if (desc.includes('SPACER')) { - itemName = 'SPACER'; - } else if (desc.includes('BLIND')) { - itemName = 'BLIND FLANGE'; + // 제조방법과 끝단처리 조합으로 상세 타입 생성 + if (manufacturingMethod && endPreparation) { + itemName = `${manufacturingMethod} PIPE (${endPreparation})`; + } else if (manufacturingMethod) { + itemName = `${manufacturingMethod} PIPE`; + } else if (endPreparation) { + itemName = `PIPE (${endPreparation})`; + } else { + // description에서 제조방법 추출 시도 + const desc = cleanDescription.toUpperCase(); + if (desc.includes('SEAMLESS')) { + itemName = 'SEAMLESS PIPE'; + } else if (desc.includes('WELDED')) { + itemName = 'WELDED PIPE'; + } else if (desc.includes('ERW')) { + itemName = 'ERW PIPE'; + } else if (desc.includes('SMLS')) { + itemName = 'SEAMLESS PIPE'; + } else { + itemName = 'PIPE'; + } + } + } else if (category === 'FITTING') { + // 피팅 상세 타입 표시 (OLET 등 풀네임) + const fittingDetails = material.fitting_details || {}; + const fittingType = fittingDetails.fitting_type || ''; + const fittingSubtype = fittingDetails.fitting_subtype || ''; + + if (fittingType === 'OLET') { + // OLET 풀네임 표시 + switch (fittingSubtype) { + case 'SOCKOLET': + itemName = 'SOCK-O-LET'; + break; + case 'WELDOLET': + itemName = 'WELD-O-LET'; + break; + case 'ELLOLET': + itemName = 'ELL-O-LET'; + break; + case 'THREADOLET': + itemName = 'THREAD-O-LET'; + break; + case 'ELBOLET': + itemName = 'ELB-O-LET'; + break; + case 'NIPOLET': + itemName = 'NIP-O-LET'; + break; + case 'COUPOLET': + itemName = 'COUP-O-LET'; + break; + default: + itemName = 'OLET'; + } + } else if (fittingType === 'ELBOW') { + // 엘보 각도 표시 + const angle = fittingSubtype === '90DEG' ? '90도' : fittingSubtype === '45DEG' ? '45도' : ''; + itemName = `엘보 ${angle}`.trim(); + } else if (fittingType === 'TEE') { + // 티 타입 표시 + const teeType = fittingSubtype === 'EQUAL' ? '등경' : fittingSubtype === 'REDUCING' ? '축소' : ''; + itemName = `티 ${teeType}`.trim(); + } else if (fittingType === 'REDUCER') { + // 리듀서 타입 표시 + const reducerType = fittingSubtype === 'CONCENTRIC' ? '동심' : fittingSubtype === 'ECCENTRIC' ? '편심' : ''; + itemName = `리듀서 ${reducerType}`.trim(); + } else { + itemName = fittingType || 'FITTING'; + } + } else if (category === 'FLANGE') { + // 플랜지 상세 타입 표시 + const flangeDetails = material.flange_details || {}; + const flangeType = flangeDetails.flange_type || ''; + const facingType = flangeDetails.facing_type || ''; + + if (flangeType === 'WELD_NECK') { + itemName = '웰드넥 플랜지'; + } else if (flangeType === 'SLIP_ON') { + itemName = '슬립온 플랜지'; + } else if (flangeType === 'SOCKET_WELD') { + itemName = '소켓웰드 플랜지'; + } else if (flangeType === 'BLIND') { + itemName = '블라인드 플랜지'; + } else if (flangeType === 'REDUCING') { + itemName = '축소 플랜지'; + } else { + // 특수 플랜지는 구분 + const desc = cleanDescription.toUpperCase(); + if (desc.includes('ORIFICE')) { + itemName = '오리피스 플랜지'; + } else if (desc.includes('SPECTACLE')) { + itemName = '스펙터클 블라인드'; + } else if (desc.includes('PADDLE')) { + itemName = '패들 블라인드'; + } else if (desc.includes('SPACER')) { + itemName = '스페이서'; + } else { + itemName = '플랜지'; + } } - // 상세내역에 플랜지 타입 정보 저장 (줄임말 사용) - if (material.flange_details && material.flange_details.flange_type) { - detailInfo = material.flange_details.flange_type; // WN RF, SO RF 등 + // 상세내역에 플랜지 타입 정보 저장 + if (flangeDetails.flange_type) { + detailInfo = `${flangeType} ${facingType}`.trim(); } else { - // description에서 추출 (전체 이름 그대로 사용) + // description에서 추출 const flangeTypeMatch = cleanDescription.match(/FLG\s+([^,]+?)(?=\s*SCH|\s*,\s*\d+LB|$)/i); if (flangeTypeMatch) { - detailInfo = flangeTypeMatch[1].trim(); // WELD NECK RF 등 그대로 + detailInfo = flangeTypeMatch[1].trim(); } } } else if (category === 'VALVE') { - itemName = 'VALVE'; - } else if (category === 'GASKET') { - // 가스켓 상세 타입 추출 - if (material.gasket_details) { - const gasketType = material.gasket_details.gasket_type || ''; - const gasketSubtype = material.gasket_details.gasket_subtype || ''; - - if (gasketSubtype && gasketSubtype !== gasketType) { - itemName = gasketSubtype; - } else if (gasketType) { - itemName = gasketType; + // 밸브 상세 타입 표시 + const valveDetails = material.valve_details || {}; + const valveType = valveDetails.valve_type || ''; + + if (valveType === 'GATE') { + itemName = '게이트 밸브'; + } else if (valveType === 'BALL') { + itemName = '볼 밸브'; + } else if (valveType === 'GLOBE') { + itemName = '글로브 밸브'; + } else if (valveType === 'CHECK') { + itemName = '체크 밸브'; + } else if (valveType === 'BUTTERFLY') { + itemName = '버터플라이 밸브'; + } else if (valveType === 'NEEDLE') { + itemName = '니들 밸브'; + } else if (valveType === 'RELIEF') { + itemName = '릴리프 밸브'; + } else { + // description에서 추출 + const desc = cleanDescription.toUpperCase(); + if (desc.includes('GATE')) { + itemName = '게이트 밸브'; + } else if (desc.includes('BALL')) { + itemName = '볼 밸브'; + } else if (desc.includes('GLOBE')) { + itemName = '글로브 밸브'; + } else if (desc.includes('CHECK')) { + itemName = '체크 밸브'; + } else if (desc.includes('BUTTERFLY')) { + itemName = '버터플라이 밸브'; + } else if (desc.includes('NEEDLE')) { + itemName = '니들 밸브'; + } else if (desc.includes('RELIEF')) { + itemName = '릴리프 밸브'; } else { - itemName = 'GASKET'; + itemName = '밸브'; } + } + } else if (category === 'GASKET') { + // 가스켓 상세 타입 표시 + const gasketDetails = material.gasket_details || {}; + const gasketType = gasketDetails.gasket_type || ''; + const gasketSubtype = gasketDetails.gasket_subtype || ''; + + if (gasketType === 'SPIRAL_WOUND') { + itemName = '스파이럴 워운드 가스켓'; + } else if (gasketType === 'RING_JOINT') { + itemName = '링 조인트 가스켓'; + } else if (gasketType === 'FULL_FACE') { + itemName = '풀 페이스 가스켓'; + } else if (gasketType === 'RAISED_FACE') { + itemName = '레이즈드 페이스 가스켓'; + } else if (gasketSubtype && gasketSubtype !== gasketType) { + itemName = gasketSubtype; + } else if (gasketType) { + itemName = gasketType; } else { // gasket_details가 없으면 description에서 추출 - if (cleanDescription.includes('SWG') || cleanDescription.includes('SPIRAL')) { - itemName = 'SWG'; - } else if (cleanDescription.includes('RTJ') || cleanDescription.includes('RING')) { - itemName = 'RTJ'; - } else if (cleanDescription.includes('FF') || cleanDescription.includes('FULL FACE')) { - itemName = 'FF'; - } else if (cleanDescription.includes('RF') || cleanDescription.includes('RAISED')) { - itemName = 'RF'; + const desc = cleanDescription.toUpperCase(); + if (desc.includes('SWG') || desc.includes('SPIRAL')) { + itemName = '스파이럴 워운드 가스켓'; + } else if (desc.includes('RTJ') || desc.includes('RING')) { + itemName = '링 조인트 가스켓'; + } else if (desc.includes('FF') || desc.includes('FULL FACE')) { + itemName = '풀 페이스 가스켓'; + } else if (desc.includes('RF') || desc.includes('RAISED')) { + itemName = '레이즈드 페이스 가스켓'; } else { - itemName = 'GASKET'; + itemName = '가스켓'; } } } else if (category === 'BOLT') { - itemName = 'BOLT'; + // 볼트 상세 타입 표시 + const boltDetails = material.bolt_details || {}; + const boltType = boltDetails.bolt_type || ''; + + if (boltType === 'HEX_BOLT') { + itemName = '육각 볼트'; + } else if (boltType === 'STUD_BOLT') { + itemName = '스터드 볼트'; + } else if (boltType === 'U_BOLT') { + itemName = '유볼트'; + } else if (boltType === 'FLANGE_BOLT') { + itemName = '플랜지 볼트'; + } else if (boltType === 'PSV_BOLT') { + itemName = 'PSV 볼트'; + } else if (boltType === 'LT_BOLT') { + itemName = '저온용 볼트'; + } else if (boltType === 'CK_BOLT') { + itemName = '체크밸브용 볼트'; + } else { + // description에서 추출 + const desc = cleanDescription.toUpperCase(); + if (desc.includes('PSV')) { + itemName = 'PSV 볼트'; + } else if (desc.includes('LT')) { + itemName = '저온용 볼트'; + } else if (desc.includes('CK')) { + itemName = '체크밸브용 볼트'; + } else if (desc.includes('STUD')) { + itemName = '스터드 볼트'; + } else if (desc.includes('U-BOLT') || desc.includes('U BOLT')) { + itemName = '유볼트'; + } else { + itemName = '볼트'; + } + } } else if (category === 'SUPPORT' || category === 'U_BOLT') { - // SUPPORT 카테고리: 타입별 구분 + // 서포트 상세 타입 표시 const desc = cleanDescription.toUpperCase(); if (desc.includes('URETHANE') || desc.includes('BLOCK SHOE')) { - itemName = 'URETHANE BLOCK SHOE'; + itemName = '우레탄 블록 슈'; } else if (desc.includes('CLAMP')) { - itemName = 'CLAMP'; + itemName = '클램프'; } else if (desc.includes('U-BOLT') || desc.includes('U BOLT')) { - itemName = 'U-BOLT'; + itemName = '유볼트'; + } else if (desc.includes('HANGER')) { + itemName = '행거'; + } else if (desc.includes('SPRING')) { + itemName = '스프링 서포트'; } else { - itemName = 'SUPPORT'; + itemName = '서포트'; } } else { itemName = category || 'UNKNOWN'; @@ -445,54 +613,138 @@ const formatMaterialForExcel = (material, includeComparison = false) => { } } - // 새로운 엑셀 양식에 맞춘 데이터 구조 + // 새로운 엑셀 양식: A~E 고정, F~O 카테고리별, P 납기일 const base = { - 'TAGNO': '', // 비워둠 - '품목명': itemName, - '수량': quantity, - '통화구분': 'KRW', // 기본값 - '단가': 1, // 일괄 1로 설정 - '크기': material.size_spec || '-', - '압력등급': pressure + 'TAGNO': '', // A열: 비워둠 + '품목명': itemName, // B열: 카테고리별 상세 타입 + '수량': quantity, // C열: 수량 + '통화구분': 'KRW', // D열: 기본값 + '단가': 1 // E열: 일괄 1로 설정 }; - // 카테고리별 전용 컬럼 구성 - if (category === 'GASKET') { - // 가스켓 전용 컬럼 순서 - base['타입/구조'] = grade; // H/F/I/O, SWG 등 (스케줄 대신) - base['재질'] = gasketMaterial || '-'; // SS304/GRAPHITE/SS304/SS304 - base['두께'] = gasketThickness || '-'; // 4.5mm - base['사용자요구'] = material.user_requirement || ''; - base['관리항목8'] = ''; // 빈칸 - base['관리항목9'] = ''; // 빈칸 - base['관리항목10'] = ''; // 빈칸 - base['납기일(YYYY-MM-DD)'] = new Date().toISOString().split('T')[0]; // 가장 마지막 - } else if (category === 'BOLT') { - // 볼트 전용 컬럼 순서 (스케줄 → 길이) - base['길이'] = schedule; // 볼트는 길이 정보 - base['재질'] = grade; - base['추가요구'] = detailInfo || '-'; // 상세내역 → 추가요구로 변경 - base['사용자요구'] = material.user_requirement || ''; - base['관리항목1'] = ''; // 빈칸 - base['관리항목7'] = ''; // 빈칸 - base['관리항목8'] = ''; // 빈칸 - base['관리항목9'] = ''; // 빈칸 - base['관리항목10'] = ''; // 빈칸 - base['납기일(YYYY-MM-DD)'] = new Date().toISOString().split('T')[0]; // 가장 마지막 - } else { - // 다른 카테고리는 기존 방식 - base['스케줄'] = schedule; - base['재질'] = grade; - base['상세내역'] = detailInfo || '-'; - base['사용자요구'] = material.user_requirement || ''; - base['관리항목1'] = ''; // 빈칸 - base['관리항목7'] = ''; // 빈칸 - base['관리항목8'] = ''; // 빈칸 - base['관리항목9'] = ''; // 빈칸 - base['관리항목10'] = ''; // 빈칸 - base['납기일(YYYY-MM-DD)'] = new Date().toISOString().split('T')[0]; // 가장 마지막 + // F~O열: 카테고리별 전용 컬럼 구성 (10개 컬럼) + if (category === 'PIPE') { + // 파이프 전용 컬럼 (F~O) + base['크기'] = material.size_spec || '-'; // F열 + base['압력등급'] = pressure; // G열 + base['스케줄'] = schedule; // H열 + base['재질'] = grade; // I열 + base['제조방법'] = material.pipe_details?.manufacturing_method || '-'; // J열 + base['끝단처리'] = material.pipe_details?.end_preparation || '-'; // K열 + base['사용자요구'] = material.user_requirement || ''; // L열 + base['관리항목1'] = ''; // M열 + base['관리항목2'] = ''; // N열 + base['관리항목3'] = ''; // O열 + } else if (category === 'FITTING') { + // 피팅 전용 컬럼 (F~O) + base['크기'] = material.size_spec || '-'; // F열 + base['압력등급'] = pressure; // G열 + base['타입'] = material.fitting_details?.fitting_type || '-'; // H열 + base['재질'] = grade; // I열 + base['상세내역'] = detailInfo || '-'; // J열 + base['사용자요구'] = material.user_requirement || ''; // K열 + base['관리항목1'] = ''; // L열 + base['관리항목2'] = ''; // M열 + base['관리항목3'] = ''; // N열 + base['관리항목4'] = ''; // O열 + } else if (category === 'FLANGE') { + // 플랜지 타입 풀네임 매핑 (영어) + const flangeTypeMap = { + 'WN': 'WELD NECK FLANGE', + 'WELD_NECK': 'WELD NECK FLANGE', + 'SO': 'SLIP ON FLANGE', + 'SLIP_ON': 'SLIP ON FLANGE', + 'SW': 'SOCKET WELD FLANGE', + 'SOCKET_WELD': 'SOCKET WELD FLANGE', + 'BL': 'BLIND FLANGE', + 'BLIND': 'BLIND FLANGE', + 'RED': 'REDUCING FLANGE', + 'REDUCING': 'REDUCING FLANGE', + 'ORIFICE': 'ORIFICE FLANGE', + 'SPECTACLE': 'SPECTACLE BLIND', + 'PADDLE': 'PADDLE BLIND', + 'SPACER': 'SPACER' + }; + + // 끝단처리 풀네임 매핑 (영어) + const facingTypeMap = { + 'RF': 'RAISED FACE', + 'RAISED_FACE': 'RAISED FACE', + 'FF': 'FULL FACE', + 'FULL_FACE': 'FULL FACE', + 'RTJ': 'RING JOINT', + 'RING_JOINT': 'RING JOINT', + 'MALE': 'MALE', + 'FEMALE': 'FEMALE' + }; + + const rawFlangeType = material.flange_details?.flange_type || ''; + const rawFacingType = material.flange_details?.facing_type || ''; + + // 플랜지 전용 컬럼 (F~O) + base['크기'] = material.size_spec || '-'; // F열 + base['압력등급'] = pressure; // G열 + base['타입'] = flangeTypeMap[rawFlangeType] || rawFlangeType || 'FLANGE'; // H열 + base['재질'] = grade; // I열 + base['페이싱'] = facingTypeMap[rawFacingType] || rawFacingType || '-'; // J열 + base['사용자요구'] = material.user_requirement || ''; // K열 + base['관리항목1'] = ''; // L열 + base['관리항목2'] = ''; // M열 + base['관리항목3'] = ''; // N열 + base['관리항목4'] = ''; // O열 + } else if (category === 'VALVE') { + // 밸브 전용 컬럼 (F~O) + base['크기'] = material.size_spec || '-'; // F열 + base['압력등급'] = pressure; // G열 + base['타입'] = material.valve_details?.valve_type || '-'; // H열 + base['재질'] = grade; // I열 + base['상세내역'] = detailInfo || '-'; // J열 + base['사용자요구'] = material.user_requirement || ''; // K열 + base['관리항목1'] = ''; // L열 + base['관리항목2'] = ''; // M열 + base['관리항목3'] = ''; // N열 + base['관리항목4'] = ''; // O열 + } else if (category === 'GASKET') { + // 가스켓 전용 컬럼 (F~O) + base['크기'] = material.size_spec || '-'; // F열 + base['압력등급'] = pressure; // G열 + base['타입/구조'] = grade; // H열: H/F/I/O, SWG 등 + base['재질'] = gasketMaterial || '-'; // I열: SS304/GRAPHITE/SS304/SS304 + base['두께'] = gasketThickness || '-'; // J열: 4.5mm + base['사용자요구'] = material.user_requirement || ''; // K열 + base['관리항목1'] = ''; // L열 + base['관리항목2'] = ''; // M열 + base['관리항목3'] = ''; // N열 + base['관리항목4'] = ''; // O열 + } else if (category === 'BOLT') { + // 볼트 전용 컬럼 (F~O) + base['크기'] = material.size_spec || '-'; // F열 + base['압력등급'] = pressure; // G열 + base['길이'] = schedule; // H열: 볼트는 길이 정보 + base['재질'] = grade; // I열 + base['추가요구'] = detailInfo || '-'; // J열: 표면처리 등 + base['사용자요구'] = material.user_requirement || ''; // K열 + base['관리항목1'] = ''; // L열 + base['관리항목2'] = ''; // M열 + base['관리항목3'] = ''; // N열 + base['관리항목4'] = ''; // O열 + } else { + // 기타 카테고리 기본 컬럼 (F~O) + base['크기'] = material.size_spec || '-'; // F열 + base['압력등급'] = pressure; // G열 + base['스케줄'] = schedule; // H열 + base['재질'] = grade; // I열 + base['상세내역'] = detailInfo || '-'; // J열 + base['사용자요구'] = material.user_requirement || ''; // K열 + base['관리항목1'] = ''; // L열 + base['관리항목2'] = ''; // M열 + base['관리항목3'] = ''; // N열 + base['관리항목4'] = ''; // O열 } + // P열: 납기일 (고정) + base['납기일(YYYY-MM-DD)'] = new Date().toISOString().split('T')[0]; + // 비교 모드인 경우 추가 정보 if (includeComparison) { if (material.previous_quantity !== undefined) {