o2o-negosium-original/negodata/backend/router/v1/supplier_item/supplier_item.py
Mina Choi 43dbcbcd96 [feat] negodata: 협력사↔상품 매핑(취급상품·공급유형) + 견적 픽리스트 서버검색 콤보박스
- 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>
2026-07-07 09:59:13 +09:00

62 lines
3.0 KiB
Python

from uuid import UUID
from fastapi import APIRouter, Depends
from common.models.gmodel import Res_WebPacketProtocol, UserInfo
from router.v1.validator.dependencies import IsValidAccessToken, RemoveNoneResponse
from services.supplier_item_service import SupplierItemService
from .protocol import (
Req_BulkMapByNames,
Req_CreateSupplierItem,
Req_UpdateSupplyType,
Res_BulkMapByNames,
Res_ItemSupplyTypeList,
Res_SupplierItem,
Res_SupplierItemList,
)
# 협력사-상품 매핑 라우터. company_id 스코프는 상위 협력사/상품 소유권으로 확인.
router = APIRouter(prefix="/v1/supplier-item", tags=["SupplierItem"], responses={404: {"description": "Not found"}})
@router.get(path="/by-supplier/{supplier_id}", response_model=Res_SupplierItemList, summary="협력사 취급상품 목록")
async def list_supplier_items(
supplier_id: UUID, service: SupplierItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)
):
return RemoveNoneResponse(await service.list_by_supplier(user_info.company_id, str(supplier_id)))
@router.get(path="/by-item/{item_id}", response_model=Res_ItemSupplyTypeList, summary="상품 취급 협력사 공급유형 목록")
async def list_item_supply_types(
item_id: UUID, service: SupplierItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)
):
return RemoveNoneResponse(await service.list_by_item(user_info.company_id, str(item_id)))
@router.post(path="/create", response_model=Res_SupplierItem, summary="취급상품 매핑 추가")
async def create_supplier_item(
req: Req_CreateSupplierItem, service: SupplierItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)
):
return RemoveNoneResponse(await service.create(user_info.company_id, req))
@router.post(path="/by-supplier/{supplier_id}/bulk", response_model=Res_BulkMapByNames, summary="취급상품 이름 일괄 매핑(엑셀 업로드)")
async def bulk_map_supplier_items(
supplier_id: UUID, req: Req_BulkMapByNames, service: SupplierItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)
):
return RemoveNoneResponse(await service.bulk_map_by_names(user_info.company_id, str(supplier_id), req.names))
@router.patch(path="/update/{supplier_item_id}", response_model=Res_WebPacketProtocol, summary="취급상품 공급유형 수정")
async def update_supply_type(
supplier_item_id: UUID, req: Req_UpdateSupplyType, service: SupplierItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)
):
return RemoveNoneResponse(await service.update_type(user_info.company_id, str(supplier_item_id), req))
@router.delete(path="/delete/{supplier_item_id}", response_model=Res_WebPacketProtocol, summary="취급상품 매핑 삭제")
async def delete_supplier_item(
supplier_item_id: UUID, service: SupplierItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)
):
return RemoveNoneResponse(await service.delete(user_info.company_id, str(supplier_item_id)))