✅ 완성된 기능: - 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 데이터 저장 및 조회 정상
177 lines
5.9 KiB
Python
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)}")
|