diff --git a/negodata/backend/common/enums.py b/negodata/backend/common/enums.py index abfb8e9..7c4d97c 100644 --- a/negodata/backend/common/enums.py +++ b/negodata/backend/common/enums.py @@ -3,6 +3,17 @@ from enum import Enum, auto from fastapi import HTTPException +class CodeEnum(Enum): + """OpenAPI 스키마에 x-enum-varnames(멤버 이름)을 실어 orval 이 이름 있는 enum 을 생성하게 하는 베이스.""" + + @classmethod + def __get_pydantic_json_schema__(cls, core_schema, handler): + json_schema = handler(core_schema) + json_schema = handler.resolve_ref_schema(json_schema) + json_schema["x-enum-varnames"] = [m.name for m in cls] + return json_schema + + class ErrorType(Enum): """서버 전역 결과 코드. Res_WebPacketProtocol.result 에 담겨 클라이언트로 전달된다. HTTP status 와 겹치지 않도록 구간을 분리해서 관리한다. @@ -84,35 +95,35 @@ class DBWRType(Enum): # 도메인 코드값 -class UserStatus(Enum): +class UserStatus(CodeEnum): """users.status 코드값.""" ACTIVE = 1 INACTIVE = 2 -class UserRole(Enum): +class UserRole(CodeEnum): """users.role 코드값.""" USER = 1 MANAGER = 2 -class CompanyStatus(Enum): +class CompanyStatus(CodeEnum): """companies.status 코드값.""" ACTIVE = 1 INACTIVE = 2 -class QuotationType(Enum): +class QuotationType(CodeEnum): """quotations.type 코드값. 1=renego(재협상 1:1), 2=requote(재견적 1:N).""" RENEGO = 1 REQUOTE = 2 -class QuotationStatus(Enum): +class QuotationStatus(CodeEnum): """quotations.status 코드값(SMALLINT). 프론트 견적상태 뱃지와 매핑된다.""" CREATED = 1 @@ -121,7 +132,7 @@ class QuotationStatus(Enum): ON_HOLD = 4 -class SessionStatus(Enum): +class SessionStatus(CodeEnum): """negotiation.sessions.status 코드값. 협력사별 협상 세션 진행 상태.""" CREATED = 1 @@ -131,14 +142,14 @@ class SessionStatus(Enum): REJECTED = 5 -class ChatSender(Enum): +class ChatSender(CodeEnum): """negotiation.chats.sender 코드값. 채팅 발신 주체.""" BOT = 1 USER = 2 -class DeliveryType(Enum): +class DeliveryType(CodeEnum): """items.delivery_type 코드값. 협상 채팅의 배송형태 선택지와 동일 집합.""" PARTNER = 1 # 협력사배송 @@ -146,50 +157,15 @@ class DeliveryType(Enum): PICKUP = 3 # 픽업배송 -class CardStatus(Enum): +class CardStatus(CodeEnum): """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.CREATED: "협상생성", - SessionStatus.IN_PROGRESS: "협상중", - SessionStatus.DONE: "협상완료", - SessionStatus.NOT_PARTICIPATED: "미참여", - SessionStatus.REJECTED: "협상거부", - ChatSender.BOT: "봇", - ChatSender.USER: "협력사", - DeliveryType.PARTNER: "협력사배송", - DeliveryType.COURIER: "지정택배배송", - DeliveryType.PICKUP: "픽업배송", - CardStatus.ACTIVE: "적용", - CardStatus.INACTIVE: "대기", -} +class CardType(CodeEnum): + """negotiation.chats.card_type / quotation_cards.type 코드값. 1=nego_card, 2=wild_card.""" -# 프론트로 내려주는 도메인 코드 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, -} + NEGO = 1 + WILD = 2 diff --git a/negodata/backend/router/router.py b/negodata/backend/router/router.py index 19ce6b0..035373e 100644 --- a/negodata/backend/router/router.py +++ b/negodata/backend/router/router.py @@ -9,22 +9,24 @@ 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 +from scheduler import shutdown_scheduler, start_scheduler 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() @asynccontextmanager async def lifespan(app: FastAPI): - # startup + # startup: 마감 스케줄러 기동(SCHEDULER_ENABLED=1 인 프로세스에서만) + start_scheduler() yield - # shutdown: DB 엔진 커넥션 풀 정리 + # shutdown: 스케줄러 정지 + DB 엔진 커넥션 풀 정리 + shutdown_scheduler() await DB_SESSION_MNG.dispose_all() @@ -64,4 +66,3 @@ 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) diff --git a/negodata/backend/router/v1/auth/protocol.py b/negodata/backend/router/v1/auth/protocol.py index 983ed8d..f07c0d0 100644 --- a/negodata/backend/router/v1/auth/protocol.py +++ b/negodata/backend/router/v1/auth/protocol.py @@ -52,6 +52,5 @@ class Res_Me(Res_WebPacketProtocol): name: Optional[str] = None email: Optional[str] = None contact_number: Optional[str] = None - role: int = UserRole.USER.value - role_label: str = "" + role: UserRole = UserRole.USER company: Optional[CompanyData] = Field(default=None) diff --git a/negodata/backend/router/v1/card/protocol.py b/negodata/backend/router/v1/card/protocol.py index 6af1939..2bf3e19 100644 --- a/negodata/backend/router/v1/card/protocol.py +++ b/negodata/backend/router/v1/card/protocol.py @@ -44,7 +44,7 @@ class CardData(WebPacketProtocol): number: Optional[str] = None script: Optional[str] = None edit_script: Optional[Any] = None - status: int = CardStatus.ACTIVE.value + status: CardStatus = CardStatus.ACTIVE condition: Optional[str] = None memo: Optional[str] = None created_at: Optional[datetime] = None diff --git a/negodata/backend/router/v1/enums/enums.py b/negodata/backend/router/v1/enums/enums.py deleted file mode 100644 index 07b45bf..0000000 --- a/negodata/backend/router/v1/enums/enums.py +++ /dev/null @@ -1,21 +0,0 @@ -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) diff --git a/negodata/backend/router/v1/enums/protocol.py b/negodata/backend/router/v1/enums/protocol.py deleted file mode 100644 index 044351f..0000000 --- a/negodata/backend/router/v1/enums/protocol.py +++ /dev/null @@ -1,15 +0,0 @@ -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]] = {} diff --git a/negodata/backend/router/v1/item/protocol.py b/negodata/backend/router/v1/item/protocol.py index dc973a9..1ef449c 100644 --- a/negodata/backend/router/v1/item/protocol.py +++ b/negodata/backend/router/v1/item/protocol.py @@ -4,6 +4,7 @@ from typing import Optional from pydantic import ConfigDict +from common.enums import DeliveryType from common.models.gmodel import Res_PageProtocol, Res_WebPacketProtocol, WebPacketProtocol @@ -71,7 +72,7 @@ class ItemData(WebPacketProtocol): moq: Optional[str] = None lead_time: Optional[int] = None quantity_unit: Optional[str] = None - delivery_type: Optional[int] = None + delivery_type: Optional[DeliveryType] = None vat_yn: Optional[bool] = None delivery_fee_yn: Optional[bool] = None created_at: Optional[datetime] = None diff --git a/negodata/backend/router/v1/quotation/protocol.py b/negodata/backend/router/v1/quotation/protocol.py index 3c53e0d..d72172f 100644 --- a/negodata/backend/router/v1/quotation/protocol.py +++ b/negodata/backend/router/v1/quotation/protocol.py @@ -4,6 +4,7 @@ from typing import Any, Optional from pydantic import ConfigDict +from common.enums import CardType, ChatSender, DeliveryType, QuotationStatus, QuotationType, SessionStatus from common.models.gmodel import Res_PageProtocol, Res_WebPacketProtocol, WebPacketProtocol @@ -39,9 +40,9 @@ class QuotationData(WebPacketProtocol): version_id: uuid.UUID name: str number: str - type: int + type: QuotationType round: int = 1 - status: int + status: QuotationStatus start_time: datetime end_time: datetime manager_name: Optional[str] = None @@ -82,15 +83,15 @@ class SessionData(WebPacketProtocol): item_id: uuid.UUID qt_number: str qt_round: int - qt_type: int + qt_type: QuotationType target_price: int - status: int + status: SessionStatus 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 + reject_delivery_type: Optional[DeliveryType] = None url: str = "" # 세션 chat 실행 URL(공급사 협상 프론트). DB 미저장 — session_id 로 구성 @@ -124,11 +125,13 @@ class ChatMessageData(WebPacketProtocol): session_id: uuid.UUID card_id: Optional[uuid.UUID] = None index: int - sender: int + sender: ChatSender target_price: int card_used_yn: Optional[bool] = None indicator_value: Optional[float] = None - card_type: Optional[int] = None + card_type: Optional[CardType] = None + script: Optional[str] = None + step: Optional[str] = None class Res_SessionChat(Res_WebPacketProtocol): @@ -150,7 +153,7 @@ class QuotationCardData(WebPacketProtocol): qt_id: Optional[uuid.UUID] = None nego_card_id: Optional[uuid.UUID] = None wild_card_id: Optional[uuid.UUID] = None - type: Optional[int] = None + type: Optional[CardType] = None number: Optional[str] = None name: Optional[str] = None script: Optional[str] = None # 협상 멘트(평문) diff --git a/negodata/backend/services/auth_service.py b/negodata/backend/services/auth_service.py index 32536a1..d3c39af 100644 --- a/negodata/backend/services/auth_service.py +++ b/negodata/backend/services/auth_service.py @@ -4,7 +4,7 @@ from fastapi import Depends from common.database.db_session_manager import DB_SESSION_MNG from common.database.model.models import users -from common.enums import DBWRType, ErrorType, UserStatus, UserRole, ENUM_LABELS +from common.enums import DBWRType, ErrorType, UserStatus, UserRole from common.logger import LOG from common.models.gmodel import UserInfo from crud.user_crud import IUserCRUD, UserCRUD @@ -156,7 +156,6 @@ class AuthService: 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 diff --git a/negodata/front/src/api/generated/enums/enums.ts b/negodata/front/src/api/generated/enums/enums.ts deleted file mode 100644 index fb3e5ee..0000000 --- a/negodata/front/src/api/generated/enums/enums.ts +++ /dev/null @@ -1,124 +0,0 @@ -/** - * 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'; - - -type SecondParameter unknown> = Parameters[1]; - - - -/** - * @summary 도메인 코드 enum 전체 - */ -export const listEnums = ( - - options?: SecondParameter,signal?: AbortSignal -) => { - - - return customFetch( - {url: `/v1/enums`, method: 'GET', signal - }, - options); - } - - - - -export const getListEnumsQueryKey = () => { - return [ - `/v1/enums` - ] as const; - } - - -export const getListEnumsQueryOptions = >, TError = void>( options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} -) => { - -const {query: queryOptions, request: requestOptions} = options ?? {}; - - const queryKey = queryOptions?.queryKey ?? getListEnumsQueryKey(); - - - - const queryFn: QueryFunction>> = ({ signal }) => listEnums(requestOptions, signal); - - - - - - return { queryKey, queryFn, ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: DataTag } -} - -export type ListEnumsQueryResult = NonNullable>> -export type ListEnumsQueryError = void - - -export function useListEnums>, TError = void>( - options: { query:Partial>, TError, TData>> & Pick< - DefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - > , 'initialData' - >, request?: SecondParameter} - , queryClient?: QueryClient - ): DefinedUseQueryResult & { queryKey: DataTag } -export function useListEnums>, TError = void>( - options?: { query?:Partial>, TError, TData>> & Pick< - UndefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - > , 'initialData' - >, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } -export function useListEnums>, TError = void>( - options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } -/** - * @summary 도메인 코드 enum 전체 - */ - -export function useListEnums>, TError = void>( - options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } { - - const queryOptions = getListEnumsQueryOptions(options) - - const query = useQuery(queryOptions, queryClient) as UseQueryResult & { queryKey: DataTag }; - - query.queryKey = queryOptions.queryKey ; - - return query; -} - - - - diff --git a/negodata/front/src/api/generated/model/cardData.ts b/negodata/front/src/api/generated/model/cardData.ts index 797f7c0..c39fba8 100644 --- a/negodata/front/src/api/generated/model/cardData.ts +++ b/negodata/front/src/api/generated/model/cardData.ts @@ -9,6 +9,7 @@ import type { CardDataName } from './cardDataName'; import type { CardDataNumber } from './cardDataNumber'; import type { CardDataScript } from './cardDataScript'; import type { CardDataEditScript } from './cardDataEditScript'; +import type { CardStatus } from './cardStatus'; import type { CardDataCondition } from './cardDataCondition'; import type { CardDataMemo } from './cardDataMemo'; import type { CardDataCreatedAt } from './cardDataCreatedAt'; @@ -22,7 +23,7 @@ export interface CardData { number?: CardDataNumber; script?: CardDataScript; edit_script?: CardDataEditScript; - status?: number; + status?: CardStatus; condition?: CardDataCondition; memo?: CardDataMemo; created_at?: CardDataCreatedAt; diff --git a/negodata/front/src/api/generated/model/cardStatus.ts b/negodata/front/src/api/generated/model/cardStatus.ts new file mode 100644 index 0000000..3b1a68a --- /dev/null +++ b/negodata/front/src/api/generated/model/cardStatus.ts @@ -0,0 +1,18 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * Negodata Api Server + * OpenAPI spec version: 0.1.0 + */ + +/** + * nego_cards.status 코드값. 와일드카드의 협상 적용 여부(수동 승인). 일반 협상카드는 상시 ACTIVE. + */ +export type CardStatus = typeof CardStatus[keyof typeof CardStatus]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const CardStatus = { + ACTIVE: 1, + INACTIVE: 2, +} as const; diff --git a/negodata/front/src/api/generated/model/cardType.ts b/negodata/front/src/api/generated/model/cardType.ts new file mode 100644 index 0000000..2958edf --- /dev/null +++ b/negodata/front/src/api/generated/model/cardType.ts @@ -0,0 +1,18 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * Negodata Api Server + * OpenAPI spec version: 0.1.0 + */ + +/** + * negotiation.chats.card_type / quotation_cards.type 코드값. 1=nego_card, 2=wild_card. + */ +export type CardType = typeof CardType[keyof typeof CardType]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const CardType = { + NEGO: 1, + WILD: 2, +} as const; diff --git a/negodata/front/src/api/generated/model/chatMessageData.ts b/negodata/front/src/api/generated/model/chatMessageData.ts index 3b484ae..8183307 100644 --- a/negodata/front/src/api/generated/model/chatMessageData.ts +++ b/negodata/front/src/api/generated/model/chatMessageData.ts @@ -5,18 +5,23 @@ * OpenAPI spec version: 0.1.0 */ import type { ChatMessageDataCardId } from './chatMessageDataCardId'; +import type { ChatSender } from './chatSender'; import type { ChatMessageDataCardUsedYn } from './chatMessageDataCardUsedYn'; import type { ChatMessageDataIndicatorValue } from './chatMessageDataIndicatorValue'; import type { ChatMessageDataCardType } from './chatMessageDataCardType'; +import type { ChatMessageDataScript } from './chatMessageDataScript'; +import type { ChatMessageDataStep } from './chatMessageDataStep'; export interface ChatMessageData { chat_id: string; session_id: string; card_id?: ChatMessageDataCardId; index: number; - sender: number; + sender: ChatSender; target_price: number; card_used_yn?: ChatMessageDataCardUsedYn; indicator_value?: ChatMessageDataIndicatorValue; card_type?: ChatMessageDataCardType; + script?: ChatMessageDataScript; + step?: ChatMessageDataStep; } diff --git a/negodata/front/src/api/generated/model/chatMessageDataCardType.ts b/negodata/front/src/api/generated/model/chatMessageDataCardType.ts index 5cdb07f..d037726 100644 --- a/negodata/front/src/api/generated/model/chatMessageDataCardType.ts +++ b/negodata/front/src/api/generated/model/chatMessageDataCardType.ts @@ -4,5 +4,6 @@ * Negodata Api Server * OpenAPI spec version: 0.1.0 */ +import type { CardType } from './cardType'; -export type ChatMessageDataCardType = number | null; +export type ChatMessageDataCardType = CardType | null; diff --git a/negodata/front/src/api/generated/model/enumOption.ts b/negodata/front/src/api/generated/model/chatMessageDataScript.ts similarity index 60% rename from negodata/front/src/api/generated/model/enumOption.ts rename to negodata/front/src/api/generated/model/chatMessageDataScript.ts index 3b1b7e1..de3a462 100644 --- a/negodata/front/src/api/generated/model/enumOption.ts +++ b/negodata/front/src/api/generated/model/chatMessageDataScript.ts @@ -5,8 +5,4 @@ * OpenAPI spec version: 0.1.0 */ -export interface EnumOption { - value: number; - name: string; - label: string; -} +export type ChatMessageDataScript = string | null; diff --git a/negodata/front/src/api/generated/model/resEnumsMsg.ts b/negodata/front/src/api/generated/model/chatMessageDataStep.ts similarity index 71% rename from negodata/front/src/api/generated/model/resEnumsMsg.ts rename to negodata/front/src/api/generated/model/chatMessageDataStep.ts index f2ddea5..1e26300 100644 --- a/negodata/front/src/api/generated/model/resEnumsMsg.ts +++ b/negodata/front/src/api/generated/model/chatMessageDataStep.ts @@ -5,4 +5,4 @@ * OpenAPI spec version: 0.1.0 */ -export type ResEnumsMsg = string | null; +export type ChatMessageDataStep = string | null; diff --git a/negodata/front/src/api/generated/model/chatSender.ts b/negodata/front/src/api/generated/model/chatSender.ts new file mode 100644 index 0000000..a985499 --- /dev/null +++ b/negodata/front/src/api/generated/model/chatSender.ts @@ -0,0 +1,18 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * Negodata Api Server + * OpenAPI spec version: 0.1.0 + */ + +/** + * negotiation.chats.sender 코드값. 채팅 발신 주체. + */ +export type ChatSender = typeof ChatSender[keyof typeof ChatSender]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const ChatSender = { + BOT: 1, + USER: 2, +} as const; diff --git a/negodata/front/src/api/generated/model/deliveryType.ts b/negodata/front/src/api/generated/model/deliveryType.ts new file mode 100644 index 0000000..2f51973 --- /dev/null +++ b/negodata/front/src/api/generated/model/deliveryType.ts @@ -0,0 +1,19 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * Negodata Api Server + * OpenAPI spec version: 0.1.0 + */ + +/** + * items.delivery_type 코드값. 협상 채팅의 배송형태 선택지와 동일 집합. + */ +export type DeliveryType = typeof DeliveryType[keyof typeof DeliveryType]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const DeliveryType = { + PARTNER: 1, + COURIER: 2, + PICKUP: 3, +} as const; diff --git a/negodata/front/src/api/generated/model/index.ts b/negodata/front/src/api/generated/model/index.ts index 92ade6b..987f649 100644 --- a/negodata/front/src/api/generated/model/index.ts +++ b/negodata/front/src/api/generated/model/index.ts @@ -17,13 +17,18 @@ export * from './cardDataNumber'; export * from './cardDataScript'; export * from './cardDataUpdatedAt'; export * from './cardDataUserId'; +export * from './cardStatus'; +export * from './cardType'; export * from './chatMessageData'; export * from './chatMessageDataCardId'; export * from './chatMessageDataCardType'; export * from './chatMessageDataCardUsedYn'; export * from './chatMessageDataIndicatorValue'; +export * from './chatMessageDataScript'; +export * from './chatMessageDataStep'; +export * from './chatSender'; export * from './companyData'; -export * from './enumOption'; +export * from './deliveryType'; export * from './errorInfo'; export * from './errorInfoCode'; export * from './errorInfoDesc'; @@ -80,6 +85,8 @@ export * from './quotationSettingData'; export * from './quotationSettingDataCreatedAt'; export * from './quotationSettingDataUpdatedAt'; export * from './quotationSettingDataUserId'; +export * from './quotationStatus'; +export * from './quotationType'; export * from './reqCheckCodes'; export * from './reqCreateAccount'; export * from './reqCreateCard'; @@ -179,9 +186,6 @@ export * from './resDeleteQuotationSetting'; export * from './resDeleteQuotationSettingMsg'; export * from './resDeleteSupplier'; export * from './resDeleteSupplierMsg'; -export * from './resEnums'; -export * from './resEnumsEnums'; -export * from './resEnumsMsg'; export * from './resItem'; export * from './resItemCategories'; export * from './resItemCategoriesMsg'; @@ -248,6 +252,7 @@ export * from './sessionDataBidPrice'; export * from './sessionDataRejectDeliveryType'; export * from './sessionDataRejectPrice'; export * from './sessionDataRejectReason'; +export * from './sessionStatus'; export * from './supplierData'; export * from './supplierDataCode'; export * from './supplierDataCreatedAt'; @@ -256,6 +261,7 @@ export * from './supplierDataManagerEmail'; export * from './supplierDataManagerName'; export * from './supplierDataPriority'; export * from './supplierDataUpdatedAt'; +export * from './userRole'; export * from './validationError'; export * from './validationErrorCtx'; export * from './validationErrorLocItem'; \ No newline at end of file diff --git a/negodata/front/src/api/generated/model/itemDataDeliveryType.ts b/negodata/front/src/api/generated/model/itemDataDeliveryType.ts index df68667..e3e57c3 100644 --- a/negodata/front/src/api/generated/model/itemDataDeliveryType.ts +++ b/negodata/front/src/api/generated/model/itemDataDeliveryType.ts @@ -4,5 +4,6 @@ * Negodata Api Server * OpenAPI spec version: 0.1.0 */ +import type { DeliveryType } from './deliveryType'; -export type ItemDataDeliveryType = number | null; +export type ItemDataDeliveryType = DeliveryType | null; diff --git a/negodata/front/src/api/generated/model/quotationCardDataType.ts b/negodata/front/src/api/generated/model/quotationCardDataType.ts index b9b65d9..f0a7c0c 100644 --- a/negodata/front/src/api/generated/model/quotationCardDataType.ts +++ b/negodata/front/src/api/generated/model/quotationCardDataType.ts @@ -4,5 +4,6 @@ * Negodata Api Server * OpenAPI spec version: 0.1.0 */ +import type { CardType } from './cardType'; -export type QuotationCardDataType = number | null; +export type QuotationCardDataType = CardType | null; diff --git a/negodata/front/src/api/generated/model/quotationData.ts b/negodata/front/src/api/generated/model/quotationData.ts index 0665b08..ae7106b 100644 --- a/negodata/front/src/api/generated/model/quotationData.ts +++ b/negodata/front/src/api/generated/model/quotationData.ts @@ -4,6 +4,8 @@ * Negodata Api Server * OpenAPI spec version: 0.1.0 */ +import type { QuotationType } from './quotationType'; +import type { QuotationStatus } from './quotationStatus'; import type { QuotationDataManagerName } from './quotationDataManagerName'; import type { QuotationDataManagerEmail } from './quotationDataManagerEmail'; import type { QuotationDataManagerContactNumber } from './quotationDataManagerContactNumber'; @@ -25,9 +27,9 @@ export interface QuotationData { version_id: string; name: string; number: string; - type: number; + type: QuotationType; round?: number; - status: number; + status: QuotationStatus; start_time: string; end_time: string; manager_name?: QuotationDataManagerName; diff --git a/negodata/front/src/api/generated/model/quotationStatus.ts b/negodata/front/src/api/generated/model/quotationStatus.ts new file mode 100644 index 0000000..1758d54 --- /dev/null +++ b/negodata/front/src/api/generated/model/quotationStatus.ts @@ -0,0 +1,20 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * Negodata Api Server + * OpenAPI spec version: 0.1.0 + */ + +/** + * quotations.status 코드값(SMALLINT). 프론트 견적상태 뱃지와 매핑된다. + */ +export type QuotationStatus = typeof QuotationStatus[keyof typeof QuotationStatus]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const QuotationStatus = { + CREATED: 1, + ACTIVE: 2, + CLOSED: 3, + ON_HOLD: 4, +} as const; diff --git a/negodata/front/src/api/generated/model/quotationType.ts b/negodata/front/src/api/generated/model/quotationType.ts new file mode 100644 index 0000000..87f9dce --- /dev/null +++ b/negodata/front/src/api/generated/model/quotationType.ts @@ -0,0 +1,18 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * Negodata Api Server + * OpenAPI spec version: 0.1.0 + */ + +/** + * quotations.type 코드값. 1=renego(재협상 1:1), 2=requote(재견적 1:N). + */ +export type QuotationType = typeof QuotationType[keyof typeof QuotationType]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const QuotationType = { + RENEGO: 1, + REQUOTE: 2, +} as const; diff --git a/negodata/front/src/api/generated/model/resCardList.ts b/negodata/front/src/api/generated/model/resCardList.ts index ee8f869..90625fd 100644 --- a/negodata/front/src/api/generated/model/resCardList.ts +++ b/negodata/front/src/api/generated/model/resCardList.ts @@ -15,8 +15,6 @@ export interface ResCardList { page?: number; size?: number; cards?: CardData[]; - /** 협상카드 탭 카운트(검색 필터 반영) */ total_nego?: number; - /** 와일드카드 탭 카운트(검색 필터 반영) */ total_wild?: number; } diff --git a/negodata/front/src/api/generated/model/resEnums.ts b/negodata/front/src/api/generated/model/resEnums.ts deleted file mode 100644 index e234dec..0000000 --- a/negodata/front/src/api/generated/model/resEnums.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * 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; -} diff --git a/negodata/front/src/api/generated/model/resEnumsEnums.ts b/negodata/front/src/api/generated/model/resEnumsEnums.ts deleted file mode 100644 index 2afe996..0000000 --- a/negodata/front/src/api/generated/model/resEnumsEnums.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * 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[]}; diff --git a/negodata/front/src/api/generated/model/resMe.ts b/negodata/front/src/api/generated/model/resMe.ts index 291260f..226eea7 100644 --- a/negodata/front/src/api/generated/model/resMe.ts +++ b/negodata/front/src/api/generated/model/resMe.ts @@ -9,6 +9,7 @@ import type { ResMeMsg } from './resMeMsg'; import type { ResMeName } from './resMeName'; import type { ResMeEmail } from './resMeEmail'; import type { ResMeContactNumber } from './resMeContactNumber'; +import type { UserRole } from './userRole'; import type { ResMeCompany } from './resMeCompany'; export interface ResMe { @@ -19,7 +20,6 @@ export interface ResMe { name?: ResMeName; email?: ResMeEmail; contact_number?: ResMeContactNumber; - role?: number; - role_label?: string; + role?: UserRole; company?: ResMeCompany; } diff --git a/negodata/front/src/api/generated/model/sessionData.ts b/negodata/front/src/api/generated/model/sessionData.ts index 04b2986..b48fec3 100644 --- a/negodata/front/src/api/generated/model/sessionData.ts +++ b/negodata/front/src/api/generated/model/sessionData.ts @@ -4,6 +4,8 @@ * Negodata Api Server * OpenAPI spec version: 0.1.0 */ +import type { QuotationType } from './quotationType'; +import type { SessionStatus } from './sessionStatus'; import type { SessionDataBidPrice } from './sessionDataBidPrice'; import type { SessionDataBidAt } from './sessionDataBidAt'; import type { SessionDataRejectReason } from './sessionDataRejectReason'; @@ -17,9 +19,9 @@ export interface SessionData { item_id: string; qt_number: string; qt_round: number; - qt_type: number; + qt_type: QuotationType; target_price: number; - status: number; + status: SessionStatus; bid_price?: SessionDataBidPrice; bid_at?: SessionDataBidAt; end_time: string; diff --git a/negodata/front/src/api/generated/model/sessionDataRejectDeliveryType.ts b/negodata/front/src/api/generated/model/sessionDataRejectDeliveryType.ts index 475c6e0..90c8920 100644 --- a/negodata/front/src/api/generated/model/sessionDataRejectDeliveryType.ts +++ b/negodata/front/src/api/generated/model/sessionDataRejectDeliveryType.ts @@ -4,5 +4,6 @@ * Negodata Api Server * OpenAPI spec version: 0.1.0 */ +import type { DeliveryType } from './deliveryType'; -export type SessionDataRejectDeliveryType = number | null; +export type SessionDataRejectDeliveryType = DeliveryType | null; diff --git a/negodata/front/src/api/generated/model/sessionStatus.ts b/negodata/front/src/api/generated/model/sessionStatus.ts new file mode 100644 index 0000000..42fa328 --- /dev/null +++ b/negodata/front/src/api/generated/model/sessionStatus.ts @@ -0,0 +1,21 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * Negodata Api Server + * OpenAPI spec version: 0.1.0 + */ + +/** + * negotiation.sessions.status 코드값. 협력사별 협상 세션 진행 상태. + */ +export type SessionStatus = typeof SessionStatus[keyof typeof SessionStatus]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const SessionStatus = { + CREATED: 1, + IN_PROGRESS: 2, + DONE: 3, + NOT_PARTICIPATED: 4, + REJECTED: 5, +} as const; diff --git a/negodata/front/src/api/generated/model/userRole.ts b/negodata/front/src/api/generated/model/userRole.ts new file mode 100644 index 0000000..14c2610 --- /dev/null +++ b/negodata/front/src/api/generated/model/userRole.ts @@ -0,0 +1,18 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * Negodata Api Server + * OpenAPI spec version: 0.1.0 + */ + +/** + * users.role 코드값. + */ +export type UserRole = typeof UserRole[keyof typeof UserRole]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const UserRole = { + USER: 1, + MANAGER: 2, +} as const; diff --git a/negodata/front/src/features/auth/service.ts b/negodata/front/src/features/auth/service.ts index 005f408..4245c5c 100644 --- a/negodata/front/src/features/auth/service.ts +++ b/negodata/front/src/features/auth/service.ts @@ -7,6 +7,8 @@ import { import type {ResMe} from '../../api/generated/model/resMe'; import type {ErrorInfo} from '../../api/generated/model/errorInfo'; import {useAuthStore, type AuthUser, type UserRole} from '../../stores/auth'; +import {UserRole as UserRoleCode} from '../../api/generated/model'; +import {USER_ROLE_LABEL} from '../../lib/enumLabels'; const ACCESS_KEY = 'negodata.accessToken'; const REFRESH_KEY = 'negodata.refreshToken'; @@ -26,7 +28,7 @@ function toAuthUser(me: ResMe): AuthUser { loginId: me.id ?? '', email: me.email ?? '', contact: me.contact_number ?? '', - role: (me.role_label as UserRole) || '일반', + role: (USER_ROLE_LABEL[me.role ?? UserRoleCode.USER] ?? '일반') as UserRole, }; } diff --git a/negodata/front/src/features/cards/types.ts b/negodata/front/src/features/cards/types.ts index 173ed9f..19556c4 100644 --- a/negodata/front/src/features/cards/types.ts +++ b/negodata/front/src/features/cards/types.ts @@ -1,18 +1,16 @@ import type { NegotiationCard } from '@/types'; import type { CardData } from '@/api/generated/model/cardData'; +import { CardStatus } from '@/api/generated/model'; export type { NegotiationCard }; // 카드 목록 탭. 'ALL' 전체 / 'CARD' 일반 협상카드 / 'WILD' 와일드카드. export type CardTab = 'ALL' | 'CARD' | 'WILD'; -// 카드 status 코드(서버 CardStatus enum) ↔ UI 문자열. ACTIVE=1 / INACTIVE=2. -export const CARD_STATUS_ACTIVE = 1; -export const CARD_STATUS_INACTIVE = 2; export const toCardStatusCode = (s: 'ACTIVE' | 'INACTIVE') => - s === 'ACTIVE' ? CARD_STATUS_ACTIVE : CARD_STATUS_INACTIVE; + s === 'ACTIVE' ? CardStatus.ACTIVE : CardStatus.INACTIVE; export const toCardStatusLabel = (code?: number): 'ACTIVE' | 'INACTIVE' => - code === CARD_STATUS_INACTIVE ? 'INACTIVE' : 'ACTIVE'; + code === CardStatus.INACTIVE ? 'INACTIVE' : 'ACTIVE'; // 서버 CardData(nego_cards/wild_cards 통합) → UI NegotiationCard. export function mapCardData(c: CardData): NegotiationCard { diff --git a/negodata/front/src/features/products/components/ProductFormSheet.tsx b/negodata/front/src/features/products/components/ProductFormSheet.tsx index 33ce445..26b1746 100644 --- a/negodata/front/src/features/products/components/ProductFormSheet.tsx +++ b/negodata/front/src/features/products/components/ProductFormSheet.tsx @@ -3,7 +3,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import type { ReqCreateItem as ItemCreate } from '@/api/generated/model/reqCreateItem'; import type { ReqUpdateItem as ItemUpdate } from '@/api/generated/model/reqUpdateItem'; -import { useListEnums } from '@/api/generated/enums/enums'; +import { DELIVERY_TYPE_OPTIONS } from '@/lib/enumLabels'; import { uploadItemImage } from '@/api/generated/item/item'; import { showToast } from '@/lib/notify'; import ImageDropzone from '@/components/ImageDropzone'; @@ -121,9 +121,7 @@ export function ProductFormSheet({ defaultValues: buildDefaults(mode, product), }); - // 배송 형태 코드(delivery_type) 선택지는 서버 enum 에서 가져온다. - const { data: enumsData } = useListEnums(); - const deliveryTypes = enumsData?.enums?.delivery_type ?? []; + const deliveryTypes = DELIVERY_TYPE_OPTIONS; // minPrice는 화면 전용(서버 미전송). 검증된 값만 payload로. const onValid = async (v: FormValues) => { diff --git a/negodata/front/src/features/quotations/components/QuotationCreateModal.tsx b/negodata/front/src/features/quotations/components/QuotationCreateModal.tsx index d862174..4cc3db0 100644 --- a/negodata/front/src/features/quotations/components/QuotationCreateModal.tsx +++ b/negodata/front/src/features/quotations/components/QuotationCreateModal.tsx @@ -6,6 +6,8 @@ import { Input } from '@/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import type { Product, Partner, QuotationSetting, NegotiationCard } from '../types'; import type { CreateQuotationInput } from '../hooks/useQuotations'; +import { QuotationType } from '@/api/generated/model'; +import { QUOTATION_TYPE_OPTIONS } from '../types'; type QuotationCreateModalProps = { open: boolean; @@ -28,19 +30,22 @@ export function QuotationCreateModal({ }: QuotationCreateModalProps) { const [step, setStep] = useState(1); const [title, setTitle] = useState(''); - const [type, setType] = useState<'RE_NEGOTIATION' | 'RE_ESTIMATE'>('RE_NEGOTIATION'); + const [type, setType] = useState(QuotationType.REQUOTE); const [productId, setProductId] = useState(''); const [selectedPartnerIds, setSelectedPartnerIds] = useState([]); const [dueDate, setDueDate] = useState('2026-06-15T18:00'); const [settingId, setSettingId] = useState(quotationSettings[0]?.qt_setting_id ?? ''); const [selectedCardIds, setSelectedCardIds] = useState([]); const [submitting, setSubmitting] = useState(false); + const typeOptions = QUOTATION_TYPE_OPTIONS; if (!open) return null; const togglePartner = (id: string) => setSelectedPartnerIds((prev) => - prev.includes(id) ? prev.filter((p) => p !== id) : [...prev, id], + type === QuotationType.RENEGO + ? prev.includes(id) ? [] : [id] + : prev.includes(id) ? prev.filter((p) => p !== id) : [...prev, id], ); const toggleCard = (id: string) => setSelectedCardIds((prev) => @@ -73,8 +78,8 @@ export function QuotationCreateModal({
- 협상견적 생성 중… - 견적 · 협상 세션 등록 중 + 협상견적 생성 중… + 견적 · 협상 세션 등록 중
)} @@ -84,7 +89,7 @@ export function QuotationCreateModal({
- 신규 협상견적 등록 (단계 {step}/3) + 신규 협상견적 등록 (단계 {step}/3)
- {statusKey === '견적진행중' && ( - - )} + {/* 마감 버튼은 항상 노출하되, 마감 가능한 상태(생성·진행중·보류)가 아니면 비활성화만 한다. */} + {(() => { + const canClose = quotation.status !== QuotationStatus.CLOSED; + return ( + + ); + })()} } @@ -86,12 +88,18 @@ export default function QuotationPage() {
@@ -99,13 +107,18 @@ export default function QuotationPage() {
@@ -132,7 +145,7 @@ export default function QuotationPage() { )}