import uuid from datetime import datetime from typing import Optional from pydantic import ConfigDict from common.enums import DeliveryType from common.models.gmodel import Res_PageProtocol, Res_WebPacketProtocol, WebPacketProtocol class ItemProtocol(WebPacketProtocol): pass class Req_CreateItem(ItemProtocol): name: str = "" code: Optional[str] = None category: Optional[str] = None category_type: int = 1 image_url: Optional[str] = None model_name: Optional[str] = None spec: Optional[str] = None manufacturer: Optional[str] = None made_in: Optional[str] = None price: Optional[int] = None internet_lowest_price_yn: bool = False internet_lowest_price: Optional[int] = None # 인터넷 최저가 실값(원). 목표가 산정용 purchase_price: Optional[int] = None # 매입가(원). 재견적 목표가 후보(그대로) selling_price: Optional[int] = None # 판매가(원). 유통형 목표가 후보(×(1−마진율)) moq: Optional[str] = None lead_time: Optional[int] = None quantity_unit: Optional[str] = None delivery_type: Optional[int] = None vat_yn: Optional[bool] = None delivery_fee_yn: Optional[bool] = None custom: Optional[dict] = None # 회사 커스텀필드 값 {key: value} (정의는 companies.settings.item_fields) class Req_UpdateItem(ItemProtocol): name: Optional[str] = None code: Optional[str] = None category: Optional[str] = None category_type: Optional[int] = None image_url: Optional[str] = None model_name: Optional[str] = None spec: Optional[str] = None manufacturer: Optional[str] = None made_in: Optional[str] = None price: Optional[int] = None internet_lowest_price_yn: Optional[bool] = None internet_lowest_price: Optional[int] = None # 인터넷 최저가 실값(원). 목표가 산정용 purchase_price: Optional[int] = None # 매입가(원). 재견적 목표가 후보(그대로) selling_price: Optional[int] = None # 판매가(원). 유통형 목표가 후보(×(1−마진율)) moq: Optional[str] = None lead_time: Optional[int] = None quantity_unit: Optional[str] = None delivery_type: Optional[int] = None vat_yn: Optional[bool] = None delivery_fee_yn: Optional[bool] = None custom: Optional[dict] = None # 회사 커스텀필드 값 {key: value} class ItemData(WebPacketProtocol): model_config = ConfigDict(from_attributes=True) item_id: uuid.UUID company_id: uuid.UUID user_id: uuid.UUID creator_name: Optional[str] = None # 등록자(작성자) 이름. user_id→company.users.name 조인 name: str code: Optional[str] = None category: Optional[str] = None category_type: int = 1 image_url: Optional[str] = None model_name: Optional[str] = None spec: Optional[str] = None manufacturer: Optional[str] = None made_in: Optional[str] = None price: Optional[int] = None internet_lowest_price_yn: bool = False internet_lowest_price: Optional[int] = None # 인터넷 최저가 실값(원). 목표가 산정용 purchase_price: Optional[int] = None # 매입가(원). 재견적 목표가 후보(그대로) selling_price: Optional[int] = None # 판매가(원). 유통형 목표가 후보(×(1−마진율)) moq: Optional[str] = None lead_time: Optional[int] = None quantity_unit: Optional[str] = None delivery_type: Optional[DeliveryType] = None vat_yn: Optional[bool] = None delivery_fee_yn: Optional[bool] = None custom: Optional[dict] = None # 회사 커스텀필드 값 {key: value} supplier_names: list[str] = [] # 이 상품을 취급(계약)하는 공급사명 목록(목록 컬럼용, 배치 조인) created_at: Optional[datetime] = None updated_at: Optional[datetime] = None class Res_Item(Res_WebPacketProtocol): item: Optional[ItemData] = None class Res_ItemList(Res_PageProtocol): items: list[ItemData] = [] class ItemCategory(WebPacketProtocol): name: str category_type: int = 1 class Res_ItemCategories(Res_WebPacketProtocol): categories: list[ItemCategory] = [] class Req_CheckCodes(ItemProtocol): codes: list[str] = [] class Res_CheckCodes(Res_WebPacketProtocol): existing: list[str] = [] # codes 중 이미 DB(같은 회사)에 존재하는 코드들 class Res_DeleteItem(Res_WebPacketProtocol): pass class Res_ItemImage(Res_WebPacketProtocol): image_url: Optional[str] = None filename: Optional[str] = None size: Optional[int] = None class Res_LowestPriceTrigger(Res_WebPacketProtocol): item_id: str = "" status: str = "" # queued(접수) | duplicated(이미 진행 중) | unavailable(연동 비활성/LPS 다운) message: str = "" class LowestPriceEntry(WebPacketProtocol): """최저가 수집 이력 1건(partner.item_internet_lowest_prices).""" model_config = ConfigDict(from_attributes=True) lp_price: Optional[int] = None # 수집한 최저가(원). 실패면 None website: int = 0 # LowestPriceWebsite 코드(1=naver 2=coupang …) success_yn: bool = False fail_reason: Optional[str] = None lp_name: Optional[str] = None # 찾은 상품명(판매 페이지 기준) — 근거 검증용 lp_url: Optional[str] = None # 찾은 판매 페이지 링크 # 몰별 최저가 스냅샷(가격 오름차순). **가격이 있는 몰만** 들어온다 — # 그래서 없는 몰이 '거기엔 없었다'인지 '거기를 못 봤다'인지는 아래 sources 가 답한다. # [{source: 'naver'|'coupang'|…, mall_name, price, shipping_fee, shipping_type, name, detail_url}] by_mall: Optional[list[dict]] = None # 몰별 확인 상태 — {"naver": {"state": "matched"}, "coupang": {"state": "blocked"}}. # 화면은 이걸 셋으로 접어 쓴다: 가격 / '–'(없음) / '확인 못함'(미확인). # state 정의는 lps/docs/result-states.md. sources: Optional[dict] = None # 못 본 몰이 있어 결과가 최종이 아님. LPS 가 판단한 사실이라 화면은 그대로 쓰면 된다. partial: bool = False crawl_end_time: Optional[datetime] = None class Res_LowestPriceResult(Res_WebPacketProtocol): item_id: str = "" lowest_price: Optional[int] = None # 상품에 박제된 대표 최저가(items.internet_lowest_price) results: list[LowestPriceEntry] = [] # 최근 수집 이력(최신순) message: str = ""