라우터가 req.model_dump(exclude_unset=True)로 dict를 만들어 넘기던 것을 req(Req_*) 객체 그대로 전달하도록 변경. 서비스 시그니처를 전부 타입드로 통일. - create 5개(quotation/card/item/supplier/quotation_setting): ORM은 명시 kwargs 조립(item만 컬럼 16개라 model_dump 펼침). 경계 검증·타입 유지, **data 결합 제거. - update 4개: 서비스가 req 받아 내부에서 model_dump(exclude_unset=True) 생성 후 CRUD(dict)로 전달 — 공식 PATCH 메커니즘 유지. - 서비스 계층 data: dict 시그니처 0개. CRUD는 dict 유지(의도). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
160 lines
6.3 KiB
Python
160 lines
6.3 KiB
Python
import uuid
|
|
|
|
from fastapi import Depends
|
|
|
|
from common.database.db_session_manager import DB_SESSION_MNG
|
|
from common.database.model.models import suppliers
|
|
from common.enums import DBWRType, ErrorType
|
|
from common.logger import LOG
|
|
from common.models.gmodel import PageParams
|
|
from crud.supplier_crud import ISupplierCRUD, SupplierCRUD
|
|
from router.v1.supplier.protocol import (
|
|
Req_CreateSupplier,
|
|
Req_UpdateSupplier,
|
|
Res_CheckCodes,
|
|
Res_DeleteSupplier,
|
|
Res_Supplier,
|
|
Res_SupplierList,
|
|
SupplierData,
|
|
)
|
|
|
|
|
|
class SupplierService:
|
|
"""협력사 비즈니스 로직. company_id 로 소유권을 확인한다(멀티테넌트)."""
|
|
|
|
def __init__(self, supplier_crud: ISupplierCRUD = Depends(SupplierCRUD)):
|
|
self.supplier_crud = supplier_crud
|
|
|
|
async def _fetch_owned(self, company_uuid: uuid.UUID, supplier_id: uuid.UUID):
|
|
"""supplier 조회 + 소유권 확인. (ErrorType, supplier|None) 반환."""
|
|
err_type, supplier = await DB_SESSION_MNG.execute_lambda(
|
|
suppliers.DBType(),
|
|
DBWRType.DB_READ.value,
|
|
lambda s: self.supplier_crud.get_by_id(s, supplier_id),
|
|
)
|
|
if err_type != ErrorType.SUCCESS or supplier is None:
|
|
return ErrorType.SUPPLIER_NOT_FOUND, None
|
|
if supplier.company_id != company_uuid:
|
|
return ErrorType.SUPPLIER_NOT_FOUND, None
|
|
return ErrorType.SUCCESS, supplier
|
|
|
|
async def list_suppliers(self, company_id: str, search, priority, pg: PageParams) -> Res_SupplierList:
|
|
res = Res_SupplierList(page=pg.page, size=pg.size)
|
|
company_uuid = uuid.UUID(company_id)
|
|
|
|
err_type, rows, total = await DB_SESSION_MNG.execute_lambda(
|
|
suppliers.DBType(),
|
|
DBWRType.DB_READ.value,
|
|
lambda s: self.supplier_crud.search(s, company_uuid, search, priority, pg.skip, pg.size),
|
|
)
|
|
if err_type != ErrorType.SUCCESS:
|
|
res.result.SetResult(err_type)
|
|
return res
|
|
res.suppliers = [SupplierData.model_validate(r) for r in rows]
|
|
res.total = total
|
|
return res
|
|
|
|
async def get_supplier(self, company_id: str, supplier_id: str) -> Res_Supplier:
|
|
res = Res_Supplier()
|
|
err_type, supplier = await self._fetch_owned(uuid.UUID(company_id), uuid.UUID(supplier_id))
|
|
if err_type != ErrorType.SUCCESS:
|
|
res.result.SetResult(err_type)
|
|
return res
|
|
res.supplier = SupplierData.model_validate(supplier)
|
|
return res
|
|
|
|
async def check_codes(self, company_id: str, codes: list) -> Res_CheckCodes:
|
|
"""업로드 즉시 호출: codes 중 같은 회사 DB 에 이미 있는 코드를 돌려준다(미리보기 사전검사)."""
|
|
res = Res_CheckCodes()
|
|
company_uuid = uuid.UUID(company_id)
|
|
err_type, existing = await DB_SESSION_MNG.execute_lambda(
|
|
suppliers.DBType(),
|
|
DBWRType.DB_READ.value,
|
|
lambda s: self.supplier_crud.existing_codes(s, company_uuid, codes),
|
|
)
|
|
if err_type != ErrorType.SUCCESS:
|
|
res.result.SetResult(err_type)
|
|
return res
|
|
res.existing = list(existing)
|
|
return res
|
|
|
|
async def create_supplier(self, company_id: str, user_id: str, req: Req_CreateSupplier) -> Res_Supplier:
|
|
res = Res_Supplier()
|
|
company_uuid = uuid.UUID(company_id)
|
|
|
|
# DB 중복코드 검증: 같은 회사에 동일 code 가 이미 있으면 거부(프론트는 받아온 목록만 보므로 여기서 최종 차단).
|
|
code = req.code
|
|
if code:
|
|
dup_err, exists = await DB_SESSION_MNG.execute_lambda(
|
|
suppliers.DBType(),
|
|
DBWRType.DB_READ.value,
|
|
lambda s: self.supplier_crud.code_exists(s, company_uuid, code),
|
|
)
|
|
if dup_err != ErrorType.SUCCESS:
|
|
res.result.SetResult(dup_err)
|
|
return res
|
|
if exists:
|
|
res.result.SetResult(ErrorType.SUPPLIER_CODE_DUPLICATE)
|
|
return res
|
|
|
|
supplier = suppliers(
|
|
company_id=company_uuid,
|
|
user_id=uuid.UUID(user_id),
|
|
name=req.name,
|
|
code=req.code,
|
|
manager_name=req.manager_name,
|
|
manager_email=req.manager_email,
|
|
manager_contact_number=req.manager_contact_number,
|
|
priority=req.priority,
|
|
)
|
|
err_type = await DB_SESSION_MNG.execute_lambda_run(
|
|
[suppliers.DBType()],
|
|
[lambda s: self.supplier_crud.add_supplier(s, supplier)],
|
|
)
|
|
if err_type != ErrorType.SUCCESS:
|
|
res.result.SetResult(err_type)
|
|
return res
|
|
# 서버 기본값(created_at/updated_at)은 insert 후 객체에 실리지 않으므로 재조회한다.
|
|
return await self.get_supplier(company_id, str(supplier.supplier_id))
|
|
|
|
async def update_supplier(self, company_id: str, supplier_id: str, req: Req_UpdateSupplier) -> Res_Supplier:
|
|
res = Res_Supplier()
|
|
company_uuid = uuid.UUID(company_id)
|
|
supplier_uuid = uuid.UUID(supplier_id)
|
|
data = req.model_dump(exclude_unset=True)
|
|
|
|
# 소유권 확인
|
|
err_type, _ = await self._fetch_owned(company_uuid, supplier_uuid)
|
|
if err_type != ErrorType.SUCCESS:
|
|
res.result.SetResult(err_type)
|
|
return res
|
|
|
|
err_type = await DB_SESSION_MNG.execute_lambda_run(
|
|
[suppliers.DBType()],
|
|
[lambda s: self.supplier_crud.update_supplier(s, supplier_uuid, data)],
|
|
)
|
|
if err_type != ErrorType.SUCCESS:
|
|
res.result.SetResult(err_type)
|
|
return res
|
|
|
|
# 갱신 후 재조회
|
|
return await self.get_supplier(company_id, supplier_id)
|
|
|
|
async def delete_supplier(self, company_id: str, supplier_id: str) -> Res_DeleteSupplier:
|
|
res = Res_DeleteSupplier()
|
|
company_uuid = uuid.UUID(company_id)
|
|
supplier_uuid = uuid.UUID(supplier_id)
|
|
|
|
err_type, _ = await self._fetch_owned(company_uuid, supplier_uuid)
|
|
if err_type != ErrorType.SUCCESS:
|
|
res.result.SetResult(err_type)
|
|
return res
|
|
|
|
err_type = await DB_SESSION_MNG.execute_lambda_run(
|
|
[suppliers.DBType()],
|
|
[lambda s: self.supplier_crud.soft_delete(s, supplier_uuid)],
|
|
)
|
|
if err_type != ErrorType.SUCCESS:
|
|
res.result.SetResult(err_type)
|
|
return res
|