Files
TK-BOM-Project/backend/app/routers/jobs.py
Hyungi Ahn c9e0d90de4 feat: Job 관리 시스템 및 BOM 연동 완성
 완성된 기능:
- Job 관리 CRUD API 구현 (생성/조회/수정/삭제)
- PostgreSQL jobs 테이블 생성 및 더미 데이터
- files.py project_id → job_no 변경으로 완전 통합
- Job 검증 로직으로 업로드 시 유효성 확인
- Job-Files-Materials 3단계 데이터 연동 완료

📁 추가된 파일:
- scripts/create_jobs.sql: jobs 테이블 스키마
- scripts/insert_dummy_jobs.py: 더미 데이터 생성
- app/routers/jobs.py: Job 관리 API
- app/routers/files.py: BOM 업로드 (job_no 연동)

🚀 다음 단계:
- 자재 분류 시스템 통합 (classification.py)
- 검토 시스템 구현 (행별 분류 확인/수정)
- Job별 자재 통계 및 진행률 API
- 프론트엔드 UI 개발

🎯 테스트 완료:
- J24-001 Job에 BOM 파일 업로드 성공
- Job 검증 및 오류 처리 작동 확인
- PostgreSQL 데이터 저장 및 조회 정상
2025-07-15 13:26:03 +09:00

177 lines
5.9 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import text
from typing import Optional
from datetime import datetime, date
from pydantic import BaseModel
from ..database import get_db
router = APIRouter()
# Pydantic 모델들
class JobCreate(BaseModel):
job_no: str
job_name: str
client_name: str
end_user: Optional[str] = None
epc_company: Optional[str] = None
project_site: Optional[str] = None
contract_date: Optional[date] = None
delivery_date: Optional[date] = None
delivery_terms: Optional[str] = None
description: Optional[str] = None
@router.get("/")
async def get_jobs(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
search: Optional[str] = Query(None),
db: Session = Depends(get_db)
):
"""Job 목록 조회"""
try:
query = """
SELECT job_no, job_name, client_name, end_user, epc_company,
project_site, contract_date, delivery_date, delivery_terms,
status, description, created_by, created_at, updated_at, is_active
FROM jobs
WHERE is_active = true
"""
params = {}
if search:
query += " AND (job_no ILIKE :search OR job_name ILIKE :search OR client_name ILIKE :search)"
params["search"] = f"%{search}%"
query += " ORDER BY created_at DESC LIMIT :limit OFFSET :skip"
params["limit"] = limit
params["skip"] = skip
result = db.execute(text(query), params)
jobs = result.fetchall()
return {
"success": True,
"total_count": len(jobs),
"jobs": [
{
"job_no": job.job_no,
"job_name": job.job_name,
"client_name": job.client_name,
"end_user": job.end_user,
"epc_company": job.epc_company,
"project_site": job.project_site,
"contract_date": job.contract_date,
"delivery_date": job.delivery_date,
"delivery_terms": job.delivery_terms,
"status": job.status,
"description": job.description,
"created_at": job.created_at
}
for job in jobs
]
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Job 목록 조회 실패: {str(e)}")
@router.get("/{job_no}")
async def get_job(job_no: str, db: Session = Depends(get_db)):
"""Job 상세 정보 조회"""
try:
query = text("""
SELECT job_no, job_name, client_name, end_user, epc_company,
project_site, contract_date, delivery_date, delivery_terms,
status, description, created_by, created_at, updated_at, is_active
FROM jobs
WHERE job_no = :job_no AND is_active = true
""")
result = db.execute(query, {"job_no": job_no})
job = result.fetchone()
if not job:
raise HTTPException(status_code=404, detail="Job을 찾을 수 없습니다")
return {
"success": True,
"job": {
"job_no": job.job_no,
"job_name": job.job_name,
"client_name": job.client_name,
"end_user": job.end_user,
"epc_company": job.epc_company,
"project_site": job.project_site,
"contract_date": job.contract_date,
"delivery_date": job.delivery_date,
"delivery_terms": job.delivery_terms,
"status": job.status,
"description": job.description,
"created_by": job.created_by,
"created_at": job.created_at
}
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Job 조회 실패: {str(e)}")
@router.post("/")
async def create_job(job: JobCreate, db: Session = Depends(get_db)):
"""새 Job 생성"""
try:
# Job No. 중복 확인
check_query = text("SELECT job_no FROM jobs WHERE job_no = :job_no")
existing = db.execute(check_query, {"job_no": job.job_no}).fetchone()
if existing:
raise HTTPException(
status_code=400,
detail=f"Job No. '{job.job_no}'가 이미 존재합니다"
)
# 새 Job 생성
insert_query = text("""
INSERT INTO jobs (
job_no, job_name, client_name, end_user, epc_company,
project_site, contract_date, delivery_date, delivery_terms,
description, created_by, status, is_active
)
VALUES (
:job_no, :job_name, :client_name, :end_user, :epc_company,
:project_site, :contract_date, :delivery_date, :delivery_terms,
:description, :created_by, :status, :is_active
)
RETURNING job_no, job_name, client_name
""")
result = db.execute(insert_query, {
**job.dict(),
"created_by": "admin",
"status": "진행중",
"is_active": True
})
new_job = result.fetchone()
db.commit()
return {
"success": True,
"message": "Job이 성공적으로 생성되었습니다",
"job": {
"job_no": new_job.job_no,
"job_name": new_job.job_name,
"client_name": new_job.client_name
}
}
except HTTPException:
db.rollback()
raise
except Exception as e:
db.rollback()
raise HTTPException(status_code=500, detail=f"Job 생성 실패: {str(e)}")