o2o-negosium-original/negodata/backend/router/v1/supplier/supplier.py
Mina Choi 7f1c0be2b3 [feat] negodata: 협력사 채팅 계정(supplier_users) 관리 — 발급·비번 재설정·활성상태
- 백엔드: supplier_users/supplier_user_tokens ORM 매핑, /v1/supplier/{id}/account 4종(조회·발급·재설정·상태), 회사 스코프 게이팅, 재설정·비활성 시 토큰 삭제로 기존 로그인 즉시 무효화
- 비밀번호: 발급=서버 자동생성(1회 반환), 재설정=커스텀 지정 가능(미지정 시 자동생성)
- 프론트: 협력사 상세 SupplierAccountManager 섹션 + 목록 '채팅 계정' 컬럼, orval 재생성(SupplierType 은 enumLabels 로컬 상수로 이동)
- 테스트: test_supplier_account.py 9건, 전체 스위트 green
2026-07-10 10:14:00 +09:00

101 lines
5.1 KiB
Python

from uuid import UUID
from fastapi import APIRouter, Depends, Query
from common.models.gmodel import PageParams, UserInfo
from router.v1.validator.dependencies import IsValidAccessToken, RemoveNoneResponse, RequireOwner
from services.supplier_service import SupplierService
from .protocol import (
Req_CheckCodes,
Req_CreateSupplierAccount,
Req_CreateSupplier,
Req_ResetSupplierAccountPassword,
Req_UpdateSupplierAccountStatus,
Req_UpdateSupplier,
Res_SupplierAccount,
Res_CheckCodes,
Res_CreateSupplierAccount,
Res_DeleteSupplier,
Res_ResetSupplierAccountPassword,
Res_Supplier,
Res_SupplierList,
)
# 라우터(컨트롤러). 인증(Depends(IsValidAccessToken))으로 UserInfo 를 받아 company_id 로 스코프.
router = APIRouter(prefix="/v1/supplier", tags=["Supplier"], responses={404: {"description": "Not found"}})
@router.get(path="/list", response_model=Res_SupplierList, summary="협력사 목록")
async def list_suppliers(
service: SupplierService = Depends(),
user_info: UserInfo = Depends(IsValidAccessToken),
search: str | None = Query(None, description="협력사명/코드/담당자명 검색"),
pg: PageParams = Depends(),
):
return RemoveNoneResponse(await service.list_suppliers(user_info.company_id, search, pg))
@router.post(path="/create", response_model=Res_Supplier, summary="협력사 등록")
async def create_supplier(
req: Req_CreateSupplier, service: SupplierService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)
):
return RemoveNoneResponse(
await service.create_supplier(user_info.company_id, user_info.user_id, req)
)
@router.post(path="/check-codes", response_model=Res_CheckCodes, summary="코드 중복 사전검사(업로드 즉시)")
async def check_supplier_codes(
req: Req_CheckCodes, service: SupplierService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)
):
return RemoveNoneResponse(await service.check_codes(user_info.company_id, req.codes))
@router.get(path="/{supplier_id}", response_model=Res_Supplier, summary="협력사 조회")
async def get_supplier(supplier_id: UUID, service: SupplierService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
return RemoveNoneResponse(await service.get_supplier(user_info.company_id, str(supplier_id)))
@router.patch(path="/update/{supplier_id}", response_model=Res_Supplier, summary="협력사 수정")
async def update_supplier(
supplier_id: UUID, req: Req_UpdateSupplier, service: SupplierService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)
):
return RemoveNoneResponse(
await service.update_supplier(user_info.company_id, str(supplier_id), req)
)
@router.delete(path="/delete/{supplier_id}", response_model=Res_DeleteSupplier, summary="협력사 삭제 (최고관리자 전용)")
async def delete_supplier(supplier_id: UUID, service: SupplierService = Depends(), user_info: UserInfo = Depends(RequireOwner)):
# 협력사 명부는 회사 공유 자원 — 파괴적 삭제는 최고관리자만(RequireOwner 가 비-OWNER 를 403 차단).
return RemoveNoneResponse(await service.delete_supplier(user_info.company_id, str(supplier_id)))
# ---- 채팅(협상) 계정 — supplier.supplier_users 관리. 게이팅은 협력사 수정과 동일(회사 스코프).
@router.get(path="/{supplier_id}/account", response_model=Res_SupplierAccount, summary="채팅 계정 조회 (미발급이면 account=null)")
async def get_supplier_account(
supplier_id: UUID, service: SupplierService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)
):
return RemoveNoneResponse(await service.get_supplier_account(user_info.company_id, str(supplier_id)))
@router.post(path="/{supplier_id}/account/create", response_model=Res_CreateSupplierAccount, summary="채팅 계정 발급 (비밀번호 자동생성·1회 반환)")
async def create_supplier_account(
supplier_id: UUID, req: Req_CreateSupplierAccount, service: SupplierService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)
):
return RemoveNoneResponse(await service.create_supplier_account(user_info.company_id, str(supplier_id), req))
@router.post(path="/{supplier_id}/account/reset-password", response_model=Res_ResetSupplierAccountPassword, summary="채팅 계정 비밀번호 재설정 (커스텀 지정 가능, 기존 로그인 즉시 무효화)")
async def reset_supplier_account_password(
supplier_id: UUID, req: Req_ResetSupplierAccountPassword, service: SupplierService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)
):
return RemoveNoneResponse(await service.reset_supplier_account_password(user_info.company_id, str(supplier_id), req))
@router.patch(path="/{supplier_id}/account/status", response_model=Res_SupplierAccount, summary="채팅 계정 활성/비활성 (비활성 시 기존 로그인 즉시 무효화)")
async def update_supplier_account_status(
supplier_id: UUID, req: Req_UpdateSupplierAccountStatus, service: SupplierService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)
):
return RemoveNoneResponse(await service.update_supplier_account_status(user_info.company_id, str(supplier_id), req))