- partner.supplier_items 매핑테이블 신설: supply_type(0없음/1유통/2제조/3총판), 부분 유니크 인덱스(soft-delete 인지). quotations.supplier_type와 의미단위 달라 컬럼명 supply_type. - 백엔드: supplier_item CRUD/service/router(/v1/supplier-item, by-supplier·by-item·create·bulk·update·delete). 소유권 company 스코프. ErrorType.SUPPLIER_ITEM_NOT_FOUND 추가. - DDL: 01-schema/04-alter(날짜접미 리네임) + dev DB 반영. - 프론트(feature): 협력사 엑셀 '취급상품' 컬럼(콤마 상품명→일괄매핑, 이름 미매칭 스킵+리포트, 유형 안받고 없음), 협력사 상세 취급상품 관리(추가/삭제/유형변경 즉시반영), 견적모달 협력사 리스트에 선택상품 공급유형 배지. - 재사용 서버검색 Combobox 신설 → 취급상품·견적상품·협력사초청·협상카드 픽리스트에 적용(size 100 캡 해소, 검색은 서버 ILIKE). 견적 선택상품 파생값은 useGetItem 단건조회로 안정화. - orval 재생성(supplier-item 클라이언트/모델). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
78 lines
2.7 KiB
Python
78 lines
2.7 KiB
Python
import time
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.middleware.gzip import GZipMiddleware
|
|
|
|
from common.database.db_session_manager import DB_SESSION_MNG
|
|
from common.logger import LOG
|
|
from common.utils.gtime import GTime
|
|
from config.server_configs import web_server_config
|
|
from scheduler import shutdown_scheduler, start_scheduler
|
|
import router.v1.auth.account
|
|
import router.v1.company.user
|
|
import router.v1.item.item
|
|
import router.v1.supplier.supplier
|
|
import router.v1.supplier_item.supplier_item
|
|
import router.v1.card.card
|
|
import router.v1.quotation.quotation
|
|
import router.v1.quotation_setting.quotation_setting
|
|
import router.v1.dashboard.dashboard
|
|
import router.v1.notification.notification
|
|
|
|
API_SERVER_START_TIME = GTime.UTCStr()
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
# startup: 마감 스케줄러 기동(SCHEDULER_ENABLED=1 인 프로세스에서만)
|
|
start_scheduler()
|
|
yield
|
|
# shutdown: 스케줄러 정지 + DB 엔진 커넥션 풀 정리
|
|
shutdown_scheduler()
|
|
await DB_SESSION_MNG.dispose_all()
|
|
|
|
|
|
app = FastAPI(title="Negodata Api Server", lifespan=lifespan)
|
|
|
|
# CORS
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=[web_server_config.client_url],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# Accept-Encoding: gzip 요청에 대해 1000 bytes 이상 응답을 압축.
|
|
app.add_middleware(GZipMiddleware, minimum_size=1000)
|
|
|
|
|
|
@app.middleware("http")
|
|
async def log_time(request: Request, call_next):
|
|
start_time = time.time()
|
|
response = await call_next(request)
|
|
elapsed = time.time() - start_time
|
|
# status_code 를 함께 남긴다(403/4xx 등을 로그만으로 식별 가능하게).
|
|
LOG.d(f"{response.status_code} {request.method} {request.url.path} - {elapsed:.4f}s")
|
|
return response
|
|
|
|
|
|
@app.get(path="/healthz", responses={404: {"description": "Not found"}})
|
|
async def healthz():
|
|
return API_SERVER_START_TIME
|
|
|
|
|
|
# 각 도메인 라우터를 등록한다. 새 기능 추가 시 router.v1.<domain>.<file> 를 import 후 include.
|
|
app.include_router(router.v1.auth.account.router)
|
|
app.include_router(router.v1.company.user.router)
|
|
app.include_router(router.v1.item.item.router)
|
|
app.include_router(router.v1.supplier.supplier.router)
|
|
app.include_router(router.v1.supplier_item.supplier_item.router)
|
|
app.include_router(router.v1.card.card.router)
|
|
app.include_router(router.v1.quotation.quotation.router)
|
|
app.include_router(router.v1.quotation_setting.quotation_setting.router)
|
|
app.include_router(router.v1.dashboard.dashboard.router)
|
|
app.include_router(router.v1.notification.notification.router)
|