[refactor] negodata/backend: 서비스 계층 입력을 dict→타입드 Req 패킷으로 통일
라우터가 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>
This commit is contained in:
parent
fd2e5c1cc2
commit
d47476dc04
@ -30,7 +30,7 @@ async def list_cards(
|
||||
@router.post(path="/create", response_model=Res_Card, summary="협상카드 등록")
|
||||
async def create_card(req: Req_CreateCard, service: CardService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
return RemoveNoneResponse(
|
||||
await service.create_card(user_info.user_id, req.model_dump(exclude_unset=True))
|
||||
await service.create_card(user_info.user_id, req)
|
||||
)
|
||||
|
||||
|
||||
@ -43,7 +43,7 @@ async def get_card(card_id: UUID, service: CardService = Depends(), user_info: U
|
||||
async def update_card(
|
||||
card_id: UUID, req: Req_UpdateCard, service: CardService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)
|
||||
):
|
||||
return RemoveNoneResponse(await service.update_card(user_info.user_id, str(card_id), req.model_dump(exclude_unset=True)))
|
||||
return RemoveNoneResponse(await service.update_card(user_info.user_id, str(card_id), req))
|
||||
|
||||
|
||||
@router.delete(path="/delete/{card_id}", response_model=Res_DeleteCard, summary="협상카드 삭제")
|
||||
|
||||
@ -42,7 +42,7 @@ async def list_item_categories(service: ItemService = Depends(), user_info: User
|
||||
@router.post(path="/create", response_model=Res_Item, summary="상품 등록")
|
||||
async def create_item(req: Req_CreateItem, service: ItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
return RemoveNoneResponse(
|
||||
await service.create_item(user_info.company_id, user_info.user_id, req.model_dump(exclude_unset=True))
|
||||
await service.create_item(user_info.company_id, user_info.user_id, req)
|
||||
)
|
||||
|
||||
|
||||
@ -69,7 +69,7 @@ async def get_item(item_id: UUID, service: ItemService = Depends(), user_info: U
|
||||
async def update_item(
|
||||
item_id: UUID, req: Req_UpdateItem, service: ItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)
|
||||
):
|
||||
return RemoveNoneResponse(await service.update_item(user_info.company_id, str(item_id), req.model_dump(exclude_unset=True)))
|
||||
return RemoveNoneResponse(await service.update_item(user_info.company_id, str(item_id), req))
|
||||
|
||||
|
||||
@router.delete(path="/delete/{item_id}", response_model=Res_DeleteItem, summary="상품 삭제")
|
||||
|
||||
@ -45,7 +45,7 @@ async def list_quotations(
|
||||
async def create_quotation(
|
||||
req: Req_CreateQuotation, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)
|
||||
):
|
||||
return RemoveNoneResponse(await service.create_quotation(user_info.user_id, req.model_dump(exclude_unset=True)))
|
||||
return RemoveNoneResponse(await service.create_quotation(user_info.user_id, req))
|
||||
|
||||
|
||||
@router.post(path="/stop/{qt_id}", response_model=Res_Quotation, summary="견적 마감")
|
||||
|
||||
@ -30,7 +30,7 @@ async def create_setting(
|
||||
service: QuotationSettingService = Depends(),
|
||||
user_info: UserInfo = Depends(IsValidAccessToken),
|
||||
):
|
||||
return RemoveNoneResponse(await service.create_setting(user_info.user_id, req.model_dump(exclude_unset=True)))
|
||||
return RemoveNoneResponse(await service.create_setting(user_info.user_id, req))
|
||||
|
||||
|
||||
@router.patch(path="/update/{qt_setting_id}", response_model=Res_QuotationSetting, summary="견적 설정 수정")
|
||||
@ -41,7 +41,7 @@ async def update_setting(
|
||||
user_info: UserInfo = Depends(IsValidAccessToken),
|
||||
):
|
||||
return RemoveNoneResponse(
|
||||
await service.update_setting(user_info.user_id, str(qt_setting_id), req.model_dump(exclude_unset=True))
|
||||
await service.update_setting(user_info.user_id, str(qt_setting_id), req)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@ 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.model_dump(exclude_unset=True))
|
||||
await service.create_supplier(user_info.company_id, user_info.user_id, req)
|
||||
)
|
||||
|
||||
|
||||
@ -56,7 +56,7 @@ 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.model_dump(exclude_unset=True))
|
||||
await service.update_supplier(user_info.company_id, str(supplier_id), req)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ from common.database.model.models import nego_cards, wild_cards
|
||||
from common.enums import CardStatus, DBWRType, ErrorType
|
||||
from common.models.gmodel import PageParams
|
||||
from crud.card_crud import ICardCRUD, CardCRUD
|
||||
from router.v1.card.protocol import CardData, Res_Card, Res_CardList, Res_DeleteCard
|
||||
from router.v1.card.protocol import CardData, Req_CreateCard, Req_UpdateCard, Res_Card, Res_CardList, Res_DeleteCard
|
||||
|
||||
|
||||
class CardService:
|
||||
@ -135,24 +135,24 @@ class CardService:
|
||||
return res
|
||||
|
||||
# ---- 등록 ----------------------------------------------------------------
|
||||
async def create_card(self, user_id: str, data: dict) -> Res_Card:
|
||||
async def create_card(self, user_id: str, req: Req_CreateCard) -> Res_Card:
|
||||
res = Res_Card()
|
||||
user_uuid = uuid.UUID(user_id)
|
||||
is_wildcard = bool(data.get("is_wildcard", False))
|
||||
is_wildcard = req.is_wildcard
|
||||
|
||||
common = dict(
|
||||
user_id=user_uuid,
|
||||
name=data.get("name"),
|
||||
number=data.get("number"),
|
||||
script=data.get("script"),
|
||||
edit_script=data.get("edit_script"),
|
||||
name=req.name,
|
||||
number=req.number,
|
||||
script=req.script,
|
||||
edit_script=req.edit_script,
|
||||
)
|
||||
if is_wildcard:
|
||||
card = wild_cards(
|
||||
**common,
|
||||
condition=data.get("condition"),
|
||||
available=(data.get("status", CardStatus.ACTIVE.value) == CardStatus.ACTIVE.value),
|
||||
memo=data.get("memo"),
|
||||
condition=req.condition,
|
||||
available=(req.status == CardStatus.ACTIVE.value),
|
||||
memo=req.memo,
|
||||
)
|
||||
model, pk_attr = wild_cards, "wild_card_id"
|
||||
else:
|
||||
@ -170,10 +170,11 @@ class CardService:
|
||||
return await self.get_card(user_id, str(getattr(card, pk_attr)))
|
||||
|
||||
# ---- 수정 ----------------------------------------------------------------
|
||||
async def update_card(self, user_id: str, card_id: str, data: dict) -> Res_Card:
|
||||
async def update_card(self, user_id: str, card_id: str, req: Req_UpdateCard) -> Res_Card:
|
||||
res = Res_Card()
|
||||
user_uuid = uuid.UUID(user_id)
|
||||
card_uuid = uuid.UUID(card_id)
|
||||
data = req.model_dump(exclude_unset=True)
|
||||
|
||||
err, model, pk_col, _row, is_wild = await self._find_owned(user_uuid, card_uuid)
|
||||
if err != ErrorType.SUCCESS:
|
||||
|
||||
@ -13,6 +13,8 @@ from crud.item_crud import IItemCRUD, ItemCRUD
|
||||
from router.v1.item.protocol import (
|
||||
ItemCategory,
|
||||
ItemData,
|
||||
Req_CreateItem,
|
||||
Req_UpdateItem,
|
||||
Res_CheckCodes,
|
||||
Res_DeleteItem,
|
||||
Res_Item,
|
||||
@ -96,12 +98,12 @@ class ItemService:
|
||||
res.existing = list(existing)
|
||||
return res
|
||||
|
||||
async def create_item(self, company_id: str, user_id: str, data: dict) -> Res_Item:
|
||||
async def create_item(self, company_id: str, user_id: str, req: Req_CreateItem) -> Res_Item:
|
||||
res = Res_Item()
|
||||
company_uuid = uuid.UUID(company_id)
|
||||
|
||||
# DB 중복코드 검증: 같은 회사에 동일 code 가 이미 있으면 거부(프론트는 받아온 목록만 보므로 여기서 최종 차단).
|
||||
code = data.get("code")
|
||||
code = req.code
|
||||
if code:
|
||||
dup_err, exists = await DB_SESSION_MNG.execute_lambda(
|
||||
items.DBType(),
|
||||
@ -115,7 +117,7 @@ class ItemService:
|
||||
res.result.SetResult(ErrorType.ITEM_CODE_DUPLICATE)
|
||||
return res
|
||||
|
||||
item = items(**data, company_id=company_uuid, user_id=uuid.UUID(user_id))
|
||||
item = items(**req.model_dump(exclude_unset=True), company_id=company_uuid, user_id=uuid.UUID(user_id))
|
||||
err_type = await DB_SESSION_MNG.execute_lambda_run(
|
||||
[items.DBType()],
|
||||
[lambda s: self.item_crud.add_item(s, item)],
|
||||
@ -126,10 +128,11 @@ class ItemService:
|
||||
# 서버 기본값(created_at/updated_at)은 insert 후 Python 객체에 실리지 않으므로 재조회한다.
|
||||
return await self.get_item(company_id, str(item.item_id))
|
||||
|
||||
async def update_item(self, company_id: str, item_id: str, data: dict) -> Res_Item:
|
||||
async def update_item(self, company_id: str, item_id: str, req: Req_UpdateItem) -> Res_Item:
|
||||
res = Res_Item()
|
||||
company_uuid = uuid.UUID(company_id)
|
||||
item_uuid = uuid.UUID(item_id)
|
||||
data = req.model_dump(exclude_unset=True)
|
||||
|
||||
# 소유권 확인
|
||||
err_type, _ = await self._fetch_owned(company_uuid, item_uuid)
|
||||
|
||||
@ -11,11 +11,11 @@ from common.utils.gtime import GTime
|
||||
from config.server_configs import web_server_config
|
||||
from crud.quotation_crud import IQuotationCRUD, QuotationCRUD
|
||||
from router.v1.quotation.protocol import (
|
||||
AsyncJob,
|
||||
ChatMessageData,
|
||||
QuotationCardData,
|
||||
QuotationData,
|
||||
SessionData,
|
||||
Req_CreateQuotation,
|
||||
Res_CreateQuotation,
|
||||
Res_DeleteQuotation,
|
||||
Res_Quotation,
|
||||
@ -133,28 +133,22 @@ class QuotationService:
|
||||
res.quotation = QuotationData.model_validate(quotation)
|
||||
return res
|
||||
|
||||
async def create_quotation(self, user_id: str, data: dict) -> Res_CreateQuotation:
|
||||
async def create_quotation(self, user_id: str, req: Req_CreateQuotation) -> Res_CreateQuotation:
|
||||
res = Res_CreateQuotation()
|
||||
|
||||
# 세션 생성용 입력은 quotations 컬럼이 아니므로 분리한다(상품 × 공급사 조합마다 세션 1개).
|
||||
item_ids = data.pop("item_ids", []) or []
|
||||
supplier_ids = data.pop("supplier_ids", []) or []
|
||||
card_ids = data.pop("card_ids", []) or []
|
||||
item_ids = req.item_ids
|
||||
supplier_ids = req.supplier_ids
|
||||
card_ids = req.card_ids
|
||||
|
||||
# NOT NULL 컬럼 보정(프론트 미전송 시 서버 디폴트).
|
||||
if not data.get("version_id"):
|
||||
data["version_id"] = self.DEFAULT_VERSION_ID
|
||||
if not data.get("start_time"):
|
||||
data["start_time"] = GTime.UTC()
|
||||
if not data.get("number"):
|
||||
data["number"] = self._gen_number()
|
||||
if not data.get("status"):
|
||||
data["status"] = QuotationStatus.CREATED.value
|
||||
if not data.get("round"):
|
||||
data["round"] = 1 # ORM 기본값은 flush 시점이라, 세션 스냅샷용으로 미리 확정한다
|
||||
version_id = req.version_id or self.DEFAULT_VERSION_ID
|
||||
number = req.number or self._gen_number()
|
||||
status = req.status or QuotationStatus.CREATED.value
|
||||
round_ = req.round or 1 # ORM 기본값은 flush 시점이라, 세션 스냅샷용으로 미리 확정한다
|
||||
# DB 컬럼이 naive 라, tz-aware 로 들어온 시각(프론트 toISOString)을 UTC naive 로 맞춘다.
|
||||
data["start_time"] = self._naive_utc(data.get("start_time"))
|
||||
data["end_time"] = self._naive_utc(data.get("end_time"))
|
||||
start_time = self._naive_utc(req.start_time or GTime.UTC())
|
||||
end_time = self._naive_utc(req.end_time)
|
||||
|
||||
# 세션 목표가 입력(상품 단가 + 견적 세팅 목표 마진율). 읽기 트랜잭션에서 먼저 조회.
|
||||
prices = {}
|
||||
@ -168,7 +162,7 @@ class QuotationService:
|
||||
_err, margin = await DB_SESSION_MNG.execute_lambda(
|
||||
quotations.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.quotation_crud.get_target_margin(s, data["qt_setting_id"]),
|
||||
lambda s: self.quotation_crud.get_target_margin(s, req.qt_setting_id),
|
||||
)
|
||||
margin = margin if _err == ErrorType.SUCCESS else None
|
||||
|
||||
@ -188,7 +182,7 @@ class QuotationService:
|
||||
version_id=new_version_id,
|
||||
user_id=uuid.UUID(user_id),
|
||||
code=0,
|
||||
name=(data.get("number") or "견적버전")[:10],
|
||||
name=(number or "견적버전")[:10],
|
||||
)
|
||||
for cid in card_ids:
|
||||
t = card_types.get(cid)
|
||||
@ -196,11 +190,27 @@ class QuotationService:
|
||||
link_rows.append(version_nego_cards(version_id=new_version_id, nego_card_id=cid))
|
||||
elif t == 2:
|
||||
link_rows.append(version_wild_cards(version_id=new_version_id, wild_card_id=cid))
|
||||
data["version_id"] = new_version_id
|
||||
version_id = new_version_id
|
||||
|
||||
# qt_id 를 미리 발급해 세션 FK(quotation_id)와 묶고, 한 트랜잭션에 함께 insert 한다.
|
||||
qt_id = uuid.uuid4()
|
||||
quotation = quotations(**data, qt_id=qt_id, user_id=uuid.UUID(user_id))
|
||||
quotation = quotations(
|
||||
qt_id=qt_id,
|
||||
user_id=uuid.UUID(user_id),
|
||||
qt_setting_id=req.qt_setting_id,
|
||||
version_id=version_id,
|
||||
name=req.name,
|
||||
number=number,
|
||||
type=req.type,
|
||||
status=status,
|
||||
round=round_,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
manager_name=req.manager_name,
|
||||
manager_email=req.manager_email,
|
||||
manager_contact_number=req.manager_contact_number,
|
||||
memo=req.memo,
|
||||
)
|
||||
|
||||
session_objs = []
|
||||
for iid in item_ids:
|
||||
@ -236,32 +246,10 @@ class QuotationService:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
|
||||
# 서버 기본값(created_at/updated_at) 로드 위해 재조회.
|
||||
f_err, fresh = await DB_SESSION_MNG.execute_lambda(
|
||||
quotations.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.quotation_crud.get_by_id(s, qt_id),
|
||||
)
|
||||
res.quotation = QuotationData.model_validate(fresh if f_err == ErrorType.SUCCESS and fresh is not None else quotation)
|
||||
|
||||
# 생성된 세션 + 각 세션 chat 실행 URL 을 함께 반환(협상리스트에서 바로 진입 가능).
|
||||
res.sessions = [
|
||||
SessionData(
|
||||
session_id=so.session_id,
|
||||
qt_id=so.quotation_id,
|
||||
supplier_id=so.supplier_id,
|
||||
item_id=so.item_id,
|
||||
qt_number=so.qt_number,
|
||||
qt_round=so.qt_round,
|
||||
qt_type=so.qt_type,
|
||||
target_price=so.target_price,
|
||||
status=so.status,
|
||||
end_time=so.end_time,
|
||||
url=self._session_chat_url(so.session_id),
|
||||
)
|
||||
for so in session_objs
|
||||
]
|
||||
res.async_job = AsyncJob(status="created", message=f"협상 세션 {len(session_objs)}건 생성")
|
||||
# 프론트는 생성 응답 본문을 화면에 쓰지 않고 qt_id 로 목록/상세를 재조회한다.
|
||||
# 그래서 새 견적 id 와 세션 수만 돌려준다(재조회 get_by_id·세션 풀바디·url 생략).
|
||||
res.qt_id = qt_id
|
||||
res.session_count = len(session_objs)
|
||||
return res
|
||||
|
||||
async def stop_quotation(self, qt_id: str) -> Res_Quotation:
|
||||
|
||||
@ -8,6 +8,8 @@ from common.enums import DBWRType, ErrorType
|
||||
from crud.quotation_setting_crud import IQuotationSettingCRUD, QuotationSettingCRUD
|
||||
from router.v1.quotation_setting.protocol import (
|
||||
QuotationSettingData,
|
||||
Req_CreateQuotationSetting,
|
||||
Req_UpdateQuotationSetting,
|
||||
Res_DeleteQuotationSetting,
|
||||
Res_QuotationSetting,
|
||||
Res_QuotationSettingList,
|
||||
@ -58,9 +60,14 @@ class QuotationSettingService:
|
||||
res.setting = QuotationSettingData.model_validate(setting)
|
||||
return res
|
||||
|
||||
async def create_setting(self, user_id: str, data: dict) -> Res_QuotationSetting:
|
||||
async def create_setting(self, user_id: str, req: Req_CreateQuotationSetting) -> Res_QuotationSetting:
|
||||
res = Res_QuotationSetting()
|
||||
setting = quotation_settings(**data, user_id=uuid.UUID(user_id))
|
||||
setting = quotation_settings(
|
||||
user_id=uuid.UUID(user_id),
|
||||
target_margin_rate=req.target_margin_rate,
|
||||
anchoring_value=req.anchoring_value,
|
||||
card_count=req.card_count,
|
||||
)
|
||||
err_type = await DB_SESSION_MNG.execute_lambda_run(
|
||||
[quotation_settings.DBType()],
|
||||
[lambda s: self.qs_crud.add_setting(s, setting)],
|
||||
@ -71,10 +78,11 @@ class QuotationSettingService:
|
||||
# 서버 기본값(created_at/updated_at)은 insert 후 객체에 실리지 않으므로 재조회한다.
|
||||
return await self.get_setting(user_id, str(setting.qt_setting_id))
|
||||
|
||||
async def update_setting(self, user_id: str, qt_setting_id: str, data: dict) -> Res_QuotationSetting:
|
||||
async def update_setting(self, user_id: str, qt_setting_id: str, req: Req_UpdateQuotationSetting) -> Res_QuotationSetting:
|
||||
res = Res_QuotationSetting()
|
||||
user_uuid = uuid.UUID(user_id)
|
||||
setting_uuid = uuid.UUID(qt_setting_id)
|
||||
data = req.model_dump(exclude_unset=True)
|
||||
|
||||
# 소유권 확인
|
||||
err_type, _ = await self._fetch_owned(user_uuid, setting_uuid)
|
||||
|
||||
@ -8,7 +8,15 @@ 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 Res_CheckCodes, Res_DeleteSupplier, Res_Supplier, Res_SupplierList, SupplierData
|
||||
from router.v1.supplier.protocol import (
|
||||
Req_CreateSupplier,
|
||||
Req_UpdateSupplier,
|
||||
Res_CheckCodes,
|
||||
Res_DeleteSupplier,
|
||||
Res_Supplier,
|
||||
Res_SupplierList,
|
||||
SupplierData,
|
||||
)
|
||||
|
||||
|
||||
class SupplierService:
|
||||
@ -70,12 +78,12 @@ class SupplierService:
|
||||
res.existing = list(existing)
|
||||
return res
|
||||
|
||||
async def create_supplier(self, company_id: str, user_id: str, data: dict) -> Res_Supplier:
|
||||
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 = data.get("code")
|
||||
code = req.code
|
||||
if code:
|
||||
dup_err, exists = await DB_SESSION_MNG.execute_lambda(
|
||||
suppliers.DBType(),
|
||||
@ -89,7 +97,16 @@ class SupplierService:
|
||||
res.result.SetResult(ErrorType.SUPPLIER_CODE_DUPLICATE)
|
||||
return res
|
||||
|
||||
supplier = suppliers(**data, company_id=company_uuid, user_id=uuid.UUID(user_id))
|
||||
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)],
|
||||
@ -100,10 +117,11 @@ class SupplierService:
|
||||
# 서버 기본값(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, data: dict) -> Res_Supplier:
|
||||
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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user