Merge remote-tracking branch 'origin/main' into feature/backend
This commit is contained in:
commit
16c5fde284
@ -38,9 +38,10 @@ PostgreSQL (외부, 5432)
|
||||
## 빠른 시작
|
||||
|
||||
```bash
|
||||
# 1) DB 준비 (최초 1회) — 사용할 PostgreSQL 에 스키마 적용
|
||||
psql -h 127.0.0.1 -p 5432 -U postgres -f postgres-init/01-schema.sql
|
||||
# (negosium_db / negodata_db + tbl_account 생성)
|
||||
# 1) DB 준비 (최초 1회) — 사용할 PostgreSQL 에 스키마 + 시드 적용
|
||||
psql -h 127.0.0.1 -p 5432 -U postgres -f postgres-init/01-schema.sql # negosium_db + 도메인 schema
|
||||
psql -h 127.0.0.1 -p 5432 -U postgres -f postgres-init/02-learning-schema.sql # agent learning 스키마
|
||||
psql -h 127.0.0.1 -p 5432 -U postgres -f postgres-init/03-seed-negodata.sql # negodata 전용 시드
|
||||
|
||||
# 2) 백엔드 기동
|
||||
docker compose up -d # 두 backend (DB 는 config 대로 외부 연결)
|
||||
|
||||
@ -9,8 +9,9 @@
|
||||
# agent 서버: http://localhost:9500/docs
|
||||
#
|
||||
# DB 준비(최초 1회): postgres-init 의 SQL 을 대상 DB 에 적용한다.
|
||||
# psql -h <host> -p <port> -U <user> -f postgres-init/01-schema.sql (negosium_db/negodata_db + tbl_account)
|
||||
# psql -h <host> -p <port> -U <user> -f postgres-init/01-schema.sql (단일 negosium_db + 도메인별 schema)
|
||||
# psql -h <host> -p <port> -U <user> -f postgres-init/02-learning-schema.sql (agent learning 스키마)
|
||||
# psql -h <host> -p <port> -U <user> -f postgres-init/03-seed-negodata.sql (negodata 전용 시드: admin / admin1234, company.users)
|
||||
|
||||
services:
|
||||
negosium-backend:
|
||||
@ -38,6 +39,17 @@ services:
|
||||
- "host.docker.internal:host-gateway"
|
||||
restart: unless-stopped
|
||||
|
||||
# negodata 프론트 (Vite dev 서버).
|
||||
negodata-front:
|
||||
build: ./negodata/front
|
||||
container_name: negodata-front
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- ./negodata/front:/app
|
||||
- /app/node_modules
|
||||
restart: unless-stopped
|
||||
|
||||
# 협상 에이전트 (negosium_db 공유, learning 스키마 사용).
|
||||
agent:
|
||||
build: ./agent
|
||||
|
||||
@ -1,26 +1,207 @@
|
||||
import uuid
|
||||
|
||||
from sqlalchemy.orm import declarative_base
|
||||
from sqlalchemy import Column, Integer, String, Boolean, DateTime
|
||||
from sqlalchemy import Column, Integer, SmallInteger, BigInteger, Numeric, Float, String, Boolean, DateTime
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
from sqlalchemy.sql import text
|
||||
|
||||
from common.enums import DBType
|
||||
from common.enums import DBType, UserStatus, UserRole, CompanyStatus
|
||||
|
||||
# 모든 ORM 모델의 베이스. insert 시 isinstance 체크에도 사용된다.
|
||||
MAIN_BASE = declarative_base()
|
||||
|
||||
|
||||
class tbl_account(MAIN_BASE):
|
||||
# 모델이 자신이 속한 논리 DB 를 알려준다 (람다 실행 시 DBType 으로 세션 선택).
|
||||
# 공통 mixin
|
||||
# DB 계약(_DBTypeMixin)과 ERD 공통 컬럼(MainTableMixin)을 분리해 둔다.
|
||||
def _utc_now_sql():
|
||||
return text("(now() AT TIME ZONE 'utc')")
|
||||
|
||||
|
||||
class _DBTypeMixin:
|
||||
"""모델이 자신이 속한 논리 DB 를 알려준다 (람다 실행 시 DBType 으로 세션 선택)."""
|
||||
|
||||
@staticmethod
|
||||
def DBType():
|
||||
return DBType.MAIN.value
|
||||
|
||||
__tablename__ = "tbl_account"
|
||||
|
||||
uid = Column(Integer, primary_key=True, autoincrement=True)
|
||||
id = Column(String(45), nullable=False, unique=True) # 로그인 ID. 중복 가입 방지 위해 unique.
|
||||
pw = Column(String(255), nullable=False, default="") # bcrypt 해시 저장
|
||||
nickname = Column(String(45), nullable=False, default="")
|
||||
is_blocked = Column(Boolean, nullable=False, default=False)
|
||||
# PostgreSQL UTC now: now() 는 timestamptz 이므로 utc 로 변환해 timestamp 로 저장.
|
||||
last_login_at = Column(DateTime, nullable=False, server_default=text("(now() AT TIME ZONE 'utc')"))
|
||||
create_at = Column(DateTime, server_default=text("(now() AT TIME ZONE 'utc')"))
|
||||
# ERD 공통 컬럼
|
||||
class MainTableMixin(_DBTypeMixin):
|
||||
created_at = Column(DateTime, nullable=False, server_default=_utc_now_sql())
|
||||
updated_at = Column(DateTime, nullable=False, server_default=_utc_now_sql(), onupdate=_utc_now_sql())
|
||||
deleted = Column(Boolean, nullable=False, server_default=text("false"), default=False)
|
||||
|
||||
|
||||
# ERD 도메인 모델
|
||||
class companies(MainTableMixin, MAIN_BASE):
|
||||
__tablename__ = "companies"
|
||||
|
||||
company_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
name = Column(String(100), nullable=False)
|
||||
business_number = Column(String(30), nullable=True)
|
||||
code = Column(Integer, nullable=True) # 내부 인덱스용
|
||||
representative_name = Column(String(50), nullable=True)
|
||||
email = Column(String(255), nullable=True)
|
||||
contact_number = Column(String(20), nullable=True)
|
||||
website_url = Column(String(255), nullable=True)
|
||||
industry = Column(SmallInteger, nullable=True) # 업종 코드 (스키마 SMALLINT)
|
||||
status = Column(SmallInteger, nullable=False, default=CompanyStatus.ACTIVE.value) # CompanyStatus
|
||||
|
||||
|
||||
class users(MainTableMixin, MAIN_BASE):
|
||||
__tablename__ = "users"
|
||||
|
||||
user_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
company_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
||||
id = Column(String(20), nullable=False, unique=True, index=True) # 로그인 아이디
|
||||
password = Column(String(255), nullable=False) # bcrypt 해시 (ERD VARCHAR(30)→255 확장)
|
||||
name = Column(String(50), nullable=True)
|
||||
email = Column(String(255), nullable=True)
|
||||
contact_number = Column(String(20), nullable=True)
|
||||
last_accessed_at = Column(DateTime, nullable=False, server_default=_utc_now_sql())
|
||||
status = Column(SmallInteger, nullable=False, default=UserStatus.ACTIVE.value) # UserStatus
|
||||
role = Column(SmallInteger, nullable=False, default=UserRole.USER.value) # UserRole
|
||||
|
||||
|
||||
class items(MainTableMixin, MAIN_BASE):
|
||||
__tablename__ = "items"
|
||||
|
||||
item_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
company_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
||||
user_id = Column(UUID(as_uuid=True), nullable=False, index=True) # 등록한 유저
|
||||
|
||||
name = Column(String(100), nullable=False)
|
||||
code = Column(String(30), nullable=True)
|
||||
category = Column(String(255), nullable=True)
|
||||
category_type = Column(Integer, nullable=False, default=1) # 카테고리 탐색용 자동 증가 숫자
|
||||
|
||||
image_url = Column(String(255), nullable=True)
|
||||
model_name = Column(String(100), nullable=True)
|
||||
spec = Column(String(255), nullable=True)
|
||||
manufacturer = Column(String(50), nullable=True)
|
||||
made_in = Column(String(100), nullable=True)
|
||||
|
||||
price = Column(BigInteger, nullable=True) # 금액(원), 스키마 BIGINT
|
||||
internet_lowest_price_yn = Column(Boolean, nullable=False, default=False) # 최저가 솔루션 원자성 보존용
|
||||
|
||||
moq = Column(String(50), nullable=True) # 최소 주문 수량
|
||||
lead_time = Column(SmallInteger, nullable=True) # 주문 후 배송 도착까지 시간
|
||||
quantity_unit = Column(String(50), nullable=True) # 단위 라벨(자유입력): EA/BOX/SET/ROLL ...
|
||||
delivery_type = Column(SmallInteger, nullable=True) # 배송 유형 코드
|
||||
vat_yn = Column(Boolean, nullable=True)
|
||||
delivery_fee_yn = Column(Boolean, nullable=True)
|
||||
|
||||
|
||||
class suppliers(MainTableMixin, MAIN_BASE):
|
||||
__tablename__ = "suppliers"
|
||||
|
||||
supplier_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
company_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
||||
user_id = Column(UUID(as_uuid=True), nullable=False, index=True) # 등록한 유저
|
||||
|
||||
name = Column(String(100), nullable=False)
|
||||
code = Column(String(20), nullable=True)
|
||||
manager_name = Column(String(50), nullable=True)
|
||||
manager_email = Column(String(255), nullable=True)
|
||||
manager_contact_number = Column(String(20), nullable=True) # ERD 오타(manger) 교정
|
||||
priority = Column(String(10), nullable=True) # True/False 가 아닌 string value 가능
|
||||
|
||||
|
||||
class nego_cards(MainTableMixin, MAIN_BASE):
|
||||
__tablename__ = "nego_cards"
|
||||
|
||||
nego_card_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
user_id = Column(UUID(as_uuid=True), nullable=True, index=True) # 등록 유저(o2o 기본 카드는 NULL)
|
||||
name = Column(String(20), nullable=True) # 카드명
|
||||
number = Column(String(10), nullable=True) # 식별번호(카드코드)
|
||||
script = Column(String(255), nullable=True) # 협상 스크립트(평문 미리보기)
|
||||
edit_script = Column(JSONB, nullable=True) # 편집된 스크립트(Slate JSON)
|
||||
|
||||
|
||||
class wild_cards(MainTableMixin, MAIN_BASE):
|
||||
__tablename__ = "wild_cards"
|
||||
|
||||
wild_card_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
user_id = Column(UUID(as_uuid=True), nullable=True, index=True) # 등록 유저(o2o 기본 카드는 NULL)
|
||||
name = Column(String(20), nullable=True) # 카드명
|
||||
number = Column(String(10), nullable=True) # 식별번호(카드코드)
|
||||
script = Column(String(255), nullable=True) # 협상 스크립트(평문 미리보기)
|
||||
edit_script = Column(JSONB, nullable=True) # 편집된 스크립트(Slate JSON)
|
||||
condition = Column(String(255), nullable=True) # 사용 조건(트리거)
|
||||
available = Column(Boolean, nullable=False, default=False) # 수동 협상 적용 여부(ACTIVE/INACTIVE 매핑)
|
||||
memo = Column(String(255), nullable=True) # 자유 메모
|
||||
|
||||
|
||||
class quotation_settings(MainTableMixin, MAIN_BASE):
|
||||
__tablename__ = "quotation_settings"
|
||||
|
||||
qt_setting_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
user_id = Column(UUID(as_uuid=True), nullable=True, index=True) # 설정 소유 유저
|
||||
target_margin_rate = Column(Numeric(8, 6), nullable=False)
|
||||
anchoring_value = Column(Numeric(8, 6), nullable=False, default=0.01)
|
||||
card_count = Column(Integer, nullable=False, default=3) # 한 협상 내 협상카드 사용 횟수
|
||||
|
||||
|
||||
class quotations(MainTableMixin, MAIN_BASE):
|
||||
__tablename__ = "quotations"
|
||||
|
||||
qt_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
user_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
||||
qt_setting_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
||||
version_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
||||
|
||||
name = Column(String(50), nullable=False)
|
||||
number = Column(String(30), nullable=False)
|
||||
type = Column(SmallInteger, nullable=False) # QuotationType: 1=renego(1:1) / 2=requote(1:N)
|
||||
round = Column(Integer, nullable=False, default=1) # 재견적 진행 시 증가
|
||||
status = Column(SmallInteger, nullable=False) # 진행 상태 코드
|
||||
start_time = Column(DateTime, nullable=False)
|
||||
end_time = Column(DateTime, nullable=False)
|
||||
|
||||
manager_name = Column(String(50), nullable=True)
|
||||
manager_email = Column(String(255), nullable=True)
|
||||
manager_contact_number = Column(String(20), nullable=True)
|
||||
memo = Column(String(100), nullable=True)
|
||||
|
||||
iteration = Column(Integer, nullable=False, default=0)
|
||||
preferred_sp_yn = Column(Boolean, nullable=True)
|
||||
preferred_sp_id = Column(UUID(as_uuid=True), nullable=True)
|
||||
preferred_sp_name = Column(String(20), nullable=True)
|
||||
equal_bid_yn = Column(Boolean, nullable=True)
|
||||
equal_bid_data = Column(JSONB, nullable=True)
|
||||
|
||||
|
||||
class sessions(MainTableMixin, MAIN_BASE):
|
||||
__tablename__ = "sessions"
|
||||
|
||||
session_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
quotation_id = Column(UUID(as_uuid=True), nullable=False, index=True) # 소속 견적(quotations.qt_id)
|
||||
item_id = Column(UUID(as_uuid=True), nullable=False, index=True) # 대상 상품(items.item_id)
|
||||
supplier_id = Column(UUID(as_uuid=True), nullable=False, index=True) # 대상 공급사(suppliers.supplier_id)
|
||||
|
||||
qt_number = Column(String(30), nullable=False) # 견적번호 스냅샷
|
||||
qt_round = Column(Integer, nullable=False) # 견적 라운드 스냅샷
|
||||
qt_type = Column(SmallInteger, nullable=False) # QuotationType 스냅샷
|
||||
target_price = Column(BigInteger, nullable=False) # 목표가(원)
|
||||
status = Column(SmallInteger, nullable=False) # SessionStatus 코드
|
||||
bid_price = Column(BigInteger, nullable=True) # 입찰가(원)
|
||||
bid_at = Column(DateTime, nullable=True) # 입찰 시각
|
||||
end_time = Column(DateTime, nullable=False) # 세션 종료 시각
|
||||
reject_reason = Column(String(255), nullable=True)
|
||||
reject_price = Column(BigInteger, nullable=True)
|
||||
reject_delivery_type = Column(SmallInteger, nullable=True) # DeliveryType 코드
|
||||
|
||||
|
||||
class chats(MainTableMixin, MAIN_BASE):
|
||||
__tablename__ = "chats"
|
||||
|
||||
chat_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
session_id = Column(UUID(as_uuid=True), nullable=False, index=True) # 소속 세션(sessions.session_id)
|
||||
card_id = Column(UUID(as_uuid=True), nullable=True) # 사용된 카드(nego_cards.nego_card_id)
|
||||
|
||||
seq = Column(Integer, nullable=False, default=1) # 세션 내 메시지 순번(프로토콜 index)
|
||||
sender = Column(SmallInteger, nullable=False) # ChatSender 코드(1=bot, 2=partner)
|
||||
target_price = Column(BigInteger, nullable=False) # 제시 목표가(원)
|
||||
card_used_yn = Column(Boolean, nullable=True) # 카드 사용 여부
|
||||
indicator_value = Column(Numeric(8, 6), nullable=True)
|
||||
card_type = Column(SmallInteger, nullable=True) # 1=nego_card, 2=wild_card
|
||||
|
||||
@ -36,6 +36,23 @@ class ErrorType(Enum):
|
||||
ACCOUNT_ALREADY_EXIST = auto()
|
||||
ACCOUNT_BLOCKED_USER = auto()
|
||||
|
||||
# 상품 관련 에러
|
||||
ITEM_NOT_FOUND = 1300
|
||||
ITEM_CODE_DUPLICATE = auto()
|
||||
|
||||
# 협력사 관련 에러
|
||||
SUPPLIER_NOT_FOUND = 1400
|
||||
SUPPLIER_CODE_DUPLICATE = auto()
|
||||
|
||||
# 견적 관련 에러
|
||||
QUOTATION_NOT_FOUND = 1500
|
||||
|
||||
# 견적 설정 관련 에러
|
||||
QUOTATION_SETTING_NOT_FOUND = 1600
|
||||
|
||||
# 협상카드 관련 에러
|
||||
CARD_NOT_FOUND = 1700
|
||||
|
||||
|
||||
# ErrorType 의 HTTP_* 값과 status_code 를 맞춰 router 단에서 raise 한다.
|
||||
EXCEPTION_INVALID_CLIENT_REQUEST = HTTPException(status_code=ErrorType.HTTP_INVALID_CLIENT_REQUEST.value, detail=ErrorType.HTTP_INVALID_CLIENT_REQUEST.name)
|
||||
@ -59,3 +76,111 @@ class DBWRType(Enum):
|
||||
|
||||
DB_READ = 1
|
||||
DB_WRITE = 2
|
||||
|
||||
|
||||
# 도메인 코드값
|
||||
class UserStatus(Enum):
|
||||
"""users.status 코드값."""
|
||||
|
||||
ACTIVE = 1
|
||||
INACTIVE = 2
|
||||
|
||||
|
||||
class UserRole(Enum):
|
||||
"""users.role 코드값."""
|
||||
|
||||
USER = 1
|
||||
MANAGER = 2
|
||||
|
||||
|
||||
class CompanyStatus(Enum):
|
||||
"""companies.status 코드값."""
|
||||
|
||||
ACTIVE = 1
|
||||
INACTIVE = 2
|
||||
|
||||
|
||||
class QuotationType(Enum):
|
||||
"""quotations.type 코드값. 1=renego(재협상 1:1), 2=requote(재견적 1:N)."""
|
||||
|
||||
RENEGO = 1
|
||||
REQUOTE = 2
|
||||
|
||||
|
||||
class QuotationStatus(Enum):
|
||||
"""quotations.status 코드값(SMALLINT). 프론트 견적상태 뱃지와 매핑된다."""
|
||||
|
||||
CREATED = 1 # 견적생성
|
||||
ACTIVE = 2 # 견적진행중
|
||||
CLOSED = 3 # 견적마감
|
||||
ON_HOLD = 4 # 협상보류
|
||||
|
||||
|
||||
class SessionStatus(Enum):
|
||||
"""negotiation.sessions.status 코드값. 협력사별 협상 세션 진행 상태."""
|
||||
|
||||
NEGOTIATING = 1 # 협상중
|
||||
COMPLETED = 2 # 협상종료
|
||||
REJECTED = 3 # 협상거부
|
||||
|
||||
|
||||
class ChatSender(Enum):
|
||||
"""negotiation.chats.sender 코드값. 채팅 발신 주체."""
|
||||
|
||||
BOT = 1 # 구매대행 봇
|
||||
PARTNER = 2 # 협력사
|
||||
|
||||
|
||||
class DeliveryType(Enum):
|
||||
"""items.delivery_type 코드값. 협상 채팅의 배송형태 선택지와 동일 집합."""
|
||||
|
||||
PARTNER = 1 # 협력사배송
|
||||
COURIER = 2 # 지정택배배송
|
||||
PICKUP = 3 # 픽업배송
|
||||
|
||||
|
||||
class CardStatus(Enum):
|
||||
"""nego_cards.status 코드값. 와일드카드의 협상 적용 여부(수동 승인). 일반 협상카드는 상시 ACTIVE."""
|
||||
|
||||
ACTIVE = 1
|
||||
INACTIVE = 2
|
||||
|
||||
|
||||
# 도메인 enum 한글 라벨. 프론트 드롭다운 표시는 이 라벨을 쓴다(값=코드).
|
||||
ENUM_LABELS = {
|
||||
UserStatus.ACTIVE: "활성",
|
||||
UserStatus.INACTIVE: "비활성",
|
||||
UserRole.USER: "일반",
|
||||
UserRole.MANAGER: "관리자",
|
||||
CompanyStatus.ACTIVE: "활성",
|
||||
CompanyStatus.INACTIVE: "비활성",
|
||||
QuotationType.RENEGO: "재협상",
|
||||
QuotationType.REQUOTE: "재견적",
|
||||
QuotationStatus.CREATED: "견적생성",
|
||||
QuotationStatus.ACTIVE: "견적진행중",
|
||||
QuotationStatus.CLOSED: "견적마감",
|
||||
QuotationStatus.ON_HOLD: "협상보류",
|
||||
SessionStatus.NEGOTIATING: "협상중",
|
||||
SessionStatus.COMPLETED: "협상종료",
|
||||
SessionStatus.REJECTED: "협상거부",
|
||||
ChatSender.BOT: "봇",
|
||||
ChatSender.PARTNER: "협력사",
|
||||
DeliveryType.PARTNER: "협력사배송",
|
||||
DeliveryType.COURIER: "지정택배배송",
|
||||
DeliveryType.PICKUP: "픽업배송",
|
||||
CardStatus.ACTIVE: "적용",
|
||||
CardStatus.INACTIVE: "대기",
|
||||
}
|
||||
|
||||
# 프론트로 내려주는 도메인 코드 enum 모음. 새 코드 enum 추가 시 여기에 등록한다.
|
||||
DOMAIN_ENUMS = {
|
||||
"user_status": UserStatus,
|
||||
"user_role": UserRole,
|
||||
"company_status": CompanyStatus,
|
||||
"quotation_type": QuotationType,
|
||||
"quotation_status": QuotationStatus,
|
||||
"session_status": SessionStatus,
|
||||
"chat_sender": ChatSender,
|
||||
"delivery_type": DeliveryType,
|
||||
"card_status": CardStatus,
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import Query
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from common.enums import ErrorType
|
||||
@ -45,12 +46,30 @@ class Res_WebPacketProtocol(WebPacketProtocol):
|
||||
msg: Optional[str] = None
|
||||
|
||||
|
||||
class Res_PageProtocol(Res_WebPacketProtocol):
|
||||
# 목록 응답 공용 페이지 메타.
|
||||
total: int = 0
|
||||
page: int = 0
|
||||
size: int = 0
|
||||
|
||||
|
||||
class PageParams:
|
||||
# 목록 엔드포인트 공용 쿼리 파라미터. 라우터에서 Depends() 로 주입한다.
|
||||
def __init__(self, page: int = Query(1, ge=1), size: int = Query(20, ge=1, le=100)):
|
||||
self.page = page
|
||||
self.size = size
|
||||
|
||||
@property
|
||||
def skip(self) -> int:
|
||||
return (self.page - 1) * self.size
|
||||
|
||||
|
||||
class UserInfo(StructModel):
|
||||
"""JWT subject 로 인코딩되는 유저 식별 정보."""
|
||||
|
||||
uid: int
|
||||
id: str
|
||||
nickname: str
|
||||
user_id: str # users.user_id (uuid) — 데이터 스코프 키
|
||||
id: str # users.id (로그인 아이디) — get_me 재조회 키
|
||||
company_id: str # users.company_id (uuid) — 멀티테넌트 스코프 키
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__()
|
||||
|
||||
@ -7,6 +7,7 @@ port = 9400
|
||||
process_count = 1
|
||||
is_ssl = false
|
||||
is_test = true
|
||||
client_url = "http://localhost:3000" # CORS 허용
|
||||
|
||||
[LogConfig]
|
||||
print_console = true
|
||||
@ -16,7 +17,7 @@ log_level = "debug"
|
||||
# 관리형 DB(RDS/Aurora/Azure)는 host 에 엔드포인트, sslmode="require".
|
||||
[MainDBConfig]
|
||||
db_type = "postgresql"
|
||||
name = "negodata_db"
|
||||
name = "negosium_db"
|
||||
write_host = "127.0.0.1"
|
||||
write_port = 5432
|
||||
write_id = "<DB_USER>"
|
||||
|
||||
@ -7,6 +7,7 @@ class WebServerConfig(ConfigModel):
|
||||
process_count: int = 1
|
||||
is_ssl: bool = False
|
||||
is_test: bool = False
|
||||
client_url: str = ""
|
||||
|
||||
|
||||
class LogConfig(ConfigModel):
|
||||
|
||||
@ -4,6 +4,8 @@ import os
|
||||
|
||||
os.environ.setdefault("APP_ENV", "local")
|
||||
|
||||
import uuid
|
||||
|
||||
import pytest_asyncio
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
from sqlalchemy import text
|
||||
@ -28,11 +30,31 @@ async def db_engine():
|
||||
engine = create_async_engine(_write_url(main_db_config))
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(MAIN_BASE.metadata.create_all) # 이미 있으면 skip
|
||||
await conn.execute(text("TRUNCATE TABLE tbl_account"))
|
||||
# negodata 도메인 테이블 전부 비워 격리 (CASCADE: FK 미설정이라 안전망)
|
||||
await conn.execute(
|
||||
text(
|
||||
"TRUNCATE TABLE tbl_account, users, companies, items, suppliers, "
|
||||
"quotation_settings, quotations RESTART IDENTITY CASCADE"
|
||||
)
|
||||
)
|
||||
yield engine
|
||||
await engine.dispose()
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def company_id(db_engine) -> str:
|
||||
"""테스트용 소속사 1개를 시드하고 company_id(uuid str)를 돌려준다.
|
||||
users 는 company_id 를 요구하므로 계정 생성 테스트의 선행 조건이다.
|
||||
"""
|
||||
cid = uuid.uuid4()
|
||||
async with db_engine.begin() as conn:
|
||||
await conn.execute(
|
||||
text("INSERT INTO companies (company_id, name) VALUES (:cid, :name)"),
|
||||
{"cid": cid, "name": "테스트사"},
|
||||
)
|
||||
return str(cid)
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="session", autouse=True)
|
||||
async def _dispose_app_engines():
|
||||
"""테스트 세션이 끝날 때 앱 싱글톤 엔진을 정리한다.
|
||||
|
||||
104
negodata/backend/crud/card_crud.py
Normal file
104
negodata/backend/crud/card_crud.py
Normal file
@ -0,0 +1,104 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from sqlalchemy import select, func, and_, or_, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from common.database.db_session_manager import DB_SESSION_MNG
|
||||
from common.enums import ErrorType
|
||||
from common.logger import LOG
|
||||
from common.utils.gtime import GTime
|
||||
|
||||
|
||||
# 협상카드 CRUD. nego_cards/wild_cards 두 테이블에 공통으로 쓰는 제네릭 구현.
|
||||
class ICardCRUD(ABC):
|
||||
@abstractmethod
|
||||
async def search(self, cdb: AsyncSession, model, user_id, search, skip, limit) -> Tuple[ErrorType, list, int]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_by_id(self, cdb: AsyncSession, model, pk_col, card_id) -> Tuple[ErrorType, object]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def add(self, cdb: AsyncSession, card) -> ErrorType:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def update(self, cdb: AsyncSession, model, pk_col, card_id, data: dict) -> ErrorType:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def soft_delete(self, cdb: AsyncSession, model, pk_col, card_id) -> ErrorType:
|
||||
pass
|
||||
|
||||
|
||||
class CardCRUD(ICardCRUD):
|
||||
async def search(
|
||||
self, cdb: AsyncSession, model, user_id, search: Optional[str], skip: int, limit: int
|
||||
) -> Tuple[ErrorType, list, int]:
|
||||
try:
|
||||
conditions = [model.deleted == False, model.user_id == user_id] # noqa: E712
|
||||
if search:
|
||||
conditions.append(
|
||||
or_(
|
||||
model.name.ilike(f"%{search}%"),
|
||||
model.number.ilike(f"%{search}%"),
|
||||
model.script.ilike(f"%{search}%"),
|
||||
)
|
||||
)
|
||||
where = and_(*conditions)
|
||||
|
||||
cnt_err, cnt_rows = await DB_SESSION_MNG.execute(cdb, select(func.count()).select_from(model).where(where))
|
||||
if cnt_err != ErrorType.SUCCESS:
|
||||
return cnt_err, [], 0
|
||||
total = int(cnt_rows[0] or 0) if cnt_rows else 0
|
||||
|
||||
list_err, rows = await DB_SESSION_MNG.execute(
|
||||
cdb,
|
||||
select(model).where(where).order_by(model.created_at.desc()).offset(skip).limit(limit),
|
||||
)
|
||||
if list_err != ErrorType.SUCCESS:
|
||||
return list_err, [], 0
|
||||
return ErrorType.SUCCESS, list(rows), total
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, [], 0
|
||||
|
||||
async def get_by_id(self, cdb: AsyncSession, model, pk_col, card_id) -> Tuple[ErrorType, object]:
|
||||
try:
|
||||
query = select(model).where(pk_col == card_id, model.deleted == False).limit(1) # noqa: E712
|
||||
err_type, row_list = await DB_SESSION_MNG.execute(cdb, query)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
return err_type, None
|
||||
if len(row_list) != 1:
|
||||
return ErrorType.DB_INVALID_KEY, None
|
||||
return ErrorType.SUCCESS, row_list[0]
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, None
|
||||
|
||||
async def add(self, cdb: AsyncSession, card) -> ErrorType:
|
||||
try:
|
||||
return await DB_SESSION_MNG.insert(cdb, card)
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED
|
||||
|
||||
async def update(self, cdb: AsyncSession, model, pk_col, card_id, data: dict) -> ErrorType:
|
||||
try:
|
||||
if not data:
|
||||
return ErrorType.SUCCESS
|
||||
query = update(model).where(pk_col == card_id).values(**data)
|
||||
return await DB_SESSION_MNG.add(cdb, query)
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED
|
||||
|
||||
async def soft_delete(self, cdb: AsyncSession, model, pk_col, card_id) -> ErrorType:
|
||||
try:
|
||||
query = update(model).where(pk_col == card_id).values(deleted=True, updated_at=GTime.UTC())
|
||||
return await DB_SESSION_MNG.add(cdb, query)
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED
|
||||
168
negodata/backend/crud/item_crud.py
Normal file
168
negodata/backend/crud/item_crud.py
Normal file
@ -0,0 +1,168 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from sqlalchemy import select, func, and_, or_, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from common.database.db_session_manager import DB_SESSION_MNG
|
||||
from common.database.model.models import items
|
||||
from common.enums import ErrorType
|
||||
from common.logger import LOG
|
||||
from common.utils.gtime import GTime
|
||||
|
||||
|
||||
# 상품 CRUD. 모든 조회/변경은 company_id 로 스코프된다(멀티테넌트).
|
||||
class IItemCRUD(ABC):
|
||||
@abstractmethod
|
||||
async def search(self, cdb: AsyncSession, company_id, search, category, skip, limit) -> Tuple[ErrorType, list, int]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def distinct_categories(self, cdb: AsyncSession, company_id) -> Tuple[ErrorType, list]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def code_exists(self, cdb: AsyncSession, company_id, code) -> Tuple[ErrorType, bool]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def existing_codes(self, cdb: AsyncSession, company_id, codes: list) -> Tuple[ErrorType, list]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_by_id(self, cdb: AsyncSession, item_id) -> Tuple[ErrorType, items]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def add_item(self, cdb: AsyncSession, item: items) -> ErrorType:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def update_item(self, cdb: AsyncSession, item_id, data: dict) -> ErrorType:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def soft_delete(self, cdb: AsyncSession, item_id) -> ErrorType:
|
||||
pass
|
||||
|
||||
|
||||
class ItemCRUD(IItemCRUD):
|
||||
async def search(
|
||||
self, cdb: AsyncSession, company_id, search: Optional[str], category: Optional[str], skip: int, limit: int
|
||||
) -> Tuple[ErrorType, list, int]:
|
||||
try:
|
||||
conditions = [items.deleted == False, items.company_id == company_id] # noqa: E712
|
||||
if search:
|
||||
conditions.append(or_(items.name.ilike(f"%{search}%"), items.code.ilike(f"%{search}%")))
|
||||
if category:
|
||||
conditions.append(items.category == category)
|
||||
where = and_(*conditions)
|
||||
|
||||
cnt_err, cnt_rows = await DB_SESSION_MNG.execute(cdb, select(func.count()).select_from(items).where(where))
|
||||
if cnt_err != ErrorType.SUCCESS:
|
||||
return cnt_err, [], 0
|
||||
total = int(cnt_rows[0] or 0) if cnt_rows else 0
|
||||
|
||||
list_err, rows = await DB_SESSION_MNG.execute(
|
||||
cdb,
|
||||
select(items).where(where).order_by(items.created_at.desc()).offset(skip).limit(limit),
|
||||
)
|
||||
if list_err != ErrorType.SUCCESS:
|
||||
return list_err, [], 0
|
||||
return ErrorType.SUCCESS, list(rows), total
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, [], 0
|
||||
|
||||
# 회사의 모든 상품에서 카테고리를 "이름 단위"로 중복 제거해 (이름, 대표 타입) 목록을 돌려준다.
|
||||
async def distinct_categories(self, cdb: AsyncSession, company_id) -> Tuple[ErrorType, list]:
|
||||
try:
|
||||
query = (
|
||||
select(items.category, func.min(items.category_type))
|
||||
.where(
|
||||
items.company_id == company_id,
|
||||
items.deleted == False, # noqa: E712
|
||||
items.category.isnot(None),
|
||||
items.category != "",
|
||||
)
|
||||
.group_by(items.category)
|
||||
.order_by(items.category)
|
||||
)
|
||||
err_type, rows = await DB_SESSION_MNG.execute(cdb, query)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
return err_type, []
|
||||
return ErrorType.SUCCESS, list(rows)
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, []
|
||||
|
||||
async def code_exists(self, cdb: AsyncSession, company_id, code) -> Tuple[ErrorType, bool]:
|
||||
try:
|
||||
query = select(func.count()).select_from(items).where(
|
||||
items.company_id == company_id,
|
||||
items.code == code,
|
||||
items.deleted == False, # noqa: E712
|
||||
)
|
||||
err_type, rows = await DB_SESSION_MNG.execute(cdb, query)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
return err_type, False
|
||||
cnt = int(rows[0] or 0) if rows else 0
|
||||
return ErrorType.SUCCESS, cnt > 0
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, False
|
||||
|
||||
async def existing_codes(self, cdb: AsyncSession, company_id, codes: list) -> Tuple[ErrorType, list]:
|
||||
try:
|
||||
if not codes:
|
||||
return ErrorType.SUCCESS, []
|
||||
query = select(items.code).where(
|
||||
items.company_id == company_id,
|
||||
items.code.in_(codes),
|
||||
items.deleted == False, # noqa: E712
|
||||
)
|
||||
err_type, rows = await DB_SESSION_MNG.execute(cdb, query)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
return err_type, []
|
||||
return ErrorType.SUCCESS, [c for c in rows if c is not None]
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, []
|
||||
|
||||
async def get_by_id(self, cdb: AsyncSession, item_id) -> Tuple[ErrorType, items]:
|
||||
try:
|
||||
query = select(items).where(items.item_id == item_id, items.deleted == False).limit(1) # noqa: E712
|
||||
err_type, row_list = await DB_SESSION_MNG.execute(cdb, query)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
return err_type, None
|
||||
if len(row_list) != 1:
|
||||
return ErrorType.DB_INVALID_KEY, None
|
||||
return ErrorType.SUCCESS, row_list[0]
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, None
|
||||
|
||||
async def add_item(self, cdb: AsyncSession, item: items) -> ErrorType:
|
||||
try:
|
||||
return await DB_SESSION_MNG.insert(cdb, item)
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED
|
||||
|
||||
async def update_item(self, cdb: AsyncSession, item_id, data: dict) -> ErrorType:
|
||||
try:
|
||||
if not data:
|
||||
return ErrorType.SUCCESS
|
||||
query = update(items).where(items.item_id == item_id).values(**data)
|
||||
return await DB_SESSION_MNG.add(cdb, query)
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED
|
||||
|
||||
async def soft_delete(self, cdb: AsyncSession, item_id) -> ErrorType:
|
||||
try:
|
||||
query = update(items).where(items.item_id == item_id).values(deleted=True, updated_at=GTime.UTC())
|
||||
return await DB_SESSION_MNG.add(cdb, query)
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED
|
||||
190
negodata/backend/crud/quotation_crud.py
Normal file
190
negodata/backend/crud/quotation_crud.py
Normal file
@ -0,0 +1,190 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from datetime import datetime
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from sqlalchemy import select, func, and_, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from common.database.db_session_manager import DB_SESSION_MNG
|
||||
from common.database.model.models import quotations, sessions, chats, nego_cards, wild_cards
|
||||
from common.enums import ErrorType
|
||||
from common.logger import LOG
|
||||
from common.utils.gtime import GTime
|
||||
|
||||
|
||||
# 견적 CRUD.
|
||||
class IQuotationCRUD(ABC):
|
||||
@abstractmethod
|
||||
async def search(
|
||||
self, cdb: AsyncSession, status, type_, start_from, start_to, skip, limit
|
||||
) -> Tuple[ErrorType, list, int]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_by_id(self, cdb: AsyncSession, qt_id) -> Tuple[ErrorType, quotations]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def add_quotation(self, cdb: AsyncSession, quotation: quotations) -> ErrorType:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def update_quotation(self, cdb: AsyncSession, qt_id, data: dict) -> ErrorType:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def soft_delete(self, cdb: AsyncSession, qt_id) -> ErrorType:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def list_sessions(self, cdb: AsyncSession, qt_id) -> Tuple[ErrorType, list]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def list_chats(self, cdb: AsyncSession, session_id) -> Tuple[ErrorType, list]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def list_used_cards(self, cdb: AsyncSession, qt_id) -> Tuple[ErrorType, list]:
|
||||
pass
|
||||
|
||||
|
||||
class QuotationCRUD(IQuotationCRUD):
|
||||
async def search(
|
||||
self,
|
||||
cdb: AsyncSession,
|
||||
status: Optional[str],
|
||||
type_: Optional[str],
|
||||
start_from: Optional[datetime],
|
||||
start_to: Optional[datetime],
|
||||
skip: int,
|
||||
limit: int,
|
||||
) -> Tuple[ErrorType, list, int]:
|
||||
try:
|
||||
conditions = [quotations.deleted == False] # noqa: E712
|
||||
if status:
|
||||
conditions.append(quotations.status == status)
|
||||
if type_:
|
||||
conditions.append(quotations.type == type_)
|
||||
if start_from:
|
||||
conditions.append(quotations.start_time >= start_from)
|
||||
if start_to:
|
||||
conditions.append(quotations.start_time <= start_to)
|
||||
where = and_(*conditions)
|
||||
|
||||
cnt_err, cnt_rows = await DB_SESSION_MNG.execute(cdb, select(func.count()).select_from(quotations).where(where))
|
||||
if cnt_err != ErrorType.SUCCESS:
|
||||
return cnt_err, [], 0
|
||||
total = int(cnt_rows[0] or 0) if cnt_rows else 0
|
||||
|
||||
list_err, rows = await DB_SESSION_MNG.execute(
|
||||
cdb,
|
||||
select(quotations).where(where).order_by(quotations.created_at.desc()).offset(skip).limit(limit),
|
||||
)
|
||||
if list_err != ErrorType.SUCCESS:
|
||||
return list_err, [], 0
|
||||
return ErrorType.SUCCESS, list(rows), total
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, [], 0
|
||||
|
||||
async def get_by_id(self, cdb: AsyncSession, qt_id) -> Tuple[ErrorType, quotations]:
|
||||
try:
|
||||
query = select(quotations).where(quotations.qt_id == qt_id, quotations.deleted == False).limit(1) # noqa: E712
|
||||
err_type, row_list = await DB_SESSION_MNG.execute(cdb, query)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
return err_type, None
|
||||
if len(row_list) != 1:
|
||||
return ErrorType.DB_INVALID_KEY, None
|
||||
return ErrorType.SUCCESS, row_list[0]
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, None
|
||||
|
||||
async def add_quotation(self, cdb: AsyncSession, quotation: quotations) -> ErrorType:
|
||||
try:
|
||||
return await DB_SESSION_MNG.insert(cdb, quotation)
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED
|
||||
|
||||
async def update_quotation(self, cdb: AsyncSession, qt_id, data: dict) -> ErrorType:
|
||||
try:
|
||||
if not data:
|
||||
return ErrorType.SUCCESS
|
||||
query = update(quotations).where(quotations.qt_id == qt_id).values(**data)
|
||||
return await DB_SESSION_MNG.add(cdb, query)
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED
|
||||
|
||||
async def soft_delete(self, cdb: AsyncSession, qt_id) -> ErrorType:
|
||||
try:
|
||||
query = update(quotations).where(quotations.qt_id == qt_id).values(deleted=True, updated_at=GTime.UTC())
|
||||
return await DB_SESSION_MNG.add(cdb, query)
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED
|
||||
|
||||
# ----- 견적 상세: 세션 / 채팅 / 사용카드 (읽기 전용) -----
|
||||
async def list_sessions(self, cdb: AsyncSession, qt_id) -> Tuple[ErrorType, list]:
|
||||
try:
|
||||
query = (
|
||||
select(sessions)
|
||||
.where(sessions.quotation_id == qt_id, sessions.deleted == False) # noqa: E712
|
||||
.order_by(sessions.created_at.asc())
|
||||
)
|
||||
err_type, rows = await DB_SESSION_MNG.execute(cdb, query)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
return err_type, []
|
||||
return ErrorType.SUCCESS, list(rows)
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, []
|
||||
|
||||
async def list_chats(self, cdb: AsyncSession, session_id) -> Tuple[ErrorType, list]:
|
||||
try:
|
||||
query = (
|
||||
select(chats)
|
||||
.where(chats.session_id == session_id, chats.deleted == False) # noqa: E712
|
||||
.order_by(chats.seq.asc())
|
||||
)
|
||||
err_type, rows = await DB_SESSION_MNG.execute(cdb, query)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
return err_type, []
|
||||
return ErrorType.SUCCESS, list(rows)
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, []
|
||||
|
||||
async def list_used_cards(self, cdb: AsyncSession, qt_id) -> Tuple[ErrorType, list]:
|
||||
"""견적의 세션들에서 실제 사용된 카드(chats.card_used_yn)를 카드 카탈로그와 조인.
|
||||
반환: [(chat_row, card_id, name, script), ...].
|
||||
card_type 1=nego_cards / 2=wild_cards 양쪽을 LEFT JOIN 해서 어느 쪽이든 잡는다.
|
||||
"""
|
||||
try:
|
||||
query = (
|
||||
select(
|
||||
chats,
|
||||
func.coalesce(nego_cards.nego_card_id, wild_cards.wild_card_id).label("card_pk"),
|
||||
func.coalesce(nego_cards.name, wild_cards.name).label("card_name"),
|
||||
func.coalesce(nego_cards.script, wild_cards.script).label("card_script"),
|
||||
)
|
||||
.join(sessions, sessions.session_id == chats.session_id)
|
||||
.outerjoin(nego_cards, and_(nego_cards.nego_card_id == chats.card_id, chats.card_type == 1))
|
||||
.outerjoin(wild_cards, and_(wild_cards.wild_card_id == chats.card_id, chats.card_type == 2))
|
||||
.where(
|
||||
sessions.quotation_id == qt_id,
|
||||
chats.card_used_yn == True, # noqa: E712
|
||||
chats.deleted == False, # noqa: E712
|
||||
sessions.deleted == False, # noqa: E712
|
||||
)
|
||||
.order_by(chats.created_at.asc())
|
||||
)
|
||||
err_type, rows = await DB_SESSION_MNG.execute(cdb, query)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
return err_type, []
|
||||
return ErrorType.SUCCESS, list(rows)
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, []
|
||||
104
negodata/backend/crud/quotation_setting_crud.py
Normal file
104
negodata/backend/crud/quotation_setting_crud.py
Normal file
@ -0,0 +1,104 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Tuple
|
||||
|
||||
from sqlalchemy import select, func, and_, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from common.database.db_session_manager import DB_SESSION_MNG
|
||||
from common.database.model.models import quotation_settings
|
||||
from common.enums import ErrorType
|
||||
from common.logger import LOG
|
||||
from common.utils.gtime import GTime
|
||||
|
||||
|
||||
# 견적 설정 CRUD. 모든 조회/변경은 user_id 로 스코프된다(유저별 설정).
|
||||
class IQuotationSettingCRUD(ABC):
|
||||
@abstractmethod
|
||||
async def list_by_user(self, cdb: AsyncSession, user_id) -> Tuple[ErrorType, list, int]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_by_id(self, cdb: AsyncSession, qt_setting_id) -> Tuple[ErrorType, quotation_settings]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def add_setting(self, cdb: AsyncSession, setting: quotation_settings) -> ErrorType:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def update_setting(self, cdb: AsyncSession, qt_setting_id, data: dict) -> ErrorType:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def soft_delete(self, cdb: AsyncSession, qt_setting_id) -> ErrorType:
|
||||
pass
|
||||
|
||||
|
||||
class QuotationSettingCRUD(IQuotationSettingCRUD):
|
||||
async def list_by_user(self, cdb: AsyncSession, user_id) -> Tuple[ErrorType, list, int]:
|
||||
try:
|
||||
where = and_(quotation_settings.deleted == False, quotation_settings.user_id == user_id) # noqa: E712
|
||||
|
||||
cnt_err, cnt_rows = await DB_SESSION_MNG.execute(
|
||||
cdb, select(func.count()).select_from(quotation_settings).where(where)
|
||||
)
|
||||
if cnt_err != ErrorType.SUCCESS:
|
||||
return cnt_err, [], 0
|
||||
total = int(cnt_rows[0] or 0) if cnt_rows else 0
|
||||
|
||||
list_err, rows = await DB_SESSION_MNG.execute(
|
||||
cdb,
|
||||
select(quotation_settings).where(where).order_by(quotation_settings.created_at.desc()),
|
||||
)
|
||||
if list_err != ErrorType.SUCCESS:
|
||||
return list_err, [], 0
|
||||
return ErrorType.SUCCESS, list(rows), total
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, [], 0
|
||||
|
||||
async def get_by_id(self, cdb: AsyncSession, qt_setting_id) -> Tuple[ErrorType, quotation_settings]:
|
||||
try:
|
||||
query = (
|
||||
select(quotation_settings)
|
||||
.where(quotation_settings.qt_setting_id == qt_setting_id, quotation_settings.deleted == False) # noqa: E712
|
||||
.limit(1)
|
||||
)
|
||||
err_type, row_list = await DB_SESSION_MNG.execute(cdb, query)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
return err_type, None
|
||||
if len(row_list) != 1:
|
||||
return ErrorType.DB_INVALID_KEY, None
|
||||
return ErrorType.SUCCESS, row_list[0]
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, None
|
||||
|
||||
async def add_setting(self, cdb: AsyncSession, setting: quotation_settings) -> ErrorType:
|
||||
try:
|
||||
return await DB_SESSION_MNG.insert(cdb, setting)
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED
|
||||
|
||||
async def update_setting(self, cdb: AsyncSession, qt_setting_id, data: dict) -> ErrorType:
|
||||
try:
|
||||
if not data:
|
||||
return ErrorType.SUCCESS
|
||||
query = update(quotation_settings).where(quotation_settings.qt_setting_id == qt_setting_id).values(**data)
|
||||
return await DB_SESSION_MNG.add(cdb, query)
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED
|
||||
|
||||
async def soft_delete(self, cdb: AsyncSession, qt_setting_id) -> ErrorType:
|
||||
try:
|
||||
query = (
|
||||
update(quotation_settings)
|
||||
.where(quotation_settings.qt_setting_id == qt_setting_id)
|
||||
.values(deleted=True, updated_at=GTime.UTC())
|
||||
)
|
||||
return await DB_SESSION_MNG.add(cdb, query)
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED
|
||||
148
negodata/backend/crud/supplier_crud.py
Normal file
148
negodata/backend/crud/supplier_crud.py
Normal file
@ -0,0 +1,148 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from sqlalchemy import select, func, and_, or_, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from common.database.db_session_manager import DB_SESSION_MNG
|
||||
from common.database.model.models import suppliers
|
||||
from common.enums import ErrorType
|
||||
from common.logger import LOG
|
||||
from common.utils.gtime import GTime
|
||||
|
||||
|
||||
# 협력사 CRUD. 모든 조회/변경은 company_id 로 스코프된다(멀티테넌트).
|
||||
class ISupplierCRUD(ABC):
|
||||
@abstractmethod
|
||||
async def search(self, cdb: AsyncSession, company_id, search, priority, skip, limit) -> Tuple[ErrorType, list, int]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def code_exists(self, cdb: AsyncSession, company_id, code) -> Tuple[ErrorType, bool]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def existing_codes(self, cdb: AsyncSession, company_id, codes: list) -> Tuple[ErrorType, list]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_by_id(self, cdb: AsyncSession, supplier_id) -> Tuple[ErrorType, suppliers]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def add_supplier(self, cdb: AsyncSession, supplier: suppliers) -> ErrorType:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def update_supplier(self, cdb: AsyncSession, supplier_id, data: dict) -> ErrorType:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def soft_delete(self, cdb: AsyncSession, supplier_id) -> ErrorType:
|
||||
pass
|
||||
|
||||
|
||||
class SupplierCRUD(ISupplierCRUD):
|
||||
async def search(
|
||||
self, cdb: AsyncSession, company_id, search: Optional[str], priority: Optional[str], skip: int, limit: int
|
||||
) -> Tuple[ErrorType, list, int]:
|
||||
try:
|
||||
conditions = [suppliers.deleted == False, suppliers.company_id == company_id] # noqa: E712
|
||||
if search:
|
||||
conditions.append(
|
||||
or_(
|
||||
suppliers.name.ilike(f"%{search}%"),
|
||||
suppliers.code.ilike(f"%{search}%"),
|
||||
suppliers.manager_name.ilike(f"%{search}%"),
|
||||
)
|
||||
)
|
||||
if priority:
|
||||
conditions.append(suppliers.priority == priority)
|
||||
where = and_(*conditions)
|
||||
|
||||
cnt_err, cnt_rows = await DB_SESSION_MNG.execute(cdb, select(func.count()).select_from(suppliers).where(where))
|
||||
if cnt_err != ErrorType.SUCCESS:
|
||||
return cnt_err, [], 0
|
||||
total = int(cnt_rows[0] or 0) if cnt_rows else 0
|
||||
|
||||
list_err, rows = await DB_SESSION_MNG.execute(
|
||||
cdb,
|
||||
select(suppliers).where(where).order_by(suppliers.created_at.desc()).offset(skip).limit(limit),
|
||||
)
|
||||
if list_err != ErrorType.SUCCESS:
|
||||
return list_err, [], 0
|
||||
return ErrorType.SUCCESS, list(rows), total
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, [], 0
|
||||
|
||||
async def code_exists(self, cdb: AsyncSession, company_id, code) -> Tuple[ErrorType, bool]:
|
||||
try:
|
||||
query = select(func.count()).select_from(suppliers).where(
|
||||
suppliers.company_id == company_id,
|
||||
suppliers.code == code,
|
||||
suppliers.deleted == False, # noqa: E712
|
||||
)
|
||||
err_type, rows = await DB_SESSION_MNG.execute(cdb, query)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
return err_type, False
|
||||
cnt = int(rows[0] or 0) if rows else 0
|
||||
return ErrorType.SUCCESS, cnt > 0
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, False
|
||||
|
||||
async def existing_codes(self, cdb: AsyncSession, company_id, codes: list) -> Tuple[ErrorType, list]:
|
||||
try:
|
||||
if not codes:
|
||||
return ErrorType.SUCCESS, []
|
||||
query = select(suppliers.code).where(
|
||||
suppliers.company_id == company_id,
|
||||
suppliers.code.in_(codes),
|
||||
suppliers.deleted == False, # noqa: E712
|
||||
)
|
||||
err_type, rows = await DB_SESSION_MNG.execute(cdb, query)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
return err_type, []
|
||||
return ErrorType.SUCCESS, [c for c in rows if c is not None]
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, []
|
||||
|
||||
async def get_by_id(self, cdb: AsyncSession, supplier_id) -> Tuple[ErrorType, suppliers]:
|
||||
try:
|
||||
query = select(suppliers).where(suppliers.supplier_id == supplier_id, suppliers.deleted == False).limit(1) # noqa: E712
|
||||
err_type, row_list = await DB_SESSION_MNG.execute(cdb, query)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
return err_type, None
|
||||
if len(row_list) != 1:
|
||||
return ErrorType.DB_INVALID_KEY, None
|
||||
return ErrorType.SUCCESS, row_list[0]
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, None
|
||||
|
||||
async def add_supplier(self, cdb: AsyncSession, supplier: suppliers) -> ErrorType:
|
||||
try:
|
||||
return await DB_SESSION_MNG.insert(cdb, supplier)
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED
|
||||
|
||||
async def update_supplier(self, cdb: AsyncSession, supplier_id, data: dict) -> ErrorType:
|
||||
try:
|
||||
if not data:
|
||||
return ErrorType.SUCCESS
|
||||
query = update(suppliers).where(suppliers.supplier_id == supplier_id).values(**data)
|
||||
return await DB_SESSION_MNG.add(cdb, query)
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED
|
||||
|
||||
async def soft_delete(self, cdb: AsyncSession, supplier_id) -> ErrorType:
|
||||
try:
|
||||
query = update(suppliers).where(suppliers.supplier_id == supplier_id).values(deleted=True, updated_at=GTime.UTC())
|
||||
return await DB_SESSION_MNG.add(cdb, query)
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED
|
||||
@ -5,7 +5,7 @@ from sqlalchemy import select, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from common.database.db_session_manager import DB_SESSION_MNG
|
||||
from common.database.model.models import tbl_account
|
||||
from common.database.model.models import users, companies
|
||||
from common.enums import ErrorType
|
||||
from common.logger import LOG
|
||||
from common.utils.gtime import GTime
|
||||
@ -16,27 +16,31 @@ from common.utils.gtime import GTime
|
||||
# - 모든 메서드는 (session, ...) 을 받는다. session 은 람다 호출 시 매니저가 넘겨준다.
|
||||
class IUserCRUD(ABC):
|
||||
@abstractmethod
|
||||
async def get_account_by_id(self, cdb: AsyncSession, user_id: str) -> Tuple[ErrorType, tbl_account]:
|
||||
async def get_user_by_login_id(self, cdb: AsyncSession, login_id: str) -> Tuple[ErrorType, users]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def is_account(self, cdb: AsyncSession, user_id: str) -> ErrorType:
|
||||
async def is_user(self, cdb: AsyncSession, login_id: str) -> ErrorType:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def add_account(self, cdb: AsyncSession, account: tbl_account) -> ErrorType:
|
||||
async def add_user(self, cdb: AsyncSession, user: users) -> ErrorType:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def update_last_login(self, cdb: AsyncSession, user_uid: int) -> ErrorType:
|
||||
async def update_last_accessed(self, cdb: AsyncSession, user_id) -> ErrorType:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_company(self, cdb: AsyncSession, company_id) -> Tuple[ErrorType, companies]:
|
||||
pass
|
||||
|
||||
|
||||
class UserCRUD(IUserCRUD):
|
||||
async def get_account_by_id(self, cdb: AsyncSession, user_id: str) -> Tuple[ErrorType, tbl_account]:
|
||||
async def get_user_by_login_id(self, cdb: AsyncSession, login_id: str) -> Tuple[ErrorType, users]:
|
||||
try:
|
||||
query = select(tbl_account).where(tbl_account.id == user_id).limit(1)
|
||||
err_type, row_list = await DB_SESSION_MNG.execute(cdb, query, f"get_account_by_id(ID:{user_id}) failed.")
|
||||
query = select(users).where(users.id == login_id, users.deleted == False).limit(1) # noqa: E712
|
||||
err_type, row_list = await DB_SESSION_MNG.execute(cdb, query, f"get_user_by_login_id(ID:{login_id}) failed.")
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
return err_type, None
|
||||
if len(row_list) != 1:
|
||||
@ -46,9 +50,9 @@ class UserCRUD(IUserCRUD):
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, None
|
||||
|
||||
async def is_account(self, cdb: AsyncSession, user_id: str) -> ErrorType:
|
||||
async def is_user(self, cdb: AsyncSession, login_id: str) -> ErrorType:
|
||||
try:
|
||||
query = select(tbl_account).where(tbl_account.id == user_id).limit(1)
|
||||
query = select(users).where(users.id == login_id, users.deleted == False).limit(1) # noqa: E712
|
||||
err_type, row_list = await DB_SESSION_MNG.execute(cdb, query)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
return err_type
|
||||
@ -59,17 +63,30 @@ class UserCRUD(IUserCRUD):
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED
|
||||
|
||||
async def add_account(self, cdb: AsyncSession, account: tbl_account) -> ErrorType:
|
||||
async def add_user(self, cdb: AsyncSession, user: users) -> ErrorType:
|
||||
try:
|
||||
return await DB_SESSION_MNG.insert(cdb, account)
|
||||
return await DB_SESSION_MNG.insert(cdb, user)
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED
|
||||
|
||||
async def update_last_login(self, cdb: AsyncSession, user_uid: int) -> ErrorType:
|
||||
async def update_last_accessed(self, cdb: AsyncSession, user_id) -> ErrorType:
|
||||
try:
|
||||
query = update(tbl_account).where(tbl_account.uid == user_uid).values(last_login_at=GTime.UTC())
|
||||
query = update(users).where(users.user_id == user_id).values(last_accessed_at=GTime.UTC())
|
||||
return await DB_SESSION_MNG.add(cdb, query)
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED
|
||||
|
||||
async def get_company(self, cdb: AsyncSession, company_id) -> Tuple[ErrorType, companies]:
|
||||
try:
|
||||
query = select(companies).where(companies.company_id == company_id, companies.deleted == False).limit(1) # noqa: E712
|
||||
err_type, row_list = await DB_SESSION_MNG.execute(cdb, query)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
return err_type, None
|
||||
if len(row_list) != 1:
|
||||
return ErrorType.DB_INVALID_KEY, None
|
||||
return ErrorType.SUCCESS, row_list[0]
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, None
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
fastapi
|
||||
uvicorn[standard]
|
||||
sqlalchemy>=2.0
|
||||
greenlet
|
||||
asyncpg
|
||||
python-jose[cryptography]
|
||||
bcrypt
|
||||
orjson
|
||||
pydantic>=2.0
|
||||
python-multipart
|
||||
openpyxl
|
||||
|
||||
@ -2,12 +2,20 @@ import time
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.middleware.gzip import GZipMiddleware
|
||||
|
||||
from common.database.db_session_manager import DB_SESSION_MNG
|
||||
from common.logger import LOG
|
||||
from common.utils.gtime import GTime
|
||||
from config.server_configs import web_server_config
|
||||
import router.v1.auth.account
|
||||
import router.v1.item.item
|
||||
import router.v1.supplier.supplier
|
||||
import router.v1.card.card
|
||||
import router.v1.quotation.quotation
|
||||
import router.v1.quotation_setting.quotation_setting
|
||||
import router.v1.enums.enums
|
||||
|
||||
API_SERVER_START_TIME = GTime.UTCStr()
|
||||
|
||||
@ -22,6 +30,15 @@ async def lifespan(app: FastAPI):
|
||||
|
||||
app = FastAPI(title="Negodata Api Server", lifespan=lifespan)
|
||||
|
||||
# CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=[web_server_config.client_url],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Accept-Encoding: gzip 요청에 대해 1000 bytes 이상 응답을 압축.
|
||||
app.add_middleware(GZipMiddleware, minimum_size=1000)
|
||||
|
||||
@ -42,3 +59,9 @@ async def healthz():
|
||||
|
||||
# 각 도메인 라우터를 등록한다. 새 기능 추가 시 router.v1.<domain>.<file> 를 import 후 include.
|
||||
app.include_router(router.v1.auth.account.router)
|
||||
app.include_router(router.v1.item.item.router)
|
||||
app.include_router(router.v1.supplier.supplier.router)
|
||||
app.include_router(router.v1.card.card.router)
|
||||
app.include_router(router.v1.quotation.quotation.router)
|
||||
app.include_router(router.v1.quotation_setting.quotation_setting.router)
|
||||
app.include_router(router.v1.enums.enums.router)
|
||||
|
||||
@ -4,7 +4,7 @@ from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
from common.models.gmodel import UserInfo
|
||||
from router.v1.validator.dependencies import IsValidAccessToken, IsValidRefreshToken, RemoveNoneResponse
|
||||
from services.auth_service import AuthService
|
||||
from .protocol import Req_CreateAccount, Req_Login, Res_CreateAccount, Res_Login, Res_RefreshToken
|
||||
from .protocol import Req_CreateAccount, Req_Login, Res_CreateAccount, Res_Login, Res_Me, Res_RefreshToken
|
||||
|
||||
security = HTTPBearer()
|
||||
|
||||
@ -14,12 +14,14 @@ router = APIRouter(prefix="/v1/auth", tags=["Auth"], responses={404: {"descripti
|
||||
|
||||
@router.post(path="/login", response_model=Res_Login, summary="로그인", description="id/pw 로 로그인하고 JWT 토큰을 발급한다.")
|
||||
async def login(request: Request, req: Req_Login, service: AuthService = Depends()):
|
||||
return RemoveNoneResponse(await service.attempt_login(req.id, req.pw, request.client.host))
|
||||
return RemoveNoneResponse(await service.attempt_login(req.id, req.password, request.client.host))
|
||||
|
||||
|
||||
@router.post(path="/create", response_model=Res_CreateAccount, summary="계정 생성", description="새 계정을 생성한다.")
|
||||
async def create_account(request: Request, req: Req_CreateAccount, service: AuthService = Depends()):
|
||||
return RemoveNoneResponse(await service.create_account(req.id, req.pw, req.nickname, request.client.host))
|
||||
async def create_account(req: Req_CreateAccount, service: AuthService = Depends()):
|
||||
return RemoveNoneResponse(
|
||||
await service.create_account(req.id, req.password, req.company_id, req.name, req.email, req.contact_number, req.role)
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -35,8 +37,9 @@ async def refresh_token(service: AuthService = Depends(), credentials: HTTPAutho
|
||||
|
||||
@router.get(
|
||||
path="/me",
|
||||
summary="내 정보 (보호된 엔드포인트 예시)",
|
||||
description="유효한 access 토큰이 있어야 호출 가능. 토큰 검증 결과 UserInfo 를 주입받는다.",
|
||||
response_model=Res_Me,
|
||||
summary="내 정보",
|
||||
description="유효한 access 토큰이 있어야 호출 가능. 토큰의 유저+회사 정보를 반환한다.",
|
||||
)
|
||||
async def me(user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
return {"uid": user_info.uid, "id": user_info.id, "nickname": user_info.nickname}
|
||||
async def me(service: AuthService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
return RemoveNoneResponse(await service.get_me(user_info))
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from common.enums import UserRole
|
||||
from common.models.gmodel import Res_WebPacketProtocol, WebPacketProtocol
|
||||
|
||||
|
||||
@ -10,25 +13,45 @@ class AuthProtocol(WebPacketProtocol):
|
||||
|
||||
class Req_Login(AuthProtocol):
|
||||
id: str = ""
|
||||
pw: str = ""
|
||||
password: str = ""
|
||||
|
||||
|
||||
class Res_Login(Res_WebPacketProtocol):
|
||||
uid: int = Field(0, description="user uid", json_schema_extra={"format": "int64"})
|
||||
nickname: str = ""
|
||||
access_token: str = ""
|
||||
refresh_token: str = ""
|
||||
token_type: str = "bearer"
|
||||
|
||||
|
||||
class Req_CreateAccount(AuthProtocol):
|
||||
id: str = ""
|
||||
pw: str = ""
|
||||
nickname: str = ""
|
||||
password: str = ""
|
||||
company_id: str = ""
|
||||
name: str = ""
|
||||
email: str = ""
|
||||
contact_number: str = ""
|
||||
role: int = UserRole.USER.value
|
||||
|
||||
|
||||
class Res_CreateAccount(Res_WebPacketProtocol):
|
||||
uid: int = Field(0, description="생성된 user uid", json_schema_extra={"format": "int64"})
|
||||
user_id: str = ""
|
||||
|
||||
|
||||
class Res_RefreshToken(Res_WebPacketProtocol):
|
||||
access_token: str = ""
|
||||
token_type: str = "bearer"
|
||||
|
||||
|
||||
class CompanyData(WebPacketProtocol):
|
||||
company_id: str = ""
|
||||
name: str = ""
|
||||
|
||||
|
||||
class Res_Me(Res_WebPacketProtocol):
|
||||
user_id: str = ""
|
||||
id: str = ""
|
||||
name: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
contact_number: Optional[str] = None
|
||||
role: int = UserRole.USER.value
|
||||
role_label: str = ""
|
||||
company: Optional[CompanyData] = Field(default=None)
|
||||
|
||||
50
negodata/backend/router/v1/card/card.py
Normal file
50
negodata/backend/router/v1/card/card.py
Normal file
@ -0,0 +1,50 @@
|
||||
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
|
||||
from services.card_service import CardService
|
||||
from .protocol import (
|
||||
Req_CreateCard,
|
||||
Req_UpdateCard,
|
||||
Res_Card,
|
||||
Res_CardList,
|
||||
Res_DeleteCard,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/v1/card", tags=["Card"], responses={404: {"description": "Not found"}})
|
||||
|
||||
|
||||
@router.get(path="/list", response_model=Res_CardList, summary="협상카드 목록")
|
||||
async def list_cards(
|
||||
service: CardService = Depends(),
|
||||
user_info: UserInfo = Depends(IsValidAccessToken),
|
||||
search: str | None = Query(None, description="카드명/카드번호/스크립트 검색"),
|
||||
pg: PageParams = Depends(),
|
||||
):
|
||||
return RemoveNoneResponse(await service.list_cards(user_info.user_id, search, pg))
|
||||
|
||||
|
||||
@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))
|
||||
)
|
||||
|
||||
|
||||
@router.get(path="/{card_id}", response_model=Res_Card, summary="협상카드 조회")
|
||||
async def get_card(card_id: UUID, service: CardService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
return RemoveNoneResponse(await service.get_card(user_info.user_id, str(card_id)))
|
||||
|
||||
|
||||
@router.patch(path="/update/{card_id}", response_model=Res_Card, summary="협상카드 수정")
|
||||
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)))
|
||||
|
||||
|
||||
@router.delete(path="/delete/{card_id}", response_model=Res_DeleteCard, summary="협상카드 삭제")
|
||||
async def delete_card(card_id: UUID, service: CardService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
return RemoveNoneResponse(await service.delete_card(user_info.user_id, str(card_id)))
|
||||
63
negodata/backend/router/v1/card/protocol.py
Normal file
63
negodata/backend/router/v1/card/protocol.py
Normal file
@ -0,0 +1,63 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import ConfigDict
|
||||
|
||||
from common.enums import CardStatus
|
||||
from common.models.gmodel import Res_PageProtocol, Res_WebPacketProtocol, WebPacketProtocol
|
||||
|
||||
|
||||
class CardProtocol(WebPacketProtocol):
|
||||
pass
|
||||
|
||||
|
||||
class Req_CreateCard(CardProtocol):
|
||||
is_wildcard: bool = False
|
||||
name: Optional[str] = None
|
||||
number: Optional[str] = None
|
||||
script: Optional[str] = None
|
||||
edit_script: Optional[Any] = None
|
||||
status: int = CardStatus.ACTIVE.value # 와일드카드 적용 여부(available 매핑). 일반카드는 무시.
|
||||
condition: Optional[str] = None # 와일드카드 전용
|
||||
memo: Optional[str] = None # 와일드카드 전용
|
||||
|
||||
|
||||
class Req_UpdateCard(CardProtocol):
|
||||
name: Optional[str] = None
|
||||
number: Optional[str] = None
|
||||
script: Optional[str] = None
|
||||
edit_script: Optional[Any] = None
|
||||
status: Optional[int] = None
|
||||
condition: Optional[str] = None
|
||||
memo: Optional[str] = None
|
||||
|
||||
|
||||
# 통합 카드 표현(nego_cards + wild_cards 공통). nego_card_id 는 출처 테이블의 PK 를 그대로 담는다.
|
||||
class CardData(WebPacketProtocol):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
nego_card_id: uuid.UUID # 통합 식별자(일반=nego_card_id / 와일드=wild_card_id)
|
||||
user_id: Optional[uuid.UUID] = None
|
||||
is_wildcard: bool = False
|
||||
name: Optional[str] = None
|
||||
number: Optional[str] = None
|
||||
script: Optional[str] = None
|
||||
edit_script: Optional[Any] = None
|
||||
status: int = CardStatus.ACTIVE.value
|
||||
condition: Optional[str] = None
|
||||
memo: Optional[str] = None
|
||||
created_at: Optional[datetime] = None
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
|
||||
class Res_Card(Res_WebPacketProtocol):
|
||||
card: Optional[CardData] = None
|
||||
|
||||
|
||||
class Res_CardList(Res_PageProtocol):
|
||||
cards: list[CardData] = []
|
||||
|
||||
|
||||
class Res_DeleteCard(Res_WebPacketProtocol):
|
||||
pass
|
||||
21
negodata/backend/router/v1/enums/enums.py
Normal file
21
negodata/backend/router/v1/enums/enums.py
Normal file
@ -0,0 +1,21 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from common.enums import DOMAIN_ENUMS, ENUM_LABELS
|
||||
from router.v1.validator.dependencies import RemoveNoneResponse
|
||||
from .protocol import EnumOption, Res_Enums
|
||||
|
||||
# 도메인 코드 enum 메타데이터(공용). 프론트가 페이지 진입 시 드롭다운을 이걸로 채운다.
|
||||
router = APIRouter(prefix="/v1", tags=["Enums"], responses={404: {"description": "Not found"}})
|
||||
|
||||
|
||||
@router.get(path="/enums", response_model=Res_Enums, summary="도메인 코드 enum 전체")
|
||||
async def list_enums():
|
||||
res = Res_Enums()
|
||||
res.enums = {
|
||||
key: [
|
||||
EnumOption(value=member.value, name=member.name, label=ENUM_LABELS.get(member, member.name))
|
||||
for member in enum_cls
|
||||
]
|
||||
for key, enum_cls in DOMAIN_ENUMS.items()
|
||||
}
|
||||
return RemoveNoneResponse(res)
|
||||
15
negodata/backend/router/v1/enums/protocol.py
Normal file
15
negodata/backend/router/v1/enums/protocol.py
Normal file
@ -0,0 +1,15 @@
|
||||
from common.models.gmodel import Res_WebPacketProtocol, WebPacketProtocol
|
||||
|
||||
|
||||
class EnumsProtocol(WebPacketProtocol):
|
||||
pass
|
||||
|
||||
|
||||
class EnumOption(WebPacketProtocol):
|
||||
value: int
|
||||
name: str
|
||||
label: str
|
||||
|
||||
|
||||
class Res_Enums(Res_WebPacketProtocol):
|
||||
enums: dict[str, list[EnumOption]] = {}
|
||||
96
negodata/backend/router/v1/item/item.py
Normal file
96
negodata/backend/router/v1/item/item.py
Normal file
@ -0,0 +1,96 @@
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, File, Query, UploadFile
|
||||
|
||||
from common.models.gmodel import PageParams, UserInfo
|
||||
from router.v1.validator.dependencies import IsValidAccessToken, RemoveNoneResponse
|
||||
from services.item_service import ItemService
|
||||
from .protocol import (
|
||||
Req_CheckCodes,
|
||||
Req_CreateItem,
|
||||
Req_UpdateItem,
|
||||
Res_CheckCodes,
|
||||
Res_DeleteItem,
|
||||
Res_ExcelUpload,
|
||||
Res_Item,
|
||||
Res_ItemCategories,
|
||||
Res_ItemList,
|
||||
Res_LowestPriceResult,
|
||||
Res_LowestPriceTrigger,
|
||||
)
|
||||
|
||||
# 라우터(컨트롤러). 인증(Depends(IsValidAccessToken))으로 UserInfo 를 받아 company_id 로 스코프.
|
||||
router = APIRouter(prefix="/v1/item", tags=["Item"], responses={404: {"description": "Not found"}})
|
||||
|
||||
|
||||
@router.get(path="/list", response_model=Res_ItemList, summary="상품 목록")
|
||||
async def list_items(
|
||||
service: ItemService = Depends(),
|
||||
user_info: UserInfo = Depends(IsValidAccessToken),
|
||||
search: str | None = Query(None, description="상품명/상품코드 검색"),
|
||||
category: str | None = Query(None, description="카테고리 필터"),
|
||||
pg: PageParams = Depends(),
|
||||
):
|
||||
return RemoveNoneResponse(await service.list_items(user_info.company_id, search, category, pg))
|
||||
|
||||
|
||||
@router.get(path="/categories", response_model=Res_ItemCategories, summary="상품 카테고리 목록(distinct)")
|
||||
async def list_item_categories(service: ItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
return RemoveNoneResponse(await service.list_categories(user_info.company_id))
|
||||
|
||||
|
||||
@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))
|
||||
)
|
||||
|
||||
|
||||
@router.post(path="/check-codes", response_model=Res_CheckCodes, summary="코드 중복 사전검사(업로드 즉시)")
|
||||
async def check_item_codes(req: Req_CheckCodes, service: ItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
return RemoveNoneResponse(await service.check_codes(user_info.company_id, req.codes))
|
||||
|
||||
|
||||
@router.post(path="/upload-excel", response_model=Res_ExcelUpload, summary="엑셀 일괄 등록(스텁)")
|
||||
async def upload_items_excel(
|
||||
service: ItemService = Depends(),
|
||||
user_info: UserInfo = Depends(IsValidAccessToken),
|
||||
file: UploadFile = File(...),
|
||||
):
|
||||
return RemoveNoneResponse(
|
||||
Res_ExcelUpload(received_filename=file.filename, status="not_implemented", message="엑셀 일괄 등록은 추후 구현")
|
||||
)
|
||||
|
||||
|
||||
@router.get(path="/{item_id}", response_model=Res_Item, summary="상품 조회")
|
||||
async def get_item(item_id: UUID, service: ItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
return RemoveNoneResponse(await service.get_item(user_info.company_id, str(item_id)))
|
||||
|
||||
|
||||
@router.patch(path="/update/{item_id}", response_model=Res_Item, summary="상품 수정")
|
||||
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)))
|
||||
|
||||
|
||||
@router.delete(path="/delete/{item_id}", response_model=Res_DeleteItem, summary="상품 삭제")
|
||||
async def delete_item(item_id: UUID, service: ItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
return RemoveNoneResponse(await service.delete_item(user_info.company_id, str(item_id)))
|
||||
|
||||
|
||||
@router.post(path="/{item_id}/lowest-price", response_model=Res_LowestPriceTrigger, summary="최저가 수집 요청(스텁)")
|
||||
async def trigger_lowest_price(item_id: UUID, service: ItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
# 존재/소유 확인만 (없으면 result 에 ITEM_NOT_FOUND)
|
||||
got = await service.get_item(user_info.company_id, str(item_id))
|
||||
if got.item is None:
|
||||
return RemoveNoneResponse(got)
|
||||
return RemoveNoneResponse(Res_LowestPriceTrigger(item_id=str(item_id), status="queued", message="최저가 수집 요청됨(스텁)"))
|
||||
|
||||
|
||||
@router.get(path="/{item_id}/lowest-price", response_model=Res_LowestPriceResult, summary="최저가 수집 결과(스텁)")
|
||||
async def get_lowest_price(item_id: UUID, service: ItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
got = await service.get_item(user_info.company_id, str(item_id))
|
||||
if got.item is None:
|
||||
return RemoveNoneResponse(got)
|
||||
return RemoveNoneResponse(Res_LowestPriceResult(item_id=str(item_id), results=[], message="최저가 수집 결과 없음(스텁)"))
|
||||
125
negodata/backend/router/v1/item/protocol.py
Normal file
125
negodata/backend/router/v1/item/protocol.py
Normal file
@ -0,0 +1,125 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import ConfigDict
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
class ItemData(WebPacketProtocol):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
item_id: uuid.UUID
|
||||
company_id: uuid.UUID
|
||||
user_id: uuid.UUID
|
||||
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
|
||||
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
|
||||
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_ExcelUpload(Res_WebPacketProtocol):
|
||||
received_filename: Optional[str] = None
|
||||
status: str = ""
|
||||
message: str = ""
|
||||
|
||||
|
||||
class Res_LowestPriceTrigger(Res_WebPacketProtocol):
|
||||
item_id: str = ""
|
||||
status: str = ""
|
||||
message: str = ""
|
||||
|
||||
|
||||
class Res_LowestPriceResult(Res_WebPacketProtocol):
|
||||
item_id: str = ""
|
||||
results: list = []
|
||||
message: str = ""
|
||||
152
negodata/backend/router/v1/quotation/protocol.py
Normal file
152
negodata/backend/router/v1/quotation/protocol.py
Normal file
@ -0,0 +1,152 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import ConfigDict
|
||||
|
||||
from common.models.gmodel import Res_PageProtocol, Res_WebPacketProtocol, WebPacketProtocol
|
||||
|
||||
|
||||
class QuotationProtocol(WebPacketProtocol):
|
||||
pass
|
||||
|
||||
|
||||
class Req_CreateQuotation(QuotationProtocol):
|
||||
qt_setting_id: uuid.UUID
|
||||
version_id: uuid.UUID
|
||||
name: str = ""
|
||||
number: str = ""
|
||||
type: int = 0
|
||||
status: int = 0
|
||||
start_time: datetime
|
||||
end_time: datetime
|
||||
round: int = 1
|
||||
manager_name: Optional[str] = None
|
||||
manager_email: Optional[str] = None
|
||||
manager_contact_number: Optional[str] = None
|
||||
memo: Optional[str] = None
|
||||
|
||||
|
||||
class QuotationData(WebPacketProtocol):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
qt_id: uuid.UUID
|
||||
user_id: uuid.UUID
|
||||
qt_setting_id: uuid.UUID
|
||||
version_id: uuid.UUID
|
||||
name: str
|
||||
number: str
|
||||
type: int
|
||||
round: int = 1
|
||||
status: int
|
||||
start_time: datetime
|
||||
end_time: datetime
|
||||
manager_name: Optional[str] = None
|
||||
manager_email: Optional[str] = None
|
||||
manager_contact_number: Optional[str] = None
|
||||
memo: Optional[str] = None
|
||||
iteration: int = 0
|
||||
preferred_sp_yn: Optional[bool] = None
|
||||
preferred_sp_id: Optional[uuid.UUID] = None
|
||||
preferred_sp_name: Optional[str] = None
|
||||
equal_bid_yn: Optional[bool] = None
|
||||
equal_bid_data: Optional[Any] = None
|
||||
created_at: Optional[datetime] = None
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
|
||||
class Res_Quotation(Res_WebPacketProtocol):
|
||||
quotation: Optional[QuotationData] = None
|
||||
|
||||
|
||||
class Res_QuotationList(Res_PageProtocol):
|
||||
quotations: list[QuotationData] = []
|
||||
|
||||
|
||||
class Res_DeleteQuotation(Res_WebPacketProtocol):
|
||||
pass
|
||||
|
||||
|
||||
class AsyncJob(WebPacketProtocol):
|
||||
status: str = ""
|
||||
message: str = ""
|
||||
|
||||
|
||||
class Res_CreateQuotation(Res_WebPacketProtocol):
|
||||
quotation: Optional[QuotationData] = None
|
||||
async_job: Optional[AsyncJob] = None
|
||||
|
||||
|
||||
class Res_QuotationStatus(Res_WebPacketProtocol):
|
||||
qt_id: Optional[uuid.UUID] = None
|
||||
job_status: int = 0
|
||||
message: str = ""
|
||||
|
||||
|
||||
class SessionData(WebPacketProtocol):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
session_id: uuid.UUID
|
||||
qt_id: uuid.UUID
|
||||
supplier_id: uuid.UUID
|
||||
item_id: uuid.UUID
|
||||
qt_number: str
|
||||
qt_round: int
|
||||
qt_type: int
|
||||
target_price: int
|
||||
status: int
|
||||
bid_price: Optional[int] = None
|
||||
bid_at: Optional[datetime] = None
|
||||
end_time: datetime
|
||||
reject_reason: Optional[str] = None
|
||||
reject_price: Optional[int] = None
|
||||
reject_delivery_type: Optional[int] = None
|
||||
|
||||
|
||||
class Res_QuotationSessions(Res_WebPacketProtocol):
|
||||
qt_id: Optional[uuid.UUID] = None
|
||||
sessions: list[SessionData] = []
|
||||
total: int = 0
|
||||
|
||||
|
||||
class ChatMessageData(WebPacketProtocol):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
chat_id: uuid.UUID
|
||||
session_id: uuid.UUID
|
||||
card_id: Optional[uuid.UUID] = None
|
||||
index: int
|
||||
sender: int
|
||||
target_price: int
|
||||
card_used_yn: Optional[bool] = None
|
||||
indicator_value: Optional[float] = None
|
||||
card_type: Optional[int] = None
|
||||
|
||||
|
||||
class Res_SessionChat(Res_WebPacketProtocol):
|
||||
session_id: Optional[uuid.UUID] = None
|
||||
messages: list[ChatMessageData] = []
|
||||
|
||||
|
||||
class Res_QuotationResult(Res_WebPacketProtocol):
|
||||
qt_id: Optional[uuid.UUID] = None
|
||||
winner_supplier_id: Optional[uuid.UUID] = None
|
||||
winner_supplier_name: Optional[str] = None
|
||||
is_equal_bid: Optional[bool] = None
|
||||
equal_bid_data: Optional[Any] = None
|
||||
result_count: int = 0
|
||||
|
||||
|
||||
class QuotationCardData(WebPacketProtocol):
|
||||
session_card_id: uuid.UUID
|
||||
qt_id: Optional[uuid.UUID] = None
|
||||
nego_card_id: Optional[uuid.UUID] = None
|
||||
wild_card_id: Optional[uuid.UUID] = None
|
||||
type: Optional[int] = None
|
||||
name: Optional[str] = None
|
||||
script: Optional[str] = None
|
||||
|
||||
|
||||
class Res_QuotationCards(Res_WebPacketProtocol):
|
||||
qt_id: Optional[uuid.UUID] = None
|
||||
cards: list[QuotationCardData] = []
|
||||
89
negodata/backend/router/v1/quotation/quotation.py
Normal file
89
negodata/backend/router/v1/quotation/quotation.py
Normal file
@ -0,0 +1,89 @@
|
||||
from datetime import datetime
|
||||
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
|
||||
from services.quotation_service import QuotationService
|
||||
from .protocol import (
|
||||
Req_CreateQuotation,
|
||||
Res_CreateQuotation,
|
||||
Res_DeleteQuotation,
|
||||
Res_Quotation,
|
||||
Res_QuotationCards,
|
||||
Res_QuotationList,
|
||||
Res_QuotationResult,
|
||||
Res_QuotationSessions,
|
||||
Res_QuotationStatus,
|
||||
Res_SessionChat,
|
||||
)
|
||||
|
||||
# 라우터(컨트롤러). 인증(Depends(IsValidAccessToken))으로 UserInfo 를 받는다.
|
||||
# quotations 테이블에 company_id 가 없어 회사 스코핑은 하지 않는다(토큰 검증만).
|
||||
# 라우팅 주의: 정적/하위 경로(/list, /create, /{qt_id}/status ...)를 /{qt_id} 보다 먼저 선언해야
|
||||
# /{qt_id} 가 /list 등을 가로채지 않는다.
|
||||
router = APIRouter(prefix="/v1/quotation", tags=["Quotation"], responses={404: {"description": "Not found"}})
|
||||
|
||||
|
||||
# ----- 실제 엔드포인트 (DB) -----
|
||||
@router.get(path="/list", response_model=Res_QuotationList, summary="견적 목록")
|
||||
async def list_quotations(
|
||||
service: QuotationService = Depends(),
|
||||
user_info: UserInfo = Depends(IsValidAccessToken),
|
||||
status: str | None = Query(None, description="상태 필터(정확히 일치)"),
|
||||
type: str | None = Query(None, description="유형 필터(정확히 일치)"),
|
||||
start_from: datetime | None = Query(None, description="시작일시 이후(ISO)"),
|
||||
start_to: datetime | None = Query(None, description="시작일시 이전(ISO)"),
|
||||
pg: PageParams = Depends(),
|
||||
):
|
||||
return RemoveNoneResponse(await service.list_quotations(status, type, start_from, start_to, pg))
|
||||
|
||||
|
||||
@router.post(path="/create", response_model=Res_CreateQuotation, summary="견적 생성")
|
||||
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)))
|
||||
|
||||
|
||||
@router.post(path="/stop/{qt_id}", response_model=Res_Quotation, summary="견적 마감")
|
||||
async def stop_quotation(qt_id: UUID, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
return RemoveNoneResponse(await service.stop_quotation(str(qt_id)))
|
||||
|
||||
|
||||
# ----- 견적 상세 (FK로 연결된 하위 데이터 / 일부는 모델 미존재로 스텁) -----
|
||||
@router.get(path="/{qt_id}/status", response_model=Res_QuotationStatus, summary="견적 상태 조회")
|
||||
async def get_quotation_status(qt_id: UUID, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
return RemoveNoneResponse(await service.get_status(str(qt_id)))
|
||||
|
||||
|
||||
@router.get(path="/{qt_id}/sessions", response_model=Res_QuotationSessions, summary="참여현황")
|
||||
async def get_quotation_sessions(qt_id: UUID, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
return RemoveNoneResponse(await service.list_sessions(str(qt_id)))
|
||||
|
||||
|
||||
@router.get(path="/session/{session_id}/chat", response_model=Res_SessionChat, summary="채팅 상세")
|
||||
async def get_session_chat(session_id: UUID, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
return RemoveNoneResponse(await service.list_chats(str(session_id)))
|
||||
|
||||
|
||||
@router.get(path="/{qt_id}/result", response_model=Res_QuotationResult, summary="낙찰 결과")
|
||||
async def get_quotation_result(qt_id: UUID, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
return RemoveNoneResponse(await service.get_result(str(qt_id)))
|
||||
|
||||
|
||||
@router.get(path="/{qt_id}/cards", response_model=Res_QuotationCards, summary="견적 사용 카드")
|
||||
async def get_quotation_cards(qt_id: UUID, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
return RemoveNoneResponse(await service.list_cards(str(qt_id)))
|
||||
|
||||
|
||||
@router.delete(path="/delete/{qt_id}", response_model=Res_DeleteQuotation, summary="견적 삭제")
|
||||
async def delete_quotation(qt_id: UUID, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
return RemoveNoneResponse(await service.delete_quotation(str(qt_id)))
|
||||
|
||||
|
||||
# ----- 단건 조회 (정적/하위 경로 뒤에 선언) -----
|
||||
@router.get(path="/{qt_id}", response_model=Res_Quotation, summary="견적 조회")
|
||||
async def get_quotation(qt_id: UUID, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
return RemoveNoneResponse(await service.get_quotation(str(qt_id)))
|
||||
48
negodata/backend/router/v1/quotation_setting/protocol.py
Normal file
48
negodata/backend/router/v1/quotation_setting/protocol.py
Normal file
@ -0,0 +1,48 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import ConfigDict
|
||||
|
||||
from common.models.gmodel import Res_WebPacketProtocol, WebPacketProtocol
|
||||
|
||||
|
||||
class QuotationSettingProtocol(WebPacketProtocol):
|
||||
pass
|
||||
|
||||
|
||||
class Req_CreateQuotationSetting(QuotationSettingProtocol):
|
||||
target_margin_rate: float
|
||||
anchoring_value: float = 0.01
|
||||
card_count: int = 3
|
||||
|
||||
|
||||
class Req_UpdateQuotationSetting(QuotationSettingProtocol):
|
||||
target_margin_rate: Optional[float] = None
|
||||
anchoring_value: Optional[float] = None
|
||||
card_count: Optional[int] = None
|
||||
|
||||
|
||||
class QuotationSettingData(WebPacketProtocol):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
qt_setting_id: uuid.UUID
|
||||
user_id: Optional[uuid.UUID] = None
|
||||
target_margin_rate: float
|
||||
anchoring_value: float
|
||||
card_count: int
|
||||
created_at: Optional[datetime] = None
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
|
||||
class Res_QuotationSetting(Res_WebPacketProtocol):
|
||||
setting: Optional[QuotationSettingData] = None
|
||||
|
||||
|
||||
class Res_QuotationSettingList(Res_WebPacketProtocol):
|
||||
settings: list[QuotationSettingData] = []
|
||||
total: int = 0
|
||||
|
||||
|
||||
class Res_DeleteQuotationSetting(Res_WebPacketProtocol):
|
||||
pass
|
||||
@ -0,0 +1,52 @@
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from common.models.gmodel import UserInfo
|
||||
from router.v1.validator.dependencies import IsValidAccessToken, RemoveNoneResponse
|
||||
from services.quotation_setting_service import QuotationSettingService
|
||||
from .protocol import (
|
||||
Req_CreateQuotationSetting,
|
||||
Req_UpdateQuotationSetting,
|
||||
Res_DeleteQuotationSetting,
|
||||
Res_QuotationSetting,
|
||||
Res_QuotationSettingList,
|
||||
)
|
||||
|
||||
# 라우터(컨트롤러). 인증(Depends(IsValidAccessToken))으로 UserInfo 를 받아 user_id 로 스코프.
|
||||
router = APIRouter(
|
||||
prefix="/v1/quotation-setting", tags=["QuotationSetting"], responses={404: {"description": "Not found"}}
|
||||
)
|
||||
|
||||
|
||||
@router.get(path="/list", response_model=Res_QuotationSettingList, summary="견적 설정 목록")
|
||||
async def list_settings(service: QuotationSettingService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
return RemoveNoneResponse(await service.list_settings(user_info.user_id))
|
||||
|
||||
|
||||
@router.post(path="/create", response_model=Res_QuotationSetting, summary="견적 설정 등록")
|
||||
async def create_setting(
|
||||
req: Req_CreateQuotationSetting,
|
||||
service: QuotationSettingService = Depends(),
|
||||
user_info: UserInfo = Depends(IsValidAccessToken),
|
||||
):
|
||||
return RemoveNoneResponse(await service.create_setting(user_info.user_id, req.model_dump(exclude_unset=True)))
|
||||
|
||||
|
||||
@router.patch(path="/update/{qt_setting_id}", response_model=Res_QuotationSetting, summary="견적 설정 수정")
|
||||
async def update_setting(
|
||||
qt_setting_id: UUID,
|
||||
req: Req_UpdateQuotationSetting,
|
||||
service: QuotationSettingService = Depends(),
|
||||
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))
|
||||
)
|
||||
|
||||
|
||||
@router.delete(path="/delete/{qt_setting_id}", response_model=Res_DeleteQuotationSetting, summary="견적 설정 삭제")
|
||||
async def delete_setting(
|
||||
qt_setting_id: UUID, service: QuotationSettingService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)
|
||||
):
|
||||
return RemoveNoneResponse(await service.delete_setting(user_info.user_id, str(qt_setting_id)))
|
||||
71
negodata/backend/router/v1/supplier/protocol.py
Normal file
71
negodata/backend/router/v1/supplier/protocol.py
Normal file
@ -0,0 +1,71 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import ConfigDict
|
||||
|
||||
from common.models.gmodel import Res_PageProtocol, Res_WebPacketProtocol, WebPacketProtocol
|
||||
|
||||
|
||||
class SupplierProtocol(WebPacketProtocol):
|
||||
pass
|
||||
|
||||
|
||||
class Req_CreateSupplier(SupplierProtocol):
|
||||
name: str = ""
|
||||
code: Optional[str] = None
|
||||
manager_name: Optional[str] = None
|
||||
manager_email: Optional[str] = None
|
||||
manager_contact_number: Optional[str] = None
|
||||
priority: Optional[str] = None
|
||||
|
||||
|
||||
class Req_UpdateSupplier(SupplierProtocol):
|
||||
name: Optional[str] = None
|
||||
code: Optional[str] = None
|
||||
manager_name: Optional[str] = None
|
||||
manager_email: Optional[str] = None
|
||||
manager_contact_number: Optional[str] = None
|
||||
priority: Optional[str] = None
|
||||
|
||||
|
||||
class SupplierData(WebPacketProtocol):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
supplier_id: uuid.UUID
|
||||
company_id: uuid.UUID
|
||||
user_id: uuid.UUID
|
||||
name: str
|
||||
code: Optional[str] = None
|
||||
manager_name: Optional[str] = None
|
||||
manager_email: Optional[str] = None
|
||||
manager_contact_number: Optional[str] = None
|
||||
priority: Optional[str] = None
|
||||
created_at: Optional[datetime] = None
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
|
||||
class Res_Supplier(Res_WebPacketProtocol):
|
||||
supplier: Optional[SupplierData] = None
|
||||
|
||||
|
||||
class Res_SupplierList(Res_PageProtocol):
|
||||
suppliers: list[SupplierData] = []
|
||||
|
||||
|
||||
class Req_CheckCodes(SupplierProtocol):
|
||||
codes: list[str] = []
|
||||
|
||||
|
||||
class Res_CheckCodes(Res_WebPacketProtocol):
|
||||
existing: list[str] = [] # codes 중 이미 DB(같은 회사)에 존재하는 코드들
|
||||
|
||||
|
||||
class Res_DeleteSupplier(Res_WebPacketProtocol):
|
||||
pass
|
||||
|
||||
|
||||
class Res_ExcelUpload(Res_WebPacketProtocol):
|
||||
received_filename: Optional[str] = None
|
||||
status: str = ""
|
||||
message: str = ""
|
||||
77
negodata/backend/router/v1/supplier/supplier.py
Normal file
77
negodata/backend/router/v1/supplier/supplier.py
Normal file
@ -0,0 +1,77 @@
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, File, Query, UploadFile
|
||||
|
||||
from common.models.gmodel import PageParams, UserInfo
|
||||
from router.v1.validator.dependencies import IsValidAccessToken, RemoveNoneResponse
|
||||
from services.supplier_service import SupplierService
|
||||
from .protocol import (
|
||||
Req_CheckCodes,
|
||||
Req_CreateSupplier,
|
||||
Req_UpdateSupplier,
|
||||
Res_CheckCodes,
|
||||
Res_DeleteSupplier,
|
||||
Res_ExcelUpload,
|
||||
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="협력사명/코드/담당자명 검색"),
|
||||
priority: str | None = Query(None, description="우선순위 필터(HIGH/MEDIUM/LOW)"),
|
||||
pg: PageParams = Depends(),
|
||||
):
|
||||
return RemoveNoneResponse(await service.list_suppliers(user_info.company_id, search, priority, 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.model_dump(exclude_unset=True))
|
||||
)
|
||||
|
||||
|
||||
@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.post(path="/upload-excel", response_model=Res_ExcelUpload, summary="협력사 엑셀 일괄 등록(스텁)")
|
||||
async def upload_suppliers_excel(
|
||||
service: SupplierService = Depends(),
|
||||
user_info: UserInfo = Depends(IsValidAccessToken),
|
||||
file: UploadFile = File(...),
|
||||
):
|
||||
return RemoveNoneResponse(
|
||||
Res_ExcelUpload(received_filename=file.filename, status="not_implemented", message="엑셀 일괄 등록은 추후 구현")
|
||||
)
|
||||
|
||||
|
||||
@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.model_dump(exclude_unset=True))
|
||||
)
|
||||
|
||||
|
||||
@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(IsValidAccessToken)):
|
||||
return RemoveNoneResponse(await service.delete_supplier(user_info.company_id, str(supplier_id)))
|
||||
@ -110,4 +110,6 @@ def RemoveNoneValues(obj: Any) -> Any:
|
||||
|
||||
|
||||
def RemoveNoneResponse(obj) -> ORJSONResponse:
|
||||
return ORJSONResponse(content=RemoveNoneValues(obj.model_dump()))
|
||||
# mode="json": uuid/datetime 등 DB 네이티브 타입(asyncpg.UUID 포함)을 pydantic 단에서
|
||||
# JSON 안전한 문자열로 변환한다. (python 모드면 orjson 이 asyncpg.UUID 를 직렬화 못 함)
|
||||
return ORJSONResponse(content=RemoveNoneValues(obj.model_dump(mode="json")))
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
import uuid
|
||||
|
||||
from fastapi import Depends
|
||||
|
||||
from common.database.db_session_manager import DB_SESSION_MNG
|
||||
from common.database.model.models import tbl_account
|
||||
from common.enums import DBWRType, ErrorType
|
||||
from common.database.model.models import users
|
||||
from common.enums import DBWRType, ErrorType, UserStatus, UserRole, ENUM_LABELS
|
||||
from common.logger import LOG
|
||||
from common.models.gmodel import UserInfo
|
||||
from crud.user_crud import IUserCRUD, UserCRUD
|
||||
from router.v1.auth.protocol import Res_CreateAccount, Res_Login, Res_RefreshToken
|
||||
from router.v1.auth.protocol import CompanyData, Res_CreateAccount, Res_Login, Res_Me, Res_RefreshToken
|
||||
from router.v1.validator.dependencies import (
|
||||
CreateAccessToken,
|
||||
CreateRefreshToken,
|
||||
@ -29,59 +31,68 @@ class AuthService:
|
||||
def __init__(self, user_crud: IUserCRUD = Depends(UserCRUD)):
|
||||
self.user_crud = user_crud
|
||||
|
||||
async def attempt_login(self, id: str, pw: str, connect_ip: str) -> Res_Login:
|
||||
LOG.i(f"LOGIN : {id=}")
|
||||
@staticmethod
|
||||
def _user_info(user: users) -> UserInfo:
|
||||
# uuid → str (JWT json 직렬화 위해). 기능 라우터는 company_id 로 스코프한다.
|
||||
return UserInfo(
|
||||
user_id=str(user.user_id),
|
||||
id=user.id,
|
||||
company_id=str(user.company_id),
|
||||
)
|
||||
|
||||
async def attempt_login(self, login_id: str, password: str, connect_ip: str) -> Res_Login:
|
||||
LOG.i(f"LOGIN : id={login_id}")
|
||||
res = Res_Login()
|
||||
|
||||
# 1) 계정 조회 (Read DB)
|
||||
err_type, account = await DB_SESSION_MNG.execute_lambda(
|
||||
tbl_account.DBType(),
|
||||
err_type, user = await DB_SESSION_MNG.execute_lambda(
|
||||
users.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.user_crud.get_account_by_id(s, id),
|
||||
lambda s: self.user_crud.get_user_by_login_id(s, login_id),
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
# 계정 없음/조회 실패 모두 로그인 실패로 일반화
|
||||
res.result.SetResult(ErrorType.ACCOUNT_INVALID_INFO)
|
||||
return res
|
||||
account: tbl_account
|
||||
user: users
|
||||
|
||||
# 2) 비밀번호 검증
|
||||
if not await VerifyPW(pw, account.pw):
|
||||
if not await VerifyPW(password, user.password):
|
||||
res.result.SetResult(ErrorType.ACCOUNT_INVALID_INFO)
|
||||
return res
|
||||
|
||||
# 3) 차단 여부
|
||||
if account.is_blocked:
|
||||
# 3) 상태 확인 (활성 아니면 차단)
|
||||
if user.status != UserStatus.ACTIVE.value:
|
||||
res.result.SetResult(ErrorType.ACCOUNT_BLOCKED_USER)
|
||||
return res
|
||||
|
||||
# 4) 토큰 발급
|
||||
user_info = UserInfo(uid=account.uid, id=account.id, nickname=account.nickname)
|
||||
user_info = self._user_info(user)
|
||||
res.access_token = CreateAccessToken(user_info)
|
||||
res.refresh_token = CreateRefreshToken(user_info)
|
||||
|
||||
# 5) 마지막 로그인 시간 갱신 (Write DB, 트랜잭션)
|
||||
# 5) 마지막 접속 시간 갱신 (Write DB, 트랜잭션)
|
||||
err_type = await DB_SESSION_MNG.execute_lambda_run(
|
||||
[tbl_account.DBType()],
|
||||
[lambda s: self.user_crud.update_last_login(s, account.uid)],
|
||||
[users.DBType()],
|
||||
[lambda s: self.user_crud.update_last_accessed(s, user.user_id)],
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
|
||||
res.uid = account.uid
|
||||
res.nickname = account.nickname
|
||||
return res
|
||||
|
||||
async def create_account(self, id: str, pw: str, nickname: str, connect_ip: str) -> Res_CreateAccount:
|
||||
LOG.i(f"CREATE : {id=}, {nickname=}")
|
||||
async def create_account(
|
||||
self, login_id: str, password: str, company_id: str, name: str, email: str, contact_number: str, role: int
|
||||
) -> Res_CreateAccount:
|
||||
LOG.i(f"CREATE : id={login_id}, company_id={company_id}")
|
||||
res = Res_CreateAccount()
|
||||
|
||||
# 1) 중복 ID 확인 (Read DB)
|
||||
err_type = await DB_SESSION_MNG.execute_lambda(
|
||||
tbl_account.DBType(),
|
||||
users.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.user_crud.is_account(s, id),
|
||||
lambda s: self.user_crud.is_user(s, login_id),
|
||||
)
|
||||
if err_type == ErrorType.DB_ALREADY_SAME_KEY:
|
||||
res.result.SetResult(ErrorType.ACCOUNT_ALREADY_EXIST)
|
||||
@ -91,10 +102,18 @@ class AuthService:
|
||||
return res
|
||||
|
||||
# 2) 계정 생성 (비밀번호는 bcrypt 해시로 저장)
|
||||
account = tbl_account(id=id, pw=await GetHashedPW(pw), nickname=nickname or id)
|
||||
user = users(
|
||||
company_id=uuid.UUID(company_id),
|
||||
id=login_id,
|
||||
password=await GetHashedPW(password),
|
||||
name=name or None,
|
||||
email=email or None,
|
||||
contact_number=contact_number or None,
|
||||
role=role or UserRole.USER.value,
|
||||
)
|
||||
err_type = await DB_SESSION_MNG.execute_lambda_run(
|
||||
[tbl_account.DBType()],
|
||||
[lambda s: self.user_crud.add_account(s, account)],
|
||||
[users.DBType()],
|
||||
[lambda s: self.user_crud.add_user(s, user)],
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
# 사전 검사와 INSERT 사이의 경쟁 조건에서 unique 위반이 나면 동일 코드로 매핑.
|
||||
@ -104,7 +123,41 @@ class AuthService:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
|
||||
res.uid = account.uid
|
||||
res.user_id = str(user.user_id)
|
||||
return res
|
||||
|
||||
async def get_me(self, user_info: UserInfo) -> Res_Me:
|
||||
res = Res_Me()
|
||||
|
||||
# 1) 유저 조회
|
||||
err_type, user = await DB_SESSION_MNG.execute_lambda(
|
||||
users.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.user_crud.get_user_by_login_id(s, user_info.id),
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(ErrorType.ACCOUNT_INVALID_INFO)
|
||||
return res
|
||||
user: users
|
||||
|
||||
# 2) 소속사 조회 (없어도 치명적 아님)
|
||||
company = None
|
||||
c_err, company_row = await DB_SESSION_MNG.execute_lambda(
|
||||
users.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.user_crud.get_company(s, user.company_id),
|
||||
)
|
||||
if c_err == ErrorType.SUCCESS and company_row is not None:
|
||||
company = CompanyData(company_id=str(company_row.company_id), name=company_row.name)
|
||||
|
||||
res.user_id = str(user.user_id)
|
||||
res.id = user.id
|
||||
res.name = user.name
|
||||
res.email = user.email
|
||||
res.contact_number = user.contact_number
|
||||
res.role = user.role
|
||||
res.role_label = ENUM_LABELS.get(UserRole(user.role), str(user.role))
|
||||
res.company = company
|
||||
return res
|
||||
|
||||
async def refresh_token(self, refresh_token: str) -> Res_RefreshToken:
|
||||
|
||||
204
negodata/backend/services/card_service.py
Normal file
204
negodata/backend/services/card_service.py
Normal file
@ -0,0 +1,204 @@
|
||||
import uuid
|
||||
|
||||
from fastapi import Depends
|
||||
|
||||
from common.database.db_session_manager import DB_SESSION_MNG
|
||||
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
|
||||
|
||||
|
||||
class CardService:
|
||||
"""협상카드 비즈니스 로직. nego_cards/wild_cards 두 테이블을 user_id 로 스코프하고
|
||||
프론트용 단일 모델(CardData, is_wildcard 플래그)로 합친다."""
|
||||
|
||||
def __init__(self, card_crud: ICardCRUD = Depends(CardCRUD)):
|
||||
self.card_crud = card_crud
|
||||
|
||||
# ---- 행 → 통합 CardData --------------------------------------------------
|
||||
@staticmethod
|
||||
def _nego_to_data(row) -> CardData:
|
||||
# 일반 협상카드는 상태 개념이 없다(상시 적용) → ACTIVE 고정.
|
||||
return CardData(
|
||||
nego_card_id=row.nego_card_id,
|
||||
user_id=row.user_id,
|
||||
is_wildcard=False,
|
||||
name=row.name,
|
||||
number=row.number,
|
||||
script=row.script,
|
||||
edit_script=row.edit_script,
|
||||
status=CardStatus.ACTIVE.value,
|
||||
created_at=row.created_at,
|
||||
updated_at=row.updated_at,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _wild_to_data(row) -> CardData:
|
||||
# 와일드카드 적용 여부(available) → status(ACTIVE/INACTIVE) 매핑.
|
||||
return CardData(
|
||||
nego_card_id=row.wild_card_id,
|
||||
user_id=row.user_id,
|
||||
is_wildcard=True,
|
||||
name=row.name,
|
||||
number=row.number,
|
||||
script=row.script,
|
||||
edit_script=row.edit_script,
|
||||
status=CardStatus.ACTIVE.value if row.available else CardStatus.INACTIVE.value,
|
||||
condition=row.condition,
|
||||
memo=row.memo,
|
||||
created_at=row.created_at,
|
||||
updated_at=row.updated_at,
|
||||
)
|
||||
|
||||
# ---- 소유 카드 탐색(어느 테이블인지 모를 때) ------------------------------
|
||||
async def _find_owned(self, user_uuid: uuid.UUID, card_id: uuid.UUID):
|
||||
"""card_id 를 nego_cards → wild_cards 순으로 찾고 소유권 확인.
|
||||
(ErrorType, model, pk_col, row, is_wildcard) 반환."""
|
||||
err, row = await DB_SESSION_MNG.execute_lambda(
|
||||
nego_cards.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.card_crud.get_by_id(s, nego_cards, nego_cards.nego_card_id, card_id),
|
||||
)
|
||||
if err == ErrorType.SUCCESS and row is not None:
|
||||
if row.user_id != user_uuid:
|
||||
return ErrorType.CARD_NOT_FOUND, None, None, None, False
|
||||
return ErrorType.SUCCESS, nego_cards, nego_cards.nego_card_id, row, False
|
||||
|
||||
err, row = await DB_SESSION_MNG.execute_lambda(
|
||||
wild_cards.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.card_crud.get_by_id(s, wild_cards, wild_cards.wild_card_id, card_id),
|
||||
)
|
||||
if err == ErrorType.SUCCESS and row is not None:
|
||||
if row.user_id != user_uuid:
|
||||
return ErrorType.CARD_NOT_FOUND, None, None, None, True
|
||||
return ErrorType.SUCCESS, wild_cards, wild_cards.wild_card_id, row, True
|
||||
|
||||
return ErrorType.CARD_NOT_FOUND, None, None, None, False
|
||||
|
||||
# ---- 목록 ----------------------------------------------------------------
|
||||
async def list_cards(self, user_id: str, search, pg: PageParams) -> Res_CardList:
|
||||
res = Res_CardList(page=pg.page, size=pg.size)
|
||||
if not user_id:
|
||||
return res
|
||||
user_uuid = uuid.UUID(user_id)
|
||||
# 합쳐서 정렬/페이징하므로 각 테이블에서 skip+limit 까지 받아온다(카드 수가 적어 충분).
|
||||
fetch = pg.skip + pg.size
|
||||
|
||||
err_n, nego_rows, total_n = await DB_SESSION_MNG.execute_lambda(
|
||||
nego_cards.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.card_crud.search(s, nego_cards, user_uuid, search, 0, fetch),
|
||||
)
|
||||
if err_n != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_n)
|
||||
return res
|
||||
|
||||
err_w, wild_rows, total_w = await DB_SESSION_MNG.execute_lambda(
|
||||
wild_cards.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.card_crud.search(s, wild_cards, user_uuid, search, 0, fetch),
|
||||
)
|
||||
if err_w != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_w)
|
||||
return res
|
||||
|
||||
merged = [self._nego_to_data(r) for r in nego_rows] + [self._wild_to_data(r) for r in wild_rows]
|
||||
merged.sort(key=lambda c: c.created_at or "", reverse=True)
|
||||
res.cards = merged[pg.skip : pg.skip + pg.size]
|
||||
res.total = total_n + total_w
|
||||
return res
|
||||
|
||||
# ---- 단건 조회 -----------------------------------------------------------
|
||||
async def get_card(self, user_id: str, card_id: str) -> Res_Card:
|
||||
res = Res_Card()
|
||||
err, _model, _pk, row, is_wild = await self._find_owned(uuid.UUID(user_id), uuid.UUID(card_id))
|
||||
if err != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err)
|
||||
return res
|
||||
res.card = self._wild_to_data(row) if is_wild else self._nego_to_data(row)
|
||||
return res
|
||||
|
||||
# ---- 등록 ----------------------------------------------------------------
|
||||
async def create_card(self, user_id: str, data: dict) -> Res_Card:
|
||||
res = Res_Card()
|
||||
user_uuid = uuid.UUID(user_id)
|
||||
is_wildcard = bool(data.get("is_wildcard", False))
|
||||
|
||||
common = dict(
|
||||
user_id=user_uuid,
|
||||
name=data.get("name"),
|
||||
number=data.get("number"),
|
||||
script=data.get("script"),
|
||||
edit_script=data.get("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"),
|
||||
)
|
||||
model, pk_attr = wild_cards, "wild_card_id"
|
||||
else:
|
||||
card = nego_cards(**common)
|
||||
model, pk_attr = nego_cards, "nego_card_id"
|
||||
|
||||
err = await DB_SESSION_MNG.execute_lambda_run(
|
||||
[model.DBType()],
|
||||
[lambda s: self.card_crud.add(s, card)],
|
||||
)
|
||||
if err != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err)
|
||||
return res
|
||||
# 서버 기본값(created_at 등)은 insert 후 객체에 실리지 않으므로 재조회.
|
||||
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:
|
||||
res = Res_Card()
|
||||
user_uuid = uuid.UUID(user_id)
|
||||
card_uuid = uuid.UUID(card_id)
|
||||
|
||||
err, model, pk_col, _row, is_wild = await self._find_owned(user_uuid, card_uuid)
|
||||
if err != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err)
|
||||
return res
|
||||
|
||||
# 해당 테이블에 있는 컬럼만 추린다(없는 필드는 무시). status → available(와일드 전용).
|
||||
allowed = {"name", "number", "script", "edit_script"}
|
||||
if is_wild:
|
||||
allowed |= {"condition", "memo"}
|
||||
payload = {k: v for k, v in data.items() if k in allowed}
|
||||
if is_wild and "status" in data and data["status"] is not None:
|
||||
payload["available"] = data["status"] == CardStatus.ACTIVE.value
|
||||
|
||||
err = await DB_SESSION_MNG.execute_lambda_run(
|
||||
[model.DBType()],
|
||||
[lambda s: self.card_crud.update(s, model, pk_col, card_uuid, payload)],
|
||||
)
|
||||
if err != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err)
|
||||
return res
|
||||
return await self.get_card(user_id, card_id)
|
||||
|
||||
# ---- 삭제(soft) ----------------------------------------------------------
|
||||
async def delete_card(self, user_id: str, card_id: str) -> Res_DeleteCard:
|
||||
res = Res_DeleteCard()
|
||||
user_uuid = uuid.UUID(user_id)
|
||||
card_uuid = uuid.UUID(card_id)
|
||||
|
||||
err, model, pk_col, _row, _is_wild = await self._find_owned(user_uuid, card_uuid)
|
||||
if err != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err)
|
||||
return res
|
||||
|
||||
err = await DB_SESSION_MNG.execute_lambda_run(
|
||||
[model.DBType()],
|
||||
[lambda s: self.card_crud.soft_delete(s, model, pk_col, card_uuid)],
|
||||
)
|
||||
if err != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err)
|
||||
return res
|
||||
164
negodata/backend/services/item_service.py
Normal file
164
negodata/backend/services/item_service.py
Normal file
@ -0,0 +1,164 @@
|
||||
import uuid
|
||||
|
||||
from fastapi import Depends
|
||||
|
||||
from common.database.db_session_manager import DB_SESSION_MNG
|
||||
from common.database.model.models import items
|
||||
from common.enums import DBWRType, ErrorType
|
||||
from common.logger import LOG
|
||||
from common.models.gmodel import PageParams
|
||||
from crud.item_crud import IItemCRUD, ItemCRUD
|
||||
from router.v1.item.protocol import (
|
||||
ItemCategory,
|
||||
ItemData,
|
||||
Res_CheckCodes,
|
||||
Res_DeleteItem,
|
||||
Res_Item,
|
||||
Res_ItemCategories,
|
||||
Res_ItemList,
|
||||
)
|
||||
|
||||
|
||||
class ItemService:
|
||||
"""상품 비즈니스 로직. company_id 로 소유권을 확인한다(멀티테넌트)."""
|
||||
|
||||
def __init__(self, item_crud: IItemCRUD = Depends(ItemCRUD)):
|
||||
self.item_crud = item_crud
|
||||
|
||||
async def _fetch_owned(self, company_uuid: uuid.UUID, item_id: uuid.UUID):
|
||||
"""item 조회 + 소유권 확인. (ErrorType, item|None) 반환."""
|
||||
err_type, item = await DB_SESSION_MNG.execute_lambda(
|
||||
items.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.item_crud.get_by_id(s, item_id),
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS or item is None:
|
||||
return ErrorType.ITEM_NOT_FOUND, None
|
||||
if item.company_id != company_uuid:
|
||||
return ErrorType.ITEM_NOT_FOUND, None
|
||||
return ErrorType.SUCCESS, item
|
||||
|
||||
async def list_items(self, company_id: str, search, category, pg: PageParams) -> Res_ItemList:
|
||||
res = Res_ItemList(page=pg.page, size=pg.size)
|
||||
company_uuid = uuid.UUID(company_id)
|
||||
|
||||
err_type, rows, total = await DB_SESSION_MNG.execute_lambda(
|
||||
items.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.item_crud.search(s, company_uuid, search, category, pg.skip, pg.size),
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
res.items = [ItemData.model_validate(r) for r in rows]
|
||||
res.total = total
|
||||
return res
|
||||
|
||||
async def list_categories(self, company_id: str) -> Res_ItemCategories:
|
||||
"""회사 전체 상품에서 distinct 카테고리(이름+타입)를 돌려준다. 카테고리 목록은 백엔드가 책임진다."""
|
||||
res = Res_ItemCategories()
|
||||
company_uuid = uuid.UUID(company_id)
|
||||
err_type, rows = await DB_SESSION_MNG.execute_lambda(
|
||||
items.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.item_crud.distinct_categories(s, company_uuid),
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
res.categories = [ItemCategory(name=r[0], category_type=r[1] if r[1] is not None else 1) for r in rows]
|
||||
return res
|
||||
|
||||
async def get_item(self, company_id: str, item_id: str) -> Res_Item:
|
||||
res = Res_Item()
|
||||
err_type, item = await self._fetch_owned(uuid.UUID(company_id), uuid.UUID(item_id))
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
res.item = ItemData.model_validate(item)
|
||||
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(
|
||||
items.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.item_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_item(self, company_id: str, user_id: str, data: dict) -> Res_Item:
|
||||
res = Res_Item()
|
||||
company_uuid = uuid.UUID(company_id)
|
||||
|
||||
# DB 중복코드 검증: 같은 회사에 동일 code 가 이미 있으면 거부(프론트는 받아온 목록만 보므로 여기서 최종 차단).
|
||||
code = data.get("code")
|
||||
if code:
|
||||
dup_err, exists = await DB_SESSION_MNG.execute_lambda(
|
||||
items.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.item_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.ITEM_CODE_DUPLICATE)
|
||||
return res
|
||||
|
||||
item = items(**data, 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)],
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
# 서버 기본값(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:
|
||||
res = Res_Item()
|
||||
company_uuid = uuid.UUID(company_id)
|
||||
item_uuid = uuid.UUID(item_id)
|
||||
|
||||
# 소유권 확인
|
||||
err_type, _ = await self._fetch_owned(company_uuid, item_uuid)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
|
||||
err_type = await DB_SESSION_MNG.execute_lambda_run(
|
||||
[items.DBType()],
|
||||
[lambda s: self.item_crud.update_item(s, item_uuid, data)],
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
|
||||
# 갱신 후 재조회
|
||||
return await self.get_item(company_id, item_id)
|
||||
|
||||
async def delete_item(self, company_id: str, item_id: str) -> Res_DeleteItem:
|
||||
res = Res_DeleteItem()
|
||||
company_uuid = uuid.UUID(company_id)
|
||||
item_uuid = uuid.UUID(item_id)
|
||||
|
||||
err_type, _ = await self._fetch_owned(company_uuid, item_uuid)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
|
||||
err_type = await DB_SESSION_MNG.execute_lambda_run(
|
||||
[items.DBType()],
|
||||
[lambda s: self.item_crud.soft_delete(s, item_uuid)],
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
266
negodata/backend/services/quotation_service.py
Normal file
266
negodata/backend/services/quotation_service.py
Normal file
@ -0,0 +1,266 @@
|
||||
import uuid
|
||||
|
||||
from fastapi import Depends
|
||||
|
||||
from common.database.db_session_manager import DB_SESSION_MNG
|
||||
from common.database.model.models import quotations, sessions, chats
|
||||
from common.enums import DBWRType, ErrorType
|
||||
from common.models.gmodel import PageParams
|
||||
from crud.quotation_crud import IQuotationCRUD, QuotationCRUD
|
||||
from router.v1.quotation.protocol import (
|
||||
AsyncJob,
|
||||
ChatMessageData,
|
||||
QuotationCardData,
|
||||
QuotationData,
|
||||
SessionData,
|
||||
Res_CreateQuotation,
|
||||
Res_DeleteQuotation,
|
||||
Res_Quotation,
|
||||
Res_QuotationCards,
|
||||
Res_QuotationList,
|
||||
Res_QuotationResult,
|
||||
Res_QuotationSessions,
|
||||
Res_QuotationStatus,
|
||||
Res_SessionChat,
|
||||
)
|
||||
|
||||
|
||||
class QuotationService:
|
||||
"""견적 비즈니스 로직.
|
||||
|
||||
quotations 테이블에는 company_id 가 없어 회사 스코핑은 하지 않는다(토큰 검증만).
|
||||
user_id 는 생성 시 소유자로만 기록한다(조회/변경 시 소유권 필터 없음).
|
||||
"""
|
||||
|
||||
def __init__(self, quotation_crud: IQuotationCRUD = Depends(QuotationCRUD)):
|
||||
self.quotation_crud = quotation_crud
|
||||
|
||||
async def _fetch(self, qt_id: uuid.UUID):
|
||||
"""견적 단건 조회. (ErrorType, quotation|None) 반환. (회사 스코프 없음)"""
|
||||
err_type, quotation = await DB_SESSION_MNG.execute_lambda(
|
||||
quotations.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.quotation_crud.get_by_id(s, qt_id),
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS or quotation is None:
|
||||
return ErrorType.QUOTATION_NOT_FOUND, None
|
||||
return ErrorType.SUCCESS, quotation
|
||||
|
||||
async def list_quotations(self, status, type_, start_from, start_to, pg: PageParams) -> Res_QuotationList:
|
||||
res = Res_QuotationList(page=pg.page, size=pg.size)
|
||||
|
||||
err_type, rows, total = await DB_SESSION_MNG.execute_lambda(
|
||||
quotations.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.quotation_crud.search(s, status, type_, start_from, start_to, pg.skip, pg.size),
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
res.quotations = [QuotationData.model_validate(r) for r in rows]
|
||||
res.total = total
|
||||
return res
|
||||
|
||||
async def get_quotation(self, qt_id: str) -> Res_Quotation:
|
||||
res = Res_Quotation()
|
||||
err_type, quotation = await self._fetch(uuid.UUID(qt_id))
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
res.quotation = QuotationData.model_validate(quotation)
|
||||
return res
|
||||
|
||||
async def create_quotation(self, user_id: str, data: dict) -> Res_CreateQuotation:
|
||||
res = Res_CreateQuotation()
|
||||
quotation = quotations(**data, user_id=uuid.UUID(user_id))
|
||||
err_type = await DB_SESSION_MNG.execute_lambda_run(
|
||||
[quotations.DBType()],
|
||||
[lambda s: self.quotation_crud.add_quotation(s, quotation)],
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
# 서버 기본값(created_at/updated_at) 로드 위해 재조회 (응답 shape 은 Res_CreateQuotation 유지).
|
||||
f_err, fresh = await DB_SESSION_MNG.execute_lambda(
|
||||
quotations.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.quotation_crud.get_by_id(s, quotation.qt_id),
|
||||
)
|
||||
res.quotation = QuotationData.model_validate(fresh if f_err == ErrorType.SUCCESS and fresh is not None else quotation)
|
||||
# 네고시움 백엔드 비동기 요청은 스텁이므로 row 만 생성한다.
|
||||
res.async_job = AsyncJob(status="submitted", message="견적 생성 작업 요청됨(스텁)")
|
||||
return res
|
||||
|
||||
async def stop_quotation(self, qt_id: str) -> Res_Quotation:
|
||||
res = Res_Quotation()
|
||||
qt_uuid = uuid.UUID(qt_id)
|
||||
|
||||
# 존재 확인
|
||||
err_type, _ = await self._fetch(qt_uuid)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
|
||||
# 상태를 '견적마감'으로 변경(실제 DB 업데이트)
|
||||
err_type = await DB_SESSION_MNG.execute_lambda_run(
|
||||
[quotations.DBType()],
|
||||
[lambda s: self.quotation_crud.update_quotation(s, qt_uuid, {"status": "견적마감"})],
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
|
||||
# 갱신 후 재조회
|
||||
return await self.get_quotation(qt_id)
|
||||
|
||||
async def delete_quotation(self, qt_id: str) -> Res_DeleteQuotation:
|
||||
res = Res_DeleteQuotation()
|
||||
qt_uuid = uuid.UUID(qt_id)
|
||||
|
||||
err_type, _ = await self._fetch(qt_uuid)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
|
||||
err_type = await DB_SESSION_MNG.execute_lambda_run(
|
||||
[quotations.DBType()],
|
||||
[lambda s: self.quotation_crud.soft_delete(s, qt_uuid)],
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
|
||||
async def get_status(self, qt_id: str) -> Res_QuotationStatus:
|
||||
res = Res_QuotationStatus()
|
||||
err_type, quotation = await self._fetch(uuid.UUID(qt_id))
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
res.qt_id = quotation.qt_id
|
||||
res.job_status = quotation.status
|
||||
res.message = "ok"
|
||||
return res
|
||||
|
||||
async def get_result(self, qt_id: str) -> Res_QuotationResult:
|
||||
res = Res_QuotationResult()
|
||||
err_type, quotation = await self._fetch(uuid.UUID(qt_id))
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
# 낙찰 결과는 quotations 컬럼에서 직접 노출. results 테이블 미존재로 result_count 는 0.
|
||||
res.qt_id = quotation.qt_id
|
||||
res.winner_supplier_id = quotation.preferred_sp_id
|
||||
res.winner_supplier_name = quotation.preferred_sp_name
|
||||
res.is_equal_bid = quotation.equal_bid_yn
|
||||
res.equal_bid_data = quotation.equal_bid_data
|
||||
res.result_count = 0
|
||||
return res
|
||||
|
||||
async def list_sessions(self, qt_id: str) -> Res_QuotationSessions:
|
||||
res = Res_QuotationSessions()
|
||||
qt_uuid = uuid.UUID(qt_id)
|
||||
err_type, quotation = await self._fetch(qt_uuid)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
|
||||
err_type, rows = await DB_SESSION_MNG.execute_lambda(
|
||||
sessions.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.quotation_crud.list_sessions(s, qt_uuid),
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
|
||||
res.qt_id = quotation.qt_id
|
||||
# sessions.quotation_id → SessionData.qt_id 로 명시 매핑(컬럼명 불일치).
|
||||
res.sessions = [
|
||||
SessionData(
|
||||
session_id=r.session_id,
|
||||
qt_id=r.quotation_id,
|
||||
supplier_id=r.supplier_id,
|
||||
item_id=r.item_id,
|
||||
qt_number=r.qt_number,
|
||||
qt_round=r.qt_round,
|
||||
qt_type=r.qt_type,
|
||||
target_price=r.target_price,
|
||||
status=r.status,
|
||||
bid_price=r.bid_price,
|
||||
bid_at=r.bid_at,
|
||||
end_time=r.end_time,
|
||||
reject_reason=r.reject_reason,
|
||||
reject_price=r.reject_price,
|
||||
reject_delivery_type=r.reject_delivery_type,
|
||||
)
|
||||
for r in rows
|
||||
]
|
||||
res.total = len(res.sessions)
|
||||
return res
|
||||
|
||||
async def list_chats(self, session_id: str) -> Res_SessionChat:
|
||||
res = Res_SessionChat()
|
||||
sess_uuid = uuid.UUID(session_id)
|
||||
res.session_id = sess_uuid
|
||||
|
||||
err_type, rows = await DB_SESSION_MNG.execute_lambda(
|
||||
chats.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.quotation_crud.list_chats(s, sess_uuid),
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
|
||||
# chats.seq → ChatMessageData.index 로 매핑. indicator_value(Decimal) → float.
|
||||
res.messages = [
|
||||
ChatMessageData(
|
||||
chat_id=r.chat_id,
|
||||
session_id=r.session_id,
|
||||
card_id=r.card_id,
|
||||
index=r.seq,
|
||||
sender=r.sender,
|
||||
target_price=r.target_price,
|
||||
card_used_yn=r.card_used_yn,
|
||||
indicator_value=float(r.indicator_value) if r.indicator_value is not None else None,
|
||||
card_type=r.card_type,
|
||||
)
|
||||
for r in rows
|
||||
]
|
||||
return res
|
||||
|
||||
async def list_cards(self, qt_id: str) -> Res_QuotationCards:
|
||||
res = Res_QuotationCards()
|
||||
qt_uuid = uuid.UUID(qt_id)
|
||||
err_type, quotation = await self._fetch(qt_uuid)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
|
||||
err_type, rows = await DB_SESSION_MNG.execute_lambda(
|
||||
chats.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.quotation_crud.list_used_cards(s, qt_uuid),
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
|
||||
res.qt_id = quotation.qt_id
|
||||
# rows = [(chat_row, nego_card_id, name, script), ...]. nego/wild 구분은 chats.card_type.
|
||||
cards = []
|
||||
for chat_row, nc_id, nc_name, nc_script in rows:
|
||||
is_wild = chat_row.card_type == 2
|
||||
cards.append(
|
||||
QuotationCardData(
|
||||
session_card_id=chat_row.chat_id,
|
||||
qt_id=quotation.qt_id,
|
||||
nego_card_id=None if is_wild else nc_id,
|
||||
wild_card_id=nc_id if is_wild else None,
|
||||
type=chat_row.card_type if chat_row.card_type is not None else 1,
|
||||
name=nc_name,
|
||||
script=nc_script,
|
||||
)
|
||||
)
|
||||
res.cards = cards
|
||||
return res
|
||||
112
negodata/backend/services/quotation_setting_service.py
Normal file
112
negodata/backend/services/quotation_setting_service.py
Normal file
@ -0,0 +1,112 @@
|
||||
import uuid
|
||||
|
||||
from fastapi import Depends
|
||||
|
||||
from common.database.db_session_manager import DB_SESSION_MNG
|
||||
from common.database.model.models import quotation_settings
|
||||
from common.enums import DBWRType, ErrorType
|
||||
from crud.quotation_setting_crud import IQuotationSettingCRUD, QuotationSettingCRUD
|
||||
from router.v1.quotation_setting.protocol import (
|
||||
QuotationSettingData,
|
||||
Res_DeleteQuotationSetting,
|
||||
Res_QuotationSetting,
|
||||
Res_QuotationSettingList,
|
||||
)
|
||||
|
||||
|
||||
class QuotationSettingService:
|
||||
"""견적 설정 비즈니스 로직. user_id 로 소유권을 확인한다(유저별 설정)."""
|
||||
|
||||
def __init__(self, qs_crud: IQuotationSettingCRUD = Depends(QuotationSettingCRUD)):
|
||||
self.qs_crud = qs_crud
|
||||
|
||||
async def _fetch_owned(self, user_uuid: uuid.UUID, qt_setting_id: uuid.UUID):
|
||||
"""설정 조회 + 소유권 확인. (ErrorType, setting|None) 반환."""
|
||||
err_type, setting = await DB_SESSION_MNG.execute_lambda(
|
||||
quotation_settings.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.qs_crud.get_by_id(s, qt_setting_id),
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS or setting is None:
|
||||
return ErrorType.QUOTATION_SETTING_NOT_FOUND, None
|
||||
if setting.user_id != user_uuid:
|
||||
return ErrorType.QUOTATION_SETTING_NOT_FOUND, None
|
||||
return ErrorType.SUCCESS, setting
|
||||
|
||||
async def list_settings(self, user_id: str) -> Res_QuotationSettingList:
|
||||
res = Res_QuotationSettingList()
|
||||
user_uuid = uuid.UUID(user_id)
|
||||
|
||||
err_type, rows, total = await DB_SESSION_MNG.execute_lambda(
|
||||
quotation_settings.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.qs_crud.list_by_user(s, user_uuid),
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
res.settings = [QuotationSettingData.model_validate(r) for r in rows]
|
||||
res.total = total
|
||||
return res
|
||||
|
||||
async def get_setting(self, user_id: str, qt_setting_id: str) -> Res_QuotationSetting:
|
||||
res = Res_QuotationSetting()
|
||||
err_type, setting = await self._fetch_owned(uuid.UUID(user_id), uuid.UUID(qt_setting_id))
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
res.setting = QuotationSettingData.model_validate(setting)
|
||||
return res
|
||||
|
||||
async def create_setting(self, user_id: str, data: dict) -> Res_QuotationSetting:
|
||||
res = Res_QuotationSetting()
|
||||
setting = quotation_settings(**data, user_id=uuid.UUID(user_id))
|
||||
err_type = await DB_SESSION_MNG.execute_lambda_run(
|
||||
[quotation_settings.DBType()],
|
||||
[lambda s: self.qs_crud.add_setting(s, setting)],
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
# 서버 기본값(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:
|
||||
res = Res_QuotationSetting()
|
||||
user_uuid = uuid.UUID(user_id)
|
||||
setting_uuid = uuid.UUID(qt_setting_id)
|
||||
|
||||
# 소유권 확인
|
||||
err_type, _ = await self._fetch_owned(user_uuid, setting_uuid)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
|
||||
err_type = await DB_SESSION_MNG.execute_lambda_run(
|
||||
[quotation_settings.DBType()],
|
||||
[lambda s: self.qs_crud.update_setting(s, setting_uuid, data)],
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
|
||||
# 갱신 후 재조회
|
||||
return await self.get_setting(user_id, qt_setting_id)
|
||||
|
||||
async def delete_setting(self, user_id: str, qt_setting_id: str) -> Res_DeleteQuotationSetting:
|
||||
res = Res_DeleteQuotationSetting()
|
||||
user_uuid = uuid.UUID(user_id)
|
||||
setting_uuid = uuid.UUID(qt_setting_id)
|
||||
|
||||
err_type, _ = await self._fetch_owned(user_uuid, setting_uuid)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
|
||||
err_type = await DB_SESSION_MNG.execute_lambda_run(
|
||||
[quotation_settings.DBType()],
|
||||
[lambda s: self.qs_crud.soft_delete(s, setting_uuid)],
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
141
negodata/backend/services/supplier_service.py
Normal file
141
negodata/backend/services/supplier_service.py
Normal file
@ -0,0 +1,141 @@
|
||||
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 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, data: dict) -> Res_Supplier:
|
||||
res = Res_Supplier()
|
||||
company_uuid = uuid.UUID(company_id)
|
||||
|
||||
# DB 중복코드 검증: 같은 회사에 동일 code 가 이미 있으면 거부(프론트는 받아온 목록만 보므로 여기서 최종 차단).
|
||||
code = data.get("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(**data, company_id=company_uuid, user_id=uuid.UUID(user_id))
|
||||
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, data: dict) -> Res_Supplier:
|
||||
res = Res_Supplier()
|
||||
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.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
|
||||
@ -1,39 +1,48 @@
|
||||
"""auth 도메인 e2e 테스트.
|
||||
"""auth 도메인 e2e 테스트 (negodata: users/companies 기반).
|
||||
|
||||
실행 전제: docker-compose 로 PostgreSQL 이 떠 있어야 한다 (negodata_db 사용).
|
||||
실행 전제: PostgreSQL(negodata_db)이 떠 있어야 한다.
|
||||
docker compose up -d # 또는 로컬 postgres
|
||||
cd negodata/backend && python -m pytest
|
||||
계정 생성은 company_id 를 요구하므로 company_id 픽스처(conftest)가 소속사를 시드한다.
|
||||
"""
|
||||
|
||||
|
||||
async def test_create_and_login_flow(client):
|
||||
# 1) 계정 생성
|
||||
r = await client.post("/v1/auth/create", json={"id": "user1", "pw": "pw1234", "nickname": "닉네임"})
|
||||
async def test_create_and_login_flow(client, company_id):
|
||||
# 1) 계정 생성 (회사 하위로)
|
||||
r = await client.post(
|
||||
"/v1/auth/create",
|
||||
json={"id": "user1", "password": "pw1234", "company_id": company_id, "name": "홍길동"},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert body["result"]["success"] is True
|
||||
assert body["uid"] > 0
|
||||
assert body["user_id"]
|
||||
|
||||
# 2) 로그인 -> 토큰 발급
|
||||
r = await client.post("/v1/auth/login", json={"id": "user1", "pw": "pw1234"})
|
||||
r = await client.post("/v1/auth/login", json={"id": "user1", "password": "pw1234"})
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert body["result"]["success"] is True
|
||||
assert body["access_token"]
|
||||
assert body["refresh_token"]
|
||||
assert body["nickname"] == "닉네임"
|
||||
access_token = body["access_token"]
|
||||
|
||||
# 3) 보호된 엔드포인트 호출
|
||||
# 3) 보호된 엔드포인트(/me) — 토큰의 유저 + 소속사 반환
|
||||
r = await client.get("/v1/auth/me", headers={"Authorization": f"Bearer {access_token}"})
|
||||
assert r.status_code == 200
|
||||
assert r.json()["id"] == "user1"
|
||||
me = r.json()
|
||||
assert me["id"] == "user1"
|
||||
assert me["name"] == "홍길동"
|
||||
assert me["company"]["company_id"] == company_id
|
||||
|
||||
|
||||
async def test_login_with_wrong_password(client):
|
||||
await client.post("/v1/auth/create", json={"id": "user2", "pw": "correct", "nickname": "n"})
|
||||
async def test_login_with_wrong_password(client, company_id):
|
||||
await client.post(
|
||||
"/v1/auth/create",
|
||||
json={"id": "user2", "password": "correct", "company_id": company_id, "name": "n"},
|
||||
)
|
||||
|
||||
r = await client.post("/v1/auth/login", json={"id": "user2", "pw": "wrong"})
|
||||
r = await client.post("/v1/auth/login", json={"id": "user2", "password": "wrong"})
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert body["result"]["success"] is False
|
||||
@ -43,15 +52,21 @@ async def test_login_with_wrong_password(client):
|
||||
|
||||
|
||||
async def test_login_nonexistent_account(client):
|
||||
r = await client.post("/v1/auth/login", json={"id": "ghost", "pw": "whatever"})
|
||||
r = await client.post("/v1/auth/login", json={"id": "ghost", "password": "whatever"})
|
||||
assert r.json()["result"]["success"] is False
|
||||
|
||||
|
||||
async def test_duplicate_account_create(client):
|
||||
r1 = await client.post("/v1/auth/create", json={"id": "dup", "pw": "pw1234", "nickname": "n"})
|
||||
async def test_duplicate_account_create(client, company_id):
|
||||
r1 = await client.post(
|
||||
"/v1/auth/create",
|
||||
json={"id": "dup", "password": "pw1234", "company_id": company_id, "name": "n"},
|
||||
)
|
||||
assert r1.json()["result"]["success"] is True
|
||||
|
||||
r2 = await client.post("/v1/auth/create", json={"id": "dup", "pw": "pw5678", "nickname": "n2"})
|
||||
r2 = await client.post(
|
||||
"/v1/auth/create",
|
||||
json={"id": "dup", "password": "pw5678", "company_id": company_id, "name": "n2"},
|
||||
)
|
||||
body = r2.json()
|
||||
assert body["result"]["success"] is False
|
||||
# ACCOUNT_ALREADY_EXIST(1201)
|
||||
|
||||
68
negodata/backend/tests/test_features.py
Normal file
68
negodata/backend/tests/test_features.py
Normal file
@ -0,0 +1,68 @@
|
||||
"""supplier / quotation_setting / quotation 슬라이스 런타임 스모크.
|
||||
|
||||
create(재조회로 created_at 적재) + list + get 경로를 라이브 DB 로 확인한다.
|
||||
"""
|
||||
import uuid
|
||||
|
||||
|
||||
async def _headers(client, company_id, login_id):
|
||||
await client.post(
|
||||
"/v1/auth/create",
|
||||
json={"id": login_id, "password": "pw1234", "company_id": company_id, "name": "n"},
|
||||
)
|
||||
r = await client.post("/v1/auth/login", json={"id": login_id, "password": "pw1234"})
|
||||
return {"Authorization": f"Bearer {r.json()['access_token']}"}
|
||||
|
||||
|
||||
async def test_supplier_crud(client, company_id):
|
||||
h = await _headers(client, company_id, "supuser")
|
||||
r = await client.post("/v1/supplier/create", json={"name": "공급사A", "code": "S1"}, headers=h)
|
||||
body = r.json()
|
||||
assert body["result"]["success"] is True
|
||||
sup = body["supplier"]
|
||||
assert sup["name"] == "공급사A"
|
||||
assert sup["created_at"] # 재조회 픽스: 서버 기본값 적재 확인
|
||||
sid = sup["supplier_id"]
|
||||
|
||||
r = await client.get("/v1/supplier/list", headers=h)
|
||||
assert r.json()["total"] == 1
|
||||
|
||||
r = await client.get(f"/v1/supplier/{sid}", headers=h)
|
||||
assert r.json()["supplier"]["supplier_id"] == sid
|
||||
|
||||
|
||||
async def test_quotation_setting_crud(client, company_id):
|
||||
h = await _headers(client, company_id, "qsuser")
|
||||
r = await client.post("/v1/quotation-setting/create", json={"target_margin_rate": 0.15}, headers=h)
|
||||
body = r.json()
|
||||
assert body["result"]["success"] is True
|
||||
st = body["setting"]
|
||||
assert st["target_margin_rate"] == 0.15
|
||||
assert st["card_count"] == 3 # 기본값
|
||||
assert st["created_at"]
|
||||
|
||||
r = await client.get("/v1/quotation-setting/list", headers=h)
|
||||
assert r.json()["total"] >= 1
|
||||
|
||||
|
||||
async def test_quotation_create(client, company_id):
|
||||
h = await _headers(client, company_id, "qtuser")
|
||||
body = {
|
||||
"qt_setting_id": str(uuid.uuid4()),
|
||||
"version_id": str(uuid.uuid4()),
|
||||
"name": "견적A",
|
||||
"number": "Q-001",
|
||||
"type": "재견적",
|
||||
"status": "진행중",
|
||||
"start_time": "2026-06-16T00:00:00",
|
||||
"end_time": "2026-06-17T00:00:00",
|
||||
}
|
||||
r = await client.post("/v1/quotation/create", json=body, headers=h)
|
||||
res = r.json()
|
||||
assert res["result"]["success"] is True
|
||||
q = res["quotation"]
|
||||
assert q["name"] == "견적A"
|
||||
assert q["created_at"] # 재조회 픽스
|
||||
|
||||
r = await client.get("/v1/quotation/list", headers=h)
|
||||
assert r.json()["total"] >= 1
|
||||
85
negodata/backend/tests/test_item.py
Normal file
85
negodata/backend/tests/test_item.py
Normal file
@ -0,0 +1,85 @@
|
||||
"""item 도메인 e2e — CRUD + company 멀티테넌트 스코프 검증.
|
||||
|
||||
실행 전제: PostgreSQL(negodata_db). docker compose up -d 후 python -m pytest.
|
||||
"""
|
||||
import uuid
|
||||
|
||||
import pytest_asyncio
|
||||
from sqlalchemy import text
|
||||
|
||||
|
||||
async def _headers(client, company_id, login_id="itemuser", pw="pw1234"):
|
||||
await client.post(
|
||||
"/v1/auth/create",
|
||||
json={"id": login_id, "password": pw, "company_id": company_id, "name": "n"},
|
||||
)
|
||||
r = await client.post("/v1/auth/login", json={"id": login_id, "password": pw})
|
||||
return {"Authorization": f"Bearer {r.json()['access_token']}"}
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def other_company_id(db_engine) -> str:
|
||||
cid = uuid.uuid4()
|
||||
async with db_engine.begin() as conn:
|
||||
await conn.execute(
|
||||
text("INSERT INTO companies (company_id, name) VALUES (:cid, :name)"),
|
||||
{"cid": cid, "name": "다른회사"},
|
||||
)
|
||||
return str(cid)
|
||||
|
||||
|
||||
async def test_item_crud_flow(client, company_id):
|
||||
h = await _headers(client, company_id)
|
||||
|
||||
# 등록
|
||||
r = await client.post("/v1/item/create", json={"name": "상품A", "price": 1000, "code": "C1"}, headers=h)
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert body["result"]["success"] is True
|
||||
item_id = body["item"]["item_id"]
|
||||
assert body["item"]["name"] == "상품A"
|
||||
|
||||
# 목록
|
||||
r = await client.get("/v1/item/list", headers=h)
|
||||
body = r.json()
|
||||
assert body["total"] == 1 and len(body["items"]) == 1
|
||||
|
||||
# 단건 조회
|
||||
r = await client.get(f"/v1/item/{item_id}", headers=h)
|
||||
assert r.json()["item"]["item_id"] == item_id
|
||||
|
||||
# 수정 (부분)
|
||||
r = await client.patch(f"/v1/item/update/{item_id}", json={"price": 2000}, headers=h)
|
||||
assert r.json()["item"]["price"] == 2000
|
||||
assert r.json()["item"]["name"] == "상품A" # 미지정 필드 유지
|
||||
|
||||
# 삭제 (soft)
|
||||
r = await client.delete(f"/v1/item/delete/{item_id}", headers=h)
|
||||
assert r.json()["result"]["success"] is True
|
||||
|
||||
# 삭제 후 목록 0
|
||||
r = await client.get("/v1/item/list", headers=h)
|
||||
assert r.json()["total"] == 0
|
||||
|
||||
|
||||
async def test_item_not_found(client, company_id):
|
||||
h = await _headers(client, company_id)
|
||||
r = await client.get(f"/v1/item/{uuid.uuid4()}", headers=h)
|
||||
body = r.json()
|
||||
assert body["result"]["success"] is False
|
||||
assert body["result"]["code"] == 1300 # ITEM_NOT_FOUND
|
||||
|
||||
|
||||
async def test_item_company_scope(client, company_id, other_company_id):
|
||||
# 회사 A 가 상품 등록
|
||||
ha = await _headers(client, company_id, login_id="userA")
|
||||
r = await client.post("/v1/item/create", json={"name": "A상품"}, headers=ha)
|
||||
a_item_id = r.json()["item"]["item_id"]
|
||||
|
||||
# 회사 B 유저는 A 의 상품을 목록/단건에서 볼 수 없다
|
||||
hb = await _headers(client, other_company_id, login_id="userB")
|
||||
r = await client.get("/v1/item/list", headers=hb)
|
||||
assert r.json()["total"] == 0
|
||||
|
||||
r = await client.get(f"/v1/item/{a_item_id}", headers=hb)
|
||||
assert r.json()["result"]["code"] == 1300 # 타사 자원은 ITEM_NOT_FOUND
|
||||
@ -32,27 +32,18 @@ import type {
|
||||
|
||||
import { customFetch } from "../../mutator/custom-fetch";
|
||||
|
||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||
|
||||
/**
|
||||
* id/pw 로 로그인하고 JWT 토큰을 발급한다.
|
||||
* @summary 로그인
|
||||
*/
|
||||
export const login = (
|
||||
reqLogin: ReqLogin,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return customFetch<ResLogin>(
|
||||
{
|
||||
url: `/v1/auth/login`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqLogin,
|
||||
signal,
|
||||
},
|
||||
options,
|
||||
);
|
||||
export const login = (reqLogin: ReqLogin, signal?: AbortSignal) => {
|
||||
return customFetch<ResLogin>({
|
||||
url: `/v1/auth/login`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqLogin,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getLoginMutationOptions = <
|
||||
@ -65,7 +56,6 @@ export const getLoginMutationOptions = <
|
||||
{ data: ReqLogin },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof login>>,
|
||||
TError,
|
||||
@ -73,13 +63,13 @@ export const getLoginMutationOptions = <
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["login"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof login>>,
|
||||
@ -87,7 +77,7 @@ export const getLoginMutationOptions = <
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return login(data, requestOptions);
|
||||
return login(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
@ -113,7 +103,6 @@ export const useLogin = <
|
||||
{ data: ReqLogin },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
@ -127,24 +116,20 @@ export const useLogin = <
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
};
|
||||
/**
|
||||
* company_id 하위로 유저를 생성한다(시드/관리자용).
|
||||
* 새 계정을 생성한다.
|
||||
* @summary 계정 생성
|
||||
*/
|
||||
export const createAccount = (
|
||||
reqCreateAccount: ReqCreateAccount,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return customFetch<ResCreateAccount>(
|
||||
{
|
||||
url: `/v1/auth/create`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqCreateAccount,
|
||||
signal,
|
||||
},
|
||||
options,
|
||||
);
|
||||
return customFetch<ResCreateAccount>({
|
||||
url: `/v1/auth/create`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqCreateAccount,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateAccountMutationOptions = <
|
||||
@ -157,7 +142,6 @@ export const getCreateAccountMutationOptions = <
|
||||
{ data: ReqCreateAccount },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createAccount>>,
|
||||
TError,
|
||||
@ -165,13 +149,13 @@ export const getCreateAccountMutationOptions = <
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["createAccount"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof createAccount>>,
|
||||
@ -179,7 +163,7 @@ export const getCreateAccountMutationOptions = <
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return createAccount(data, requestOptions);
|
||||
return createAccount(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
@ -205,7 +189,6 @@ export const useCreateAccount = <
|
||||
{ data: ReqCreateAccount },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
@ -222,14 +205,12 @@ export const useCreateAccount = <
|
||||
* refresh 토큰으로 access 토큰을 재발급한다.
|
||||
* @summary 액세스 토큰 갱신
|
||||
*/
|
||||
export const refreshToken = (
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return customFetch<ResRefreshToken>(
|
||||
{ url: `/v1/auth/refresh_token`, method: "POST", signal },
|
||||
options,
|
||||
);
|
||||
export const refreshToken = (signal?: AbortSignal) => {
|
||||
return customFetch<ResRefreshToken>({
|
||||
url: `/v1/auth/refresh_token`,
|
||||
method: "POST",
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getRefreshTokenMutationOptions = <
|
||||
@ -242,7 +223,6 @@ export const getRefreshTokenMutationOptions = <
|
||||
void,
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof refreshToken>>,
|
||||
TError,
|
||||
@ -250,19 +230,19 @@ export const getRefreshTokenMutationOptions = <
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["refreshToken"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof refreshToken>>,
|
||||
void
|
||||
> = () => {
|
||||
return refreshToken(requestOptions);
|
||||
return refreshToken();
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
@ -285,7 +265,6 @@ export const useRefreshToken = <TError = void, TContext = unknown>(
|
||||
void,
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
@ -299,17 +278,11 @@ export const useRefreshToken = <TError = void, TContext = unknown>(
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
};
|
||||
/**
|
||||
* 유효한 access 토큰이 있어야 호출 가능. 토큰의 유저+소속사 정보를 반환한다.
|
||||
* 유효한 access 토큰이 있어야 호출 가능. 토큰의 유저+회사 정보를 반환한다.
|
||||
* @summary 내 정보
|
||||
*/
|
||||
export const me = (
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return customFetch<ResMe>(
|
||||
{ url: `/v1/auth/me`, method: "GET", signal },
|
||||
options,
|
||||
);
|
||||
export const me = (signal?: AbortSignal) => {
|
||||
return customFetch<ResMe>({ url: `/v1/auth/me`, method: "GET", signal });
|
||||
};
|
||||
|
||||
export const getMeQueryKey = () => {
|
||||
@ -323,14 +296,13 @@ export const getMeQueryOptions = <
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof me>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getMeQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof me>>> = ({ signal }) =>
|
||||
me(requestOptions, signal);
|
||||
me(signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof me>>,
|
||||
@ -355,7 +327,6 @@ export function useMe<TData = Awaited<ReturnType<typeof me>>, TError = void>(
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<TData, TError> & {
|
||||
@ -374,7 +345,6 @@ export function useMe<TData = Awaited<ReturnType<typeof me>>, TError = void>(
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
@ -385,7 +355,6 @@ export function useMe<TData = Awaited<ReturnType<typeof me>>, TError = void>(
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof me>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
@ -400,7 +369,6 @@ export function useMe<TData = Awaited<ReturnType<typeof me>>, TError = void>(
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof me>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
|
||||
551
negodata/front/src/api/generated/card/card.ts
Normal file
551
negodata/front/src/api/generated/card/card.ts
Normal file
@ -0,0 +1,551 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import type {
|
||||
DataTag,
|
||||
DefinedInitialDataOptions,
|
||||
DefinedUseQueryResult,
|
||||
MutationFunction,
|
||||
QueryClient,
|
||||
QueryFunction,
|
||||
QueryKey,
|
||||
UndefinedInitialDataOptions,
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
import type {
|
||||
HTTPValidationError,
|
||||
ListCardsParams,
|
||||
ReqCreateCard,
|
||||
ReqUpdateCard,
|
||||
ResCard,
|
||||
ResCardList,
|
||||
ResDeleteCard,
|
||||
} from ".././model";
|
||||
|
||||
import { customFetch } from "../../mutator/custom-fetch";
|
||||
|
||||
/**
|
||||
* @summary 협상카드 목록
|
||||
*/
|
||||
export const listCards = (params?: ListCardsParams, signal?: AbortSignal) => {
|
||||
return customFetch<ResCardList>({
|
||||
url: `/v1/card/list`,
|
||||
method: "GET",
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListCardsQueryKey = (params?: ListCardsParams) => {
|
||||
return [`/v1/card/list`, ...(params ? [params] : [])] as const;
|
||||
};
|
||||
|
||||
export const getListCardsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listCards>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
params?: ListCardsParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listCards>>, TError, TData>
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getListCardsQueryKey(params);
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listCards>>> = ({
|
||||
signal,
|
||||
}) => listCards(params, signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listCards>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
};
|
||||
|
||||
export type ListCardsQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listCards>>
|
||||
>;
|
||||
export type ListCardsQueryError = void | HTTPValidationError;
|
||||
|
||||
export function useListCards<
|
||||
TData = Awaited<ReturnType<typeof listCards>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
params: undefined | ListCardsParams,
|
||||
options: {
|
||||
query: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listCards>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
DefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof listCards>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof listCards>>
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useListCards<
|
||||
TData = Awaited<ReturnType<typeof listCards>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
params?: ListCardsParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listCards>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
UndefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof listCards>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof listCards>>
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useListCards<
|
||||
TData = Awaited<ReturnType<typeof listCards>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
params?: ListCardsParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listCards>>, TError, TData>
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
/**
|
||||
* @summary 협상카드 목록
|
||||
*/
|
||||
|
||||
export function useListCards<
|
||||
TData = Awaited<ReturnType<typeof listCards>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
params?: ListCardsParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listCards>>, TError, TData>
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
} {
|
||||
const queryOptions = getListCardsQueryOptions(params, options);
|
||||
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
||||
TData,
|
||||
TError
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary 협상카드 등록
|
||||
*/
|
||||
export const createCard = (
|
||||
reqCreateCard: ReqCreateCard,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return customFetch<ResCard>({
|
||||
url: `/v1/card/create`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqCreateCard,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateCardMutationOptions = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createCard>>,
|
||||
TError,
|
||||
{ data: ReqCreateCard },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createCard>>,
|
||||
TError,
|
||||
{ data: ReqCreateCard },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["createCard"];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof createCard>>,
|
||||
{ data: ReqCreateCard }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return createCard(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type CreateCardMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createCard>>
|
||||
>;
|
||||
export type CreateCardMutationBody = ReqCreateCard;
|
||||
export type CreateCardMutationError = void | HTTPValidationError;
|
||||
|
||||
/**
|
||||
* @summary 협상카드 등록
|
||||
*/
|
||||
export const useCreateCard = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(
|
||||
options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createCard>>,
|
||||
TError,
|
||||
{ data: ReqCreateCard },
|
||||
TContext
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createCard>>,
|
||||
TError,
|
||||
{ data: ReqCreateCard },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getCreateCardMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
};
|
||||
/**
|
||||
* @summary 협상카드 조회
|
||||
*/
|
||||
export const getCard = (cardId: string, signal?: AbortSignal) => {
|
||||
return customFetch<ResCard>({
|
||||
url: `/v1/card/${cardId}`,
|
||||
method: "GET",
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetCardQueryKey = (cardId?: string) => {
|
||||
return [`/v1/card/${cardId}`] as const;
|
||||
};
|
||||
|
||||
export const getGetCardQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getCard>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
cardId: string,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof getCard>>, TError, TData>
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetCardQueryKey(cardId);
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getCard>>> = ({
|
||||
signal,
|
||||
}) => getCard(cardId, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!cardId,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<Awaited<ReturnType<typeof getCard>>, TError, TData> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
};
|
||||
|
||||
export type GetCardQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getCard>>
|
||||
>;
|
||||
export type GetCardQueryError = void | HTTPValidationError;
|
||||
|
||||
export function useGetCard<
|
||||
TData = Awaited<ReturnType<typeof getCard>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
cardId: string,
|
||||
options: {
|
||||
query: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof getCard>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
DefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof getCard>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof getCard>>
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useGetCard<
|
||||
TData = Awaited<ReturnType<typeof getCard>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
cardId: string,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof getCard>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
UndefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof getCard>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof getCard>>
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useGetCard<
|
||||
TData = Awaited<ReturnType<typeof getCard>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
cardId: string,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof getCard>>, TError, TData>
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
/**
|
||||
* @summary 협상카드 조회
|
||||
*/
|
||||
|
||||
export function useGetCard<
|
||||
TData = Awaited<ReturnType<typeof getCard>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
cardId: string,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof getCard>>, TError, TData>
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
} {
|
||||
const queryOptions = getGetCardQueryOptions(cardId, options);
|
||||
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
||||
TData,
|
||||
TError
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary 협상카드 수정
|
||||
*/
|
||||
export const updateCard = (cardId: string, reqUpdateCard: ReqUpdateCard) => {
|
||||
return customFetch<ResCard>({
|
||||
url: `/v1/card/update/${cardId}`,
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqUpdateCard,
|
||||
});
|
||||
};
|
||||
|
||||
export const getUpdateCardMutationOptions = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateCard>>,
|
||||
TError,
|
||||
{ cardId: string; data: ReqUpdateCard },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateCard>>,
|
||||
TError,
|
||||
{ cardId: string; data: ReqUpdateCard },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["updateCard"];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof updateCard>>,
|
||||
{ cardId: string; data: ReqUpdateCard }
|
||||
> = (props) => {
|
||||
const { cardId, data } = props ?? {};
|
||||
|
||||
return updateCard(cardId, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type UpdateCardMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof updateCard>>
|
||||
>;
|
||||
export type UpdateCardMutationBody = ReqUpdateCard;
|
||||
export type UpdateCardMutationError = void | HTTPValidationError;
|
||||
|
||||
/**
|
||||
* @summary 협상카드 수정
|
||||
*/
|
||||
export const useUpdateCard = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(
|
||||
options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateCard>>,
|
||||
TError,
|
||||
{ cardId: string; data: ReqUpdateCard },
|
||||
TContext
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
Awaited<ReturnType<typeof updateCard>>,
|
||||
TError,
|
||||
{ cardId: string; data: ReqUpdateCard },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getUpdateCardMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
};
|
||||
/**
|
||||
* @summary 협상카드 삭제
|
||||
*/
|
||||
export const deleteCard = (cardId: string) => {
|
||||
return customFetch<ResDeleteCard>({
|
||||
url: `/v1/card/delete/${cardId}`,
|
||||
method: "DELETE",
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeleteCardMutationOptions = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteCard>>,
|
||||
TError,
|
||||
{ cardId: string },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteCard>>,
|
||||
TError,
|
||||
{ cardId: string },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["deleteCard"];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof deleteCard>>,
|
||||
{ cardId: string }
|
||||
> = (props) => {
|
||||
const { cardId } = props ?? {};
|
||||
|
||||
return deleteCard(cardId);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type DeleteCardMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof deleteCard>>
|
||||
>;
|
||||
|
||||
export type DeleteCardMutationError = void | HTTPValidationError;
|
||||
|
||||
/**
|
||||
* @summary 협상카드 삭제
|
||||
*/
|
||||
export const useDeleteCard = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(
|
||||
options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteCard>>,
|
||||
TError,
|
||||
{ cardId: string },
|
||||
TContext
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
Awaited<ReturnType<typeof deleteCard>>,
|
||||
TError,
|
||||
{ cardId: string },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getDeleteCardMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
};
|
||||
@ -19,19 +19,11 @@ import type {
|
||||
|
||||
import { customFetch } from "../../mutator/custom-fetch";
|
||||
|
||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||
|
||||
/**
|
||||
* @summary Healthz
|
||||
*/
|
||||
export const healthzHealthzGet = (
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return customFetch<unknown>(
|
||||
{ url: `/healthz`, method: "GET", signal },
|
||||
options,
|
||||
);
|
||||
export const healthzHealthzGet = (signal?: AbortSignal) => {
|
||||
return customFetch<unknown>({ url: `/healthz`, method: "GET", signal });
|
||||
};
|
||||
|
||||
export const getHealthzHealthzGetQueryKey = () => {
|
||||
@ -49,15 +41,14 @@ export const getHealthzHealthzGetQueryOptions = <
|
||||
TData
|
||||
>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getHealthzHealthzGetQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof healthzHealthzGet>>
|
||||
> = ({ signal }) => healthzHealthzGet(requestOptions, signal);
|
||||
> = ({ signal }) => healthzHealthzGet(signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof healthzHealthzGet>>,
|
||||
@ -91,7 +82,6 @@ export function useHealthzHealthzGet<
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<TData, TError> & {
|
||||
@ -117,7 +107,6 @@ export function useHealthzHealthzGet<
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
@ -135,7 +124,6 @@ export function useHealthzHealthzGet<
|
||||
TData
|
||||
>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
@ -157,7 +145,6 @@ export function useHealthzHealthzGet<
|
||||
TData
|
||||
>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
|
||||
145
negodata/front/src/api/generated/enums/enums.ts
Normal file
145
negodata/front/src/api/generated/enums/enums.ts
Normal file
@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import type {
|
||||
DataTag,
|
||||
DefinedInitialDataOptions,
|
||||
DefinedUseQueryResult,
|
||||
QueryClient,
|
||||
QueryFunction,
|
||||
QueryKey,
|
||||
UndefinedInitialDataOptions,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
import type { ResEnums } from ".././model";
|
||||
|
||||
import { customFetch } from "../../mutator/custom-fetch";
|
||||
|
||||
/**
|
||||
* @summary 도메인 코드 enum 전체
|
||||
*/
|
||||
export const listEnums = (signal?: AbortSignal) => {
|
||||
return customFetch<ResEnums>({ url: `/v1/enums`, method: "GET", signal });
|
||||
};
|
||||
|
||||
export const getListEnumsQueryKey = () => {
|
||||
return [`/v1/enums`] as const;
|
||||
};
|
||||
|
||||
export const getListEnumsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listEnums>>,
|
||||
TError = void,
|
||||
>(options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listEnums>>, TError, TData>
|
||||
>;
|
||||
}) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getListEnumsQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listEnums>>> = ({
|
||||
signal,
|
||||
}) => listEnums(signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listEnums>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
};
|
||||
|
||||
export type ListEnumsQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listEnums>>
|
||||
>;
|
||||
export type ListEnumsQueryError = void;
|
||||
|
||||
export function useListEnums<
|
||||
TData = Awaited<ReturnType<typeof listEnums>>,
|
||||
TError = void,
|
||||
>(
|
||||
options: {
|
||||
query: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listEnums>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
DefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof listEnums>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof listEnums>>
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useListEnums<
|
||||
TData = Awaited<ReturnType<typeof listEnums>>,
|
||||
TError = void,
|
||||
>(
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listEnums>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
UndefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof listEnums>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof listEnums>>
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useListEnums<
|
||||
TData = Awaited<ReturnType<typeof listEnums>>,
|
||||
TError = void,
|
||||
>(
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listEnums>>, TError, TData>
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
/**
|
||||
* @summary 도메인 코드 enum 전체
|
||||
*/
|
||||
|
||||
export function useListEnums<
|
||||
TData = Awaited<ReturnType<typeof listEnums>>,
|
||||
TError = void,
|
||||
>(
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listEnums>>, TError, TData>
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
} {
|
||||
const queryOptions = getListEnumsQueryOptions(options);
|
||||
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
||||
TData,
|
||||
TError
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
@ -24,11 +24,14 @@ import type {
|
||||
BodyUploadItemsExcelV1ItemUploadExcelPost,
|
||||
HTTPValidationError,
|
||||
ListItemsParams,
|
||||
ReqCheckCodes,
|
||||
ReqCreateItem,
|
||||
ReqUpdateItem,
|
||||
ResCheckCodes,
|
||||
ResDeleteItem,
|
||||
ResExcelUpload,
|
||||
ResItem,
|
||||
ResItemCategories,
|
||||
ResItemList,
|
||||
ResLowestPriceResult,
|
||||
ResLowestPriceTrigger,
|
||||
@ -36,20 +39,16 @@ import type {
|
||||
|
||||
import { customFetch } from "../../mutator/custom-fetch";
|
||||
|
||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||
|
||||
/**
|
||||
* @summary 상품 목록
|
||||
*/
|
||||
export const listItems = (
|
||||
params?: ListItemsParams,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return customFetch<ResItemList>(
|
||||
{ url: `/v1/item/list`, method: "GET", params, signal },
|
||||
options,
|
||||
);
|
||||
export const listItems = (params?: ListItemsParams, signal?: AbortSignal) => {
|
||||
return customFetch<ResItemList>({
|
||||
url: `/v1/item/list`,
|
||||
method: "GET",
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListItemsQueryKey = (params?: ListItemsParams) => {
|
||||
@ -65,16 +64,15 @@ export const getListItemsQueryOptions = <
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listItems>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getListItemsQueryKey(params);
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listItems>>> = ({
|
||||
signal,
|
||||
}) => listItems(params, requestOptions, signal);
|
||||
}) => listItems(params, signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listItems>>,
|
||||
@ -105,7 +103,6 @@ export function useListItems<
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<TData, TError> & {
|
||||
@ -128,7 +125,6 @@ export function useListItems<
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
@ -143,7 +139,6 @@ export function useListItems<
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listItems>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
@ -162,7 +157,6 @@ export function useListItems<
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listItems>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
@ -180,24 +174,167 @@ export function useListItems<
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary 상품 카테고리 목록(distinct)
|
||||
*/
|
||||
export const listItemCategories = (signal?: AbortSignal) => {
|
||||
return customFetch<ResItemCategories>({
|
||||
url: `/v1/item/categories`,
|
||||
method: "GET",
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListItemCategoriesQueryKey = () => {
|
||||
return [`/v1/item/categories`] as const;
|
||||
};
|
||||
|
||||
export const getListItemCategoriesQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listItemCategories>>,
|
||||
TError = void,
|
||||
>(options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listItemCategories>>,
|
||||
TError,
|
||||
TData
|
||||
>
|
||||
>;
|
||||
}) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getListItemCategoriesQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof listItemCategories>>
|
||||
> = ({ signal }) => listItemCategories(signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listItemCategories>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
};
|
||||
|
||||
export type ListItemCategoriesQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listItemCategories>>
|
||||
>;
|
||||
export type ListItemCategoriesQueryError = void;
|
||||
|
||||
export function useListItemCategories<
|
||||
TData = Awaited<ReturnType<typeof listItemCategories>>,
|
||||
TError = void,
|
||||
>(
|
||||
options: {
|
||||
query: Partial<
|
||||
UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listItemCategories>>,
|
||||
TError,
|
||||
TData
|
||||
>
|
||||
> &
|
||||
Pick<
|
||||
DefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof listItemCategories>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof listItemCategories>>
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useListItemCategories<
|
||||
TData = Awaited<ReturnType<typeof listItemCategories>>,
|
||||
TError = void,
|
||||
>(
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listItemCategories>>,
|
||||
TError,
|
||||
TData
|
||||
>
|
||||
> &
|
||||
Pick<
|
||||
UndefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof listItemCategories>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof listItemCategories>>
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useListItemCategories<
|
||||
TData = Awaited<ReturnType<typeof listItemCategories>>,
|
||||
TError = void,
|
||||
>(
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listItemCategories>>,
|
||||
TError,
|
||||
TData
|
||||
>
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
/**
|
||||
* @summary 상품 카테고리 목록(distinct)
|
||||
*/
|
||||
|
||||
export function useListItemCategories<
|
||||
TData = Awaited<ReturnType<typeof listItemCategories>>,
|
||||
TError = void,
|
||||
>(
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listItemCategories>>,
|
||||
TError,
|
||||
TData
|
||||
>
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
} {
|
||||
const queryOptions = getListItemCategoriesQueryOptions(options);
|
||||
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
||||
TData,
|
||||
TError
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary 상품 등록
|
||||
*/
|
||||
export const createItem = (
|
||||
reqCreateItem: ReqCreateItem,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return customFetch<ResItem>(
|
||||
{
|
||||
url: `/v1/item/create`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqCreateItem,
|
||||
signal,
|
||||
},
|
||||
options,
|
||||
);
|
||||
return customFetch<ResItem>({
|
||||
url: `/v1/item/create`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqCreateItem,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateItemMutationOptions = <
|
||||
@ -210,7 +347,6 @@ export const getCreateItemMutationOptions = <
|
||||
{ data: ReqCreateItem },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createItem>>,
|
||||
TError,
|
||||
@ -218,13 +354,13 @@ export const getCreateItemMutationOptions = <
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["createItem"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof createItem>>,
|
||||
@ -232,7 +368,7 @@ export const getCreateItemMutationOptions = <
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return createItem(data, requestOptions);
|
||||
return createItem(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
@ -258,7 +394,6 @@ export const useCreateItem = <
|
||||
{ data: ReqCreateItem },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
@ -271,27 +406,108 @@ export const useCreateItem = <
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
};
|
||||
/**
|
||||
* @summary 코드 중복 사전검사(업로드 즉시)
|
||||
*/
|
||||
export const checkItemCodes = (
|
||||
reqCheckCodes: ReqCheckCodes,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return customFetch<ResCheckCodes>({
|
||||
url: `/v1/item/check-codes`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqCheckCodes,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCheckItemCodesMutationOptions = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof checkItemCodes>>,
|
||||
TError,
|
||||
{ data: ReqCheckCodes },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof checkItemCodes>>,
|
||||
TError,
|
||||
{ data: ReqCheckCodes },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["checkItemCodes"];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof checkItemCodes>>,
|
||||
{ data: ReqCheckCodes }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return checkItemCodes(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type CheckItemCodesMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof checkItemCodes>>
|
||||
>;
|
||||
export type CheckItemCodesMutationBody = ReqCheckCodes;
|
||||
export type CheckItemCodesMutationError = void | HTTPValidationError;
|
||||
|
||||
/**
|
||||
* @summary 코드 중복 사전검사(업로드 즉시)
|
||||
*/
|
||||
export const useCheckItemCodes = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(
|
||||
options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof checkItemCodes>>,
|
||||
TError,
|
||||
{ data: ReqCheckCodes },
|
||||
TContext
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
Awaited<ReturnType<typeof checkItemCodes>>,
|
||||
TError,
|
||||
{ data: ReqCheckCodes },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getCheckItemCodesMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
};
|
||||
/**
|
||||
* @summary 엑셀 일괄 등록(스텁)
|
||||
*/
|
||||
export const uploadItemsExcel = (
|
||||
bodyUploadItemsExcelV1ItemUploadExcelPost: BodyUploadItemsExcelV1ItemUploadExcelPost,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
const formData = new FormData();
|
||||
formData.append(`file`, bodyUploadItemsExcelV1ItemUploadExcelPost.file);
|
||||
|
||||
return customFetch<ResExcelUpload>(
|
||||
{
|
||||
url: `/v1/item/upload-excel`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
data: formData,
|
||||
signal,
|
||||
},
|
||||
options,
|
||||
);
|
||||
return customFetch<ResExcelUpload>({
|
||||
url: `/v1/item/upload-excel`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
data: formData,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getUploadItemsExcelMutationOptions = <
|
||||
@ -304,7 +520,6 @@ export const getUploadItemsExcelMutationOptions = <
|
||||
{ data: BodyUploadItemsExcelV1ItemUploadExcelPost },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof uploadItemsExcel>>,
|
||||
TError,
|
||||
@ -312,13 +527,13 @@ export const getUploadItemsExcelMutationOptions = <
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["uploadItemsExcel"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof uploadItemsExcel>>,
|
||||
@ -326,7 +541,7 @@ export const getUploadItemsExcelMutationOptions = <
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return uploadItemsExcel(data, requestOptions);
|
||||
return uploadItemsExcel(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
@ -353,7 +568,6 @@ export const useUploadItemsExcel = <
|
||||
{ data: BodyUploadItemsExcelV1ItemUploadExcelPost },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
@ -369,15 +583,12 @@ export const useUploadItemsExcel = <
|
||||
/**
|
||||
* @summary 상품 조회
|
||||
*/
|
||||
export const getItem = (
|
||||
itemId: string,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return customFetch<ResItem>(
|
||||
{ url: `/v1/item/${itemId}`, method: "GET", signal },
|
||||
options,
|
||||
);
|
||||
export const getItem = (itemId: string, signal?: AbortSignal) => {
|
||||
return customFetch<ResItem>({
|
||||
url: `/v1/item/${itemId}`,
|
||||
method: "GET",
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetItemQueryKey = (itemId?: string) => {
|
||||
@ -393,16 +604,15 @@ export const getGetItemQueryOptions = <
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof getItem>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetItemQueryKey(itemId);
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getItem>>> = ({
|
||||
signal,
|
||||
}) => getItem(itemId, requestOptions, signal);
|
||||
}) => getItem(itemId, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
@ -436,7 +646,6 @@ export function useGetItem<
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<TData, TError> & {
|
||||
@ -459,7 +668,6 @@ export function useGetItem<
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
@ -474,7 +682,6 @@ export function useGetItem<
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof getItem>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
@ -493,7 +700,6 @@ export function useGetItem<
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof getItem>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
@ -514,20 +720,13 @@ export function useGetItem<
|
||||
/**
|
||||
* @summary 상품 수정
|
||||
*/
|
||||
export const updateItem = (
|
||||
itemId: string,
|
||||
reqUpdateItem: ReqUpdateItem,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
) => {
|
||||
return customFetch<ResItem>(
|
||||
{
|
||||
url: `/v1/item/update/${itemId}`,
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqUpdateItem,
|
||||
},
|
||||
options,
|
||||
);
|
||||
export const updateItem = (itemId: string, reqUpdateItem: ReqUpdateItem) => {
|
||||
return customFetch<ResItem>({
|
||||
url: `/v1/item/update/${itemId}`,
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqUpdateItem,
|
||||
});
|
||||
};
|
||||
|
||||
export const getUpdateItemMutationOptions = <
|
||||
@ -540,7 +739,6 @@ export const getUpdateItemMutationOptions = <
|
||||
{ itemId: string; data: ReqUpdateItem },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateItem>>,
|
||||
TError,
|
||||
@ -548,13 +746,13 @@ export const getUpdateItemMutationOptions = <
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["updateItem"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof updateItem>>,
|
||||
@ -562,7 +760,7 @@ export const getUpdateItemMutationOptions = <
|
||||
> = (props) => {
|
||||
const { itemId, data } = props ?? {};
|
||||
|
||||
return updateItem(itemId, data, requestOptions);
|
||||
return updateItem(itemId, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
@ -588,7 +786,6 @@ export const useUpdateItem = <
|
||||
{ itemId: string; data: ReqUpdateItem },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
@ -604,14 +801,11 @@ export const useUpdateItem = <
|
||||
/**
|
||||
* @summary 상품 삭제
|
||||
*/
|
||||
export const deleteItem = (
|
||||
itemId: string,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
) => {
|
||||
return customFetch<ResDeleteItem>(
|
||||
{ url: `/v1/item/delete/${itemId}`, method: "DELETE" },
|
||||
options,
|
||||
);
|
||||
export const deleteItem = (itemId: string) => {
|
||||
return customFetch<ResDeleteItem>({
|
||||
url: `/v1/item/delete/${itemId}`,
|
||||
method: "DELETE",
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeleteItemMutationOptions = <
|
||||
@ -624,7 +818,6 @@ export const getDeleteItemMutationOptions = <
|
||||
{ itemId: string },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteItem>>,
|
||||
TError,
|
||||
@ -632,13 +825,13 @@ export const getDeleteItemMutationOptions = <
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["deleteItem"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof deleteItem>>,
|
||||
@ -646,7 +839,7 @@ export const getDeleteItemMutationOptions = <
|
||||
> = (props) => {
|
||||
const { itemId } = props ?? {};
|
||||
|
||||
return deleteItem(itemId, requestOptions);
|
||||
return deleteItem(itemId);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
@ -672,7 +865,6 @@ export const useDeleteItem = <
|
||||
{ itemId: string },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
@ -688,15 +880,12 @@ export const useDeleteItem = <
|
||||
/**
|
||||
* @summary 최저가 수집 요청(스텁)
|
||||
*/
|
||||
export const triggerLowestPrice = (
|
||||
itemId: string,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return customFetch<ResLowestPriceTrigger>(
|
||||
{ url: `/v1/item/${itemId}/lowest-price`, method: "POST", signal },
|
||||
options,
|
||||
);
|
||||
export const triggerLowestPrice = (itemId: string, signal?: AbortSignal) => {
|
||||
return customFetch<ResLowestPriceTrigger>({
|
||||
url: `/v1/item/${itemId}/lowest-price`,
|
||||
method: "POST",
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getTriggerLowestPriceMutationOptions = <
|
||||
@ -709,7 +898,6 @@ export const getTriggerLowestPriceMutationOptions = <
|
||||
{ itemId: string },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof triggerLowestPrice>>,
|
||||
TError,
|
||||
@ -717,13 +905,13 @@ export const getTriggerLowestPriceMutationOptions = <
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["triggerLowestPrice"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof triggerLowestPrice>>,
|
||||
@ -731,7 +919,7 @@ export const getTriggerLowestPriceMutationOptions = <
|
||||
> = (props) => {
|
||||
const { itemId } = props ?? {};
|
||||
|
||||
return triggerLowestPrice(itemId, requestOptions);
|
||||
return triggerLowestPrice(itemId);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
@ -757,7 +945,6 @@ export const useTriggerLowestPrice = <
|
||||
{ itemId: string },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
@ -773,15 +960,12 @@ export const useTriggerLowestPrice = <
|
||||
/**
|
||||
* @summary 최저가 수집 결과(스텁)
|
||||
*/
|
||||
export const getLowestPrice = (
|
||||
itemId: string,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return customFetch<ResLowestPriceResult>(
|
||||
{ url: `/v1/item/${itemId}/lowest-price`, method: "GET", signal },
|
||||
options,
|
||||
);
|
||||
export const getLowestPrice = (itemId: string, signal?: AbortSignal) => {
|
||||
return customFetch<ResLowestPriceResult>({
|
||||
url: `/v1/item/${itemId}/lowest-price`,
|
||||
method: "GET",
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetLowestPriceQueryKey = (itemId?: string) => {
|
||||
@ -797,16 +981,15 @@ export const getGetLowestPriceQueryOptions = <
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof getLowestPrice>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetLowestPriceQueryKey(itemId);
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getLowestPrice>>> = ({
|
||||
signal,
|
||||
}) => getLowestPrice(itemId, requestOptions, signal);
|
||||
}) => getLowestPrice(itemId, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
@ -842,7 +1025,6 @@ export function useGetLowestPrice<
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<TData, TError> & {
|
||||
@ -865,7 +1047,6 @@ export function useGetLowestPrice<
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
@ -880,7 +1061,6 @@ export function useGetLowestPrice<
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof getLowestPrice>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
@ -899,7 +1079,6 @@ export function useGetLowestPrice<
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof getLowestPrice>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
|
||||
30
negodata/front/src/api/generated/model/cardData.ts
Normal file
30
negodata/front/src/api/generated/model/cardData.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { CardDataUserId } from "./cardDataUserId";
|
||||
import type { CardDataName } from "./cardDataName";
|
||||
import type { CardDataNumber } from "./cardDataNumber";
|
||||
import type { CardDataScript } from "./cardDataScript";
|
||||
import type { CardDataEditScript } from "./cardDataEditScript";
|
||||
import type { CardDataCondition } from "./cardDataCondition";
|
||||
import type { CardDataMemo } from "./cardDataMemo";
|
||||
import type { CardDataCreatedAt } from "./cardDataCreatedAt";
|
||||
import type { CardDataUpdatedAt } from "./cardDataUpdatedAt";
|
||||
|
||||
export interface CardData {
|
||||
nego_card_id: string;
|
||||
user_id?: CardDataUserId;
|
||||
is_wildcard?: boolean;
|
||||
name?: CardDataName;
|
||||
number?: CardDataNumber;
|
||||
script?: CardDataScript;
|
||||
edit_script?: CardDataEditScript;
|
||||
status?: number;
|
||||
condition?: CardDataCondition;
|
||||
memo?: CardDataMemo;
|
||||
created_at?: CardDataCreatedAt;
|
||||
updated_at?: CardDataUpdatedAt;
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type CardDataCondition = string | null;
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type CardDataCreatedAt = string | null;
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type CardDataEditScript = unknown | null;
|
||||
8
negodata/front/src/api/generated/model/cardDataMemo.ts
Normal file
8
negodata/front/src/api/generated/model/cardDataMemo.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type CardDataMemo = string | null;
|
||||
8
negodata/front/src/api/generated/model/cardDataName.ts
Normal file
8
negodata/front/src/api/generated/model/cardDataName.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type CardDataName = string | null;
|
||||
8
negodata/front/src/api/generated/model/cardDataNumber.ts
Normal file
8
negodata/front/src/api/generated/model/cardDataNumber.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type CardDataNumber = string | null;
|
||||
8
negodata/front/src/api/generated/model/cardDataScript.ts
Normal file
8
negodata/front/src/api/generated/model/cardDataScript.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type CardDataScript = string | null;
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type CardDataUpdatedAt = string | null;
|
||||
8
negodata/front/src/api/generated/model/cardDataUserId.ts
Normal file
8
negodata/front/src/api/generated/model/cardDataUserId.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type CardDataUserId = string | null;
|
||||
@ -5,7 +5,7 @@
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export interface CompanyBrief {
|
||||
export interface CompanyData {
|
||||
company_id?: string;
|
||||
name?: string;
|
||||
}
|
||||
12
negodata/front/src/api/generated/model/enumOption.ts
Normal file
12
negodata/front/src/api/generated/model/enumOption.ts
Normal file
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export interface EnumOption {
|
||||
value: number;
|
||||
name: string;
|
||||
label: string;
|
||||
}
|
||||
@ -8,17 +8,29 @@
|
||||
export * from "./asyncJob";
|
||||
export * from "./bodyUploadItemsExcelV1ItemUploadExcelPost";
|
||||
export * from "./bodyUploadSuppliersExcelV1SupplierUploadExcelPost";
|
||||
export * from "./cardData";
|
||||
export * from "./cardDataCondition";
|
||||
export * from "./cardDataCreatedAt";
|
||||
export * from "./cardDataEditScript";
|
||||
export * from "./cardDataMemo";
|
||||
export * from "./cardDataName";
|
||||
export * from "./cardDataNumber";
|
||||
export * from "./cardDataScript";
|
||||
export * from "./cardDataUpdatedAt";
|
||||
export * from "./cardDataUserId";
|
||||
export * from "./chatMessageData";
|
||||
export * from "./chatMessageDataCardId";
|
||||
export * from "./chatMessageDataCardType";
|
||||
export * from "./chatMessageDataCardUsedYn";
|
||||
export * from "./chatMessageDataIndicatorValue";
|
||||
export * from "./companyBrief";
|
||||
export * from "./companyData";
|
||||
export * from "./enumOption";
|
||||
export * from "./errorInfo";
|
||||
export * from "./errorInfoCode";
|
||||
export * from "./errorInfoDesc";
|
||||
export * from "./errorInfoSuccess";
|
||||
export * from "./hTTPValidationError";
|
||||
export * from "./itemCategory";
|
||||
export * from "./itemData";
|
||||
export * from "./itemDataCategory";
|
||||
export * from "./itemDataCode";
|
||||
@ -36,6 +48,7 @@ export * from "./itemDataQuantityUnit";
|
||||
export * from "./itemDataSpec";
|
||||
export * from "./itemDataUpdatedAt";
|
||||
export * from "./itemDataVatYn";
|
||||
export * from "./listCardsParams";
|
||||
export * from "./listItemsParams";
|
||||
export * from "./listQuotationsParams";
|
||||
export * from "./listSuppliersParams";
|
||||
@ -62,7 +75,15 @@ export * from "./quotationSettingData";
|
||||
export * from "./quotationSettingDataCreatedAt";
|
||||
export * from "./quotationSettingDataUpdatedAt";
|
||||
export * from "./quotationSettingDataUserId";
|
||||
export * from "./reqCheckCodes";
|
||||
export * from "./reqCreateAccount";
|
||||
export * from "./reqCreateCard";
|
||||
export * from "./reqCreateCardCondition";
|
||||
export * from "./reqCreateCardEditScript";
|
||||
export * from "./reqCreateCardMemo";
|
||||
export * from "./reqCreateCardName";
|
||||
export * from "./reqCreateCardNumber";
|
||||
export * from "./reqCreateCardScript";
|
||||
export * from "./reqCreateItem";
|
||||
export * from "./reqCreateItemCategory";
|
||||
export * from "./reqCreateItemCode";
|
||||
@ -91,6 +112,14 @@ export * from "./reqCreateSupplierManagerEmail";
|
||||
export * from "./reqCreateSupplierManagerName";
|
||||
export * from "./reqCreateSupplierPriority";
|
||||
export * from "./reqLogin";
|
||||
export * from "./reqUpdateCard";
|
||||
export * from "./reqUpdateCardCondition";
|
||||
export * from "./reqUpdateCardEditScript";
|
||||
export * from "./reqUpdateCardMemo";
|
||||
export * from "./reqUpdateCardName";
|
||||
export * from "./reqUpdateCardNumber";
|
||||
export * from "./reqUpdateCardScript";
|
||||
export * from "./reqUpdateCardStatus";
|
||||
export * from "./reqUpdateItem";
|
||||
export * from "./reqUpdateItemCategory";
|
||||
export * from "./reqUpdateItemCategoryType";
|
||||
@ -120,12 +149,21 @@ export * from "./reqUpdateSupplierManagerEmail";
|
||||
export * from "./reqUpdateSupplierManagerName";
|
||||
export * from "./reqUpdateSupplierName";
|
||||
export * from "./reqUpdateSupplierPriority";
|
||||
export * from "./resCard";
|
||||
export * from "./resCardCard";
|
||||
export * from "./resCardList";
|
||||
export * from "./resCardListMsg";
|
||||
export * from "./resCardMsg";
|
||||
export * from "./resCheckCodes";
|
||||
export * from "./resCheckCodesMsg";
|
||||
export * from "./resCreateAccount";
|
||||
export * from "./resCreateAccountMsg";
|
||||
export * from "./resCreateQuotation";
|
||||
export * from "./resCreateQuotationAsyncJob";
|
||||
export * from "./resCreateQuotationMsg";
|
||||
export * from "./resCreateQuotationQuotation";
|
||||
export * from "./resDeleteCard";
|
||||
export * from "./resDeleteCardMsg";
|
||||
export * from "./resDeleteItem";
|
||||
export * from "./resDeleteItemMsg";
|
||||
export * from "./resDeleteQuotation";
|
||||
@ -134,10 +172,15 @@ export * from "./resDeleteQuotationSetting";
|
||||
export * from "./resDeleteQuotationSettingMsg";
|
||||
export * from "./resDeleteSupplier";
|
||||
export * from "./resDeleteSupplierMsg";
|
||||
export * from "./resEnums";
|
||||
export * from "./resEnumsEnums";
|
||||
export * from "./resEnumsMsg";
|
||||
export * from "./resExcelUpload";
|
||||
export * from "./resExcelUploadMsg";
|
||||
export * from "./resExcelUploadReceivedFilename";
|
||||
export * from "./resItem";
|
||||
export * from "./resItemCategories";
|
||||
export * from "./resItemCategoriesMsg";
|
||||
export * from "./resItemItem";
|
||||
export * from "./resItemList";
|
||||
export * from "./resItemListMsg";
|
||||
|
||||
11
negodata/front/src/api/generated/model/itemCategory.ts
Normal file
11
negodata/front/src/api/generated/model/itemCategory.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export interface ItemCategory {
|
||||
name: string;
|
||||
category_type?: number;
|
||||
}
|
||||
@ -5,4 +5,4 @@
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ItemDataDeliveryType = string | null;
|
||||
export type ItemDataDeliveryType = number | null;
|
||||
|
||||
@ -5,4 +5,4 @@
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ItemDataQuantityUnit = number | null;
|
||||
export type ItemDataQuantityUnit = string | null;
|
||||
|
||||
22
negodata/front/src/api/generated/model/listCardsParams.ts
Normal file
22
negodata/front/src/api/generated/model/listCardsParams.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ListCardsParams = {
|
||||
/**
|
||||
* 카드명/카드번호/스크립트 검색
|
||||
*/
|
||||
search?: string | null;
|
||||
/**
|
||||
* @minimum 1
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* @minimum 1
|
||||
* @maximum 100
|
||||
*/
|
||||
size?: number;
|
||||
};
|
||||
@ -10,6 +10,10 @@ export type ListSuppliersParams = {
|
||||
* 협력사명/코드/담당자명 검색
|
||||
*/
|
||||
search?: string | null;
|
||||
/**
|
||||
* 우선순위 필터(HIGH/MEDIUM/LOW)
|
||||
*/
|
||||
priority?: string | null;
|
||||
/**
|
||||
* @minimum 1
|
||||
*/
|
||||
|
||||
10
negodata/front/src/api/generated/model/reqCheckCodes.ts
Normal file
10
negodata/front/src/api/generated/model/reqCheckCodes.ts
Normal file
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export interface ReqCheckCodes {
|
||||
codes?: string[];
|
||||
}
|
||||
23
negodata/front/src/api/generated/model/reqCreateCard.ts
Normal file
23
negodata/front/src/api/generated/model/reqCreateCard.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { ReqCreateCardName } from "./reqCreateCardName";
|
||||
import type { ReqCreateCardNumber } from "./reqCreateCardNumber";
|
||||
import type { ReqCreateCardScript } from "./reqCreateCardScript";
|
||||
import type { ReqCreateCardEditScript } from "./reqCreateCardEditScript";
|
||||
import type { ReqCreateCardCondition } from "./reqCreateCardCondition";
|
||||
import type { ReqCreateCardMemo } from "./reqCreateCardMemo";
|
||||
|
||||
export interface ReqCreateCard {
|
||||
is_wildcard?: boolean;
|
||||
name?: ReqCreateCardName;
|
||||
number?: ReqCreateCardNumber;
|
||||
script?: ReqCreateCardScript;
|
||||
edit_script?: ReqCreateCardEditScript;
|
||||
status?: number;
|
||||
condition?: ReqCreateCardCondition;
|
||||
memo?: ReqCreateCardMemo;
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ReqCreateCardCondition = string | null;
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ReqCreateCardEditScript = unknown | null;
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ReqCreateCardMemo = string | null;
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ReqCreateCardName = string | null;
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ReqCreateCardNumber = string | null;
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ReqCreateCardScript = string | null;
|
||||
@ -5,4 +5,4 @@
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ReqCreateItemDeliveryType = string | null;
|
||||
export type ReqCreateItemDeliveryType = number | null;
|
||||
|
||||
@ -5,4 +5,4 @@
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ReqCreateItemQuantityUnit = number | null;
|
||||
export type ReqCreateItemQuantityUnit = string | null;
|
||||
|
||||
23
negodata/front/src/api/generated/model/reqUpdateCard.ts
Normal file
23
negodata/front/src/api/generated/model/reqUpdateCard.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { ReqUpdateCardName } from "./reqUpdateCardName";
|
||||
import type { ReqUpdateCardNumber } from "./reqUpdateCardNumber";
|
||||
import type { ReqUpdateCardScript } from "./reqUpdateCardScript";
|
||||
import type { ReqUpdateCardEditScript } from "./reqUpdateCardEditScript";
|
||||
import type { ReqUpdateCardStatus } from "./reqUpdateCardStatus";
|
||||
import type { ReqUpdateCardCondition } from "./reqUpdateCardCondition";
|
||||
import type { ReqUpdateCardMemo } from "./reqUpdateCardMemo";
|
||||
|
||||
export interface ReqUpdateCard {
|
||||
name?: ReqUpdateCardName;
|
||||
number?: ReqUpdateCardNumber;
|
||||
script?: ReqUpdateCardScript;
|
||||
edit_script?: ReqUpdateCardEditScript;
|
||||
status?: ReqUpdateCardStatus;
|
||||
condition?: ReqUpdateCardCondition;
|
||||
memo?: ReqUpdateCardMemo;
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ReqUpdateCardCondition = string | null;
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ReqUpdateCardEditScript = unknown | null;
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ReqUpdateCardMemo = string | null;
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ReqUpdateCardName = string | null;
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ReqUpdateCardNumber = string | null;
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ReqUpdateCardScript = string | null;
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ReqUpdateCardStatus = number | null;
|
||||
@ -5,4 +5,4 @@
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ReqUpdateItemDeliveryType = string | null;
|
||||
export type ReqUpdateItemDeliveryType = number | null;
|
||||
|
||||
@ -5,4 +5,4 @@
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ReqUpdateItemQuantityUnit = number | null;
|
||||
export type ReqUpdateItemQuantityUnit = string | null;
|
||||
|
||||
15
negodata/front/src/api/generated/model/resCard.ts
Normal file
15
negodata/front/src/api/generated/model/resCard.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { ErrorInfo } from "./errorInfo";
|
||||
import type { ResCardMsg } from "./resCardMsg";
|
||||
import type { ResCardCard } from "./resCardCard";
|
||||
|
||||
export interface ResCard {
|
||||
result?: ErrorInfo;
|
||||
msg?: ResCardMsg;
|
||||
card?: ResCardCard;
|
||||
}
|
||||
9
negodata/front/src/api/generated/model/resCardCard.ts
Normal file
9
negodata/front/src/api/generated/model/resCardCard.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { CardData } from "./cardData";
|
||||
|
||||
export type ResCardCard = CardData | null;
|
||||
18
negodata/front/src/api/generated/model/resCardList.ts
Normal file
18
negodata/front/src/api/generated/model/resCardList.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { ErrorInfo } from "./errorInfo";
|
||||
import type { ResCardListMsg } from "./resCardListMsg";
|
||||
import type { CardData } from "./cardData";
|
||||
|
||||
export interface ResCardList {
|
||||
result?: ErrorInfo;
|
||||
msg?: ResCardListMsg;
|
||||
cards?: CardData[];
|
||||
total?: number;
|
||||
page?: number;
|
||||
size?: number;
|
||||
}
|
||||
8
negodata/front/src/api/generated/model/resCardListMsg.ts
Normal file
8
negodata/front/src/api/generated/model/resCardListMsg.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ResCardListMsg = string | null;
|
||||
8
negodata/front/src/api/generated/model/resCardMsg.ts
Normal file
8
negodata/front/src/api/generated/model/resCardMsg.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ResCardMsg = string | null;
|
||||
14
negodata/front/src/api/generated/model/resCheckCodes.ts
Normal file
14
negodata/front/src/api/generated/model/resCheckCodes.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { ErrorInfo } from "./errorInfo";
|
||||
import type { ResCheckCodesMsg } from "./resCheckCodesMsg";
|
||||
|
||||
export interface ResCheckCodes {
|
||||
result?: ErrorInfo;
|
||||
msg?: ResCheckCodesMsg;
|
||||
existing?: string[];
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ResCheckCodesMsg = string | null;
|
||||
13
negodata/front/src/api/generated/model/resDeleteCard.ts
Normal file
13
negodata/front/src/api/generated/model/resDeleteCard.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { ErrorInfo } from "./errorInfo";
|
||||
import type { ResDeleteCardMsg } from "./resDeleteCardMsg";
|
||||
|
||||
export interface ResDeleteCard {
|
||||
result?: ErrorInfo;
|
||||
msg?: ResDeleteCardMsg;
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ResDeleteCardMsg = string | null;
|
||||
15
negodata/front/src/api/generated/model/resEnums.ts
Normal file
15
negodata/front/src/api/generated/model/resEnums.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { ErrorInfo } from "./errorInfo";
|
||||
import type { ResEnumsMsg } from "./resEnumsMsg";
|
||||
import type { ResEnumsEnums } from "./resEnumsEnums";
|
||||
|
||||
export interface ResEnums {
|
||||
result?: ErrorInfo;
|
||||
msg?: ResEnumsMsg;
|
||||
enums?: ResEnumsEnums;
|
||||
}
|
||||
9
negodata/front/src/api/generated/model/resEnumsEnums.ts
Normal file
9
negodata/front/src/api/generated/model/resEnumsEnums.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { EnumOption } from "./enumOption";
|
||||
|
||||
export type ResEnumsEnums = { [key: string]: EnumOption[] };
|
||||
8
negodata/front/src/api/generated/model/resEnumsMsg.ts
Normal file
8
negodata/front/src/api/generated/model/resEnumsMsg.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ResEnumsMsg = string | null;
|
||||
15
negodata/front/src/api/generated/model/resItemCategories.ts
Normal file
15
negodata/front/src/api/generated/model/resItemCategories.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { ErrorInfo } from "./errorInfo";
|
||||
import type { ResItemCategoriesMsg } from "./resItemCategoriesMsg";
|
||||
import type { ItemCategory } from "./itemCategory";
|
||||
|
||||
export interface ResItemCategories {
|
||||
result?: ErrorInfo;
|
||||
msg?: ResItemCategoriesMsg;
|
||||
categories?: ItemCategory[];
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ResItemCategoriesMsg = string | null;
|
||||
@ -20,5 +20,6 @@ export interface ResMe {
|
||||
email?: ResMeEmail;
|
||||
contact_number?: ResMeContactNumber;
|
||||
role?: number;
|
||||
role_label?: string;
|
||||
company?: ResMeCompany;
|
||||
}
|
||||
|
||||
@ -4,6 +4,6 @@
|
||||
* Negodata Api Server
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { CompanyBrief } from "./companyBrief";
|
||||
import type { CompanyData } from "./companyData";
|
||||
|
||||
export type ResMeCompany = CompanyBrief | null;
|
||||
export type ResMeCompany = CompanyData | null;
|
||||
|
||||
@ -31,19 +31,15 @@ import type {
|
||||
|
||||
import { customFetch } from "../../mutator/custom-fetch";
|
||||
|
||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||
|
||||
/**
|
||||
* @summary 견적 설정 목록
|
||||
*/
|
||||
export const listSettings = (
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return customFetch<ResQuotationSettingList>(
|
||||
{ url: `/v1/quotation-setting/list`, method: "GET", signal },
|
||||
options,
|
||||
);
|
||||
export const listSettings = (signal?: AbortSignal) => {
|
||||
return customFetch<ResQuotationSettingList>({
|
||||
url: `/v1/quotation-setting/list`,
|
||||
method: "GET",
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListSettingsQueryKey = () => {
|
||||
@ -57,15 +53,14 @@ export const getListSettingsQueryOptions = <
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listSettings>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getListSettingsQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listSettings>>> = ({
|
||||
signal,
|
||||
}) => listSettings(requestOptions, signal);
|
||||
}) => listSettings(signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listSettings>>,
|
||||
@ -95,7 +90,6 @@ export function useListSettings<
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<TData, TError> & {
|
||||
@ -117,7 +111,6 @@ export function useListSettings<
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
@ -131,7 +124,6 @@ export function useListSettings<
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listSettings>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
@ -149,7 +141,6 @@ export function useListSettings<
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listSettings>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
@ -172,19 +163,15 @@ export function useListSettings<
|
||||
*/
|
||||
export const createSetting = (
|
||||
reqCreateQuotationSetting: ReqCreateQuotationSetting,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return customFetch<ResQuotationSetting>(
|
||||
{
|
||||
url: `/v1/quotation-setting/create`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqCreateQuotationSetting,
|
||||
signal,
|
||||
},
|
||||
options,
|
||||
);
|
||||
return customFetch<ResQuotationSetting>({
|
||||
url: `/v1/quotation-setting/create`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqCreateQuotationSetting,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateSettingMutationOptions = <
|
||||
@ -197,7 +184,6 @@ export const getCreateSettingMutationOptions = <
|
||||
{ data: ReqCreateQuotationSetting },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createSetting>>,
|
||||
TError,
|
||||
@ -205,13 +191,13 @@ export const getCreateSettingMutationOptions = <
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["createSetting"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof createSetting>>,
|
||||
@ -219,7 +205,7 @@ export const getCreateSettingMutationOptions = <
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return createSetting(data, requestOptions);
|
||||
return createSetting(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
@ -245,7 +231,6 @@ export const useCreateSetting = <
|
||||
{ data: ReqCreateQuotationSetting },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
@ -264,17 +249,13 @@ export const useCreateSetting = <
|
||||
export const updateSetting = (
|
||||
qtSettingId: string,
|
||||
reqUpdateQuotationSetting: ReqUpdateQuotationSetting,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
) => {
|
||||
return customFetch<ResQuotationSetting>(
|
||||
{
|
||||
url: `/v1/quotation-setting/update/${qtSettingId}`,
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqUpdateQuotationSetting,
|
||||
},
|
||||
options,
|
||||
);
|
||||
return customFetch<ResQuotationSetting>({
|
||||
url: `/v1/quotation-setting/update/${qtSettingId}`,
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqUpdateQuotationSetting,
|
||||
});
|
||||
};
|
||||
|
||||
export const getUpdateSettingMutationOptions = <
|
||||
@ -287,7 +268,6 @@ export const getUpdateSettingMutationOptions = <
|
||||
{ qtSettingId: string; data: ReqUpdateQuotationSetting },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateSetting>>,
|
||||
TError,
|
||||
@ -295,13 +275,13 @@ export const getUpdateSettingMutationOptions = <
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["updateSetting"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof updateSetting>>,
|
||||
@ -309,7 +289,7 @@ export const getUpdateSettingMutationOptions = <
|
||||
> = (props) => {
|
||||
const { qtSettingId, data } = props ?? {};
|
||||
|
||||
return updateSetting(qtSettingId, data, requestOptions);
|
||||
return updateSetting(qtSettingId, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
@ -335,7 +315,6 @@ export const useUpdateSetting = <
|
||||
{ qtSettingId: string; data: ReqUpdateQuotationSetting },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
@ -351,14 +330,11 @@ export const useUpdateSetting = <
|
||||
/**
|
||||
* @summary 견적 설정 삭제
|
||||
*/
|
||||
export const deleteSetting = (
|
||||
qtSettingId: string,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
) => {
|
||||
return customFetch<ResDeleteQuotationSetting>(
|
||||
{ url: `/v1/quotation-setting/delete/${qtSettingId}`, method: "DELETE" },
|
||||
options,
|
||||
);
|
||||
export const deleteSetting = (qtSettingId: string) => {
|
||||
return customFetch<ResDeleteQuotationSetting>({
|
||||
url: `/v1/quotation-setting/delete/${qtSettingId}`,
|
||||
method: "DELETE",
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeleteSettingMutationOptions = <
|
||||
@ -371,7 +347,6 @@ export const getDeleteSettingMutationOptions = <
|
||||
{ qtSettingId: string },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteSetting>>,
|
||||
TError,
|
||||
@ -379,13 +354,13 @@ export const getDeleteSettingMutationOptions = <
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["deleteSetting"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof deleteSetting>>,
|
||||
@ -393,7 +368,7 @@ export const getDeleteSettingMutationOptions = <
|
||||
> = (props) => {
|
||||
const { qtSettingId } = props ?? {};
|
||||
|
||||
return deleteSetting(qtSettingId, requestOptions);
|
||||
return deleteSetting(qtSettingId);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
@ -419,7 +394,6 @@ export const useDeleteSetting = <
|
||||
{ qtSettingId: string },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user