[refactor] negodata/front: 전역 타입 정리 — 공유(2+) 타입만 글로벌, 중복 제거

- src/types.ts: DB 미러 중복 제거, generated DTO 기반 별칭만(Product/Partner/NegotiationCard/PageType)
- products/partners 의 로컬 Product/Partner 중복 → 글로벌 단일 정본 재수출

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Mina Choi 2026-06-22 17:06:37 +09:00
parent eb6612af1c
commit ae103fd0f1
3 changed files with 27 additions and 315 deletions

View File

@ -1,11 +1,4 @@
import type { SupplierData } from '@/api/generated/model/supplierData';
// UI에서 쓰는 협력사 타입. 서버 SupplierData에 화면 전용 파생 필드만 얹는다.
// (level/rank/status 등 서버 미연동 가짜 필드는 두지 않는다 — 표시 가능한 건 priority뿐.)
export type Partner = SupplierData & {
deleted?: boolean;
id?: string;
};
export type { Partner } from '@/types';
// 우선순위 필터 목록. 'ALL'은 필터 전용(폼에서는 제외).
export const prioritiesList = ['ALL', 'HIGH', 'MEDIUM', 'LOW'];

View File

@ -1,14 +1,4 @@
import type { ItemData } from '@/api/generated/model/itemData';
// UI에서 쓰는 상품 타입. 서버 ItemData에 화면 전용 파생 필드를 얹는다.
export type Product = ItemData & {
deleted?: boolean;
id?: string;
minPrice?: number;
status?: string;
};
// 분류 카테고리는 서버 items 에서 distinct 로 파생한다(useProducts). 하드코딩 상수 제거됨.
export type { Product } from '@/types';
// 인터넷 최저가 데모 산정(표준 단가의 83%). 서버 미연동 — 표시/초기값 용도.
export const toMinPrice = (price?: number | null) => Math.round((price || 0) * 0.83);

View File

@ -1,306 +1,35 @@
// ============================================================
// Negosium/NegoData ERD Type Definitions (TypeScript)
// ------------------------------------------------------------
// 기준: postgres-init/01-schema.sql (negosium_db, 도메인별 schema) + 현행 백엔드 API 계약.
// 표기 규칙:
// - 코드값(status/role/type/quantity_unit/delivery_type 등)은 DB에서 SMALLINT 정수코드지만,
// 현행 API(openapi.json) 가 문자열로 직렬화하므로 여기서는 string 으로 둔다(주석에 DB 타입 명시).
// - DB 에 대응 테이블/컬럼이 없는 항목은 "⚠ DB 없음" 으로 표시한다.
// ============================================================
// 2개 이상 feature가 공유하는 타입만 둔다. 단일 feature 전용은 그 feature/types.ts 로.
import type { ItemData } from '@/api/generated/model/itemData';
import type { SupplierData } from '@/api/generated/model/supplierData';
// Base abstract structure properties
interface BaseEntity {
created_at: string;
updated_at: string;
deleted: boolean;
}
// 1. Companies (고객사) — company.companies
export interface Company extends BaseEntity {
company_id: string; // uuid (Primary Key)
name: string; // 회사명
business_number: string | null; // 사업자등록번호
code: number | null; // 회사코드 (내부 인덱스용)
representative_name: string | null; // 대표자 명
email: string | null; // 대표 이메일
contact_number: string | null; // 대표 연락처
website_url: string | null; // 홈페이지 URL
industry: string | null; // 업종 (DB: SMALLINT 코드)
status: string; // 상태 (DB: SMALLINT NOT NULL, 1=active 2=inactive)
}
// 2. Users (유저) — company.users
export interface User extends BaseEntity {
user_id: string; // uuid (Primary Key)
company_id: string; // 회사 아이디 (company.companies.company_id)
id: string; // 로그인시, 입력 아이디
password: string; // 비밀번호 (해시)
name: string | null; // 이름
email: string | null; // 이메일
contact_number: string | null; // 전화번호
last_accessed_at: string; // 마지막 접속 시간
status: string; // 상태 (DB: SMALLINT, 1=active 2=inactive)
role: string; // 권한 (DB: SMALLINT, 1=user 2=manager)
}
// 3. User Tokens (유저 토큰) — company.user_tokens
export interface UserToken extends BaseEntity {
user_tokens_id: string; // uuid (Primary Key) — DB 컬럼명 user_tokens_id
user_id: string; // 유저 아이디 (company.users.user_id)
type: string; // 토큰 타입 (DB: SMALLINT 코드)
token: any; // 토큰 값 (JSONB)
issued_at: string;
expired_at: string;
}
// 4. Suppliers (협력사) - 기존 Partner 대응 — partner.suppliers
export interface Supplier extends BaseEntity {
supplier_id: string; // uuid (Primary Key)
company_id: string; // 회사 아이디 (company.companies.company_id)
user_id: string; // 등록 유저 (company.users.user_id) — DB NOT NULL
name: string; // 협력사명
code: string | null; // 협력사코드
manager_name: string | null; // 담당자명
manager_email: string | null; // 담당자 이메일
manager_contact_number: string | null; // 담당자 연락처 (DB 철자 정상 — 기존 typo 가정은 오류였음)
priority: string | null; // 우선순위 (DB: VARCHAR, 고객사별 문자열 유지)
}
// 5. Items (상품 정보) - 기존 Product 대응 — partner.items
export interface Item extends BaseEntity {
item_id: string; // uuid (Primary Key)
company_id: string; // 회사 아이디 (company.companies.company_id)
user_id: string; // 등록 유저 (company.users.user_id) — DB NOT NULL
name: string; // 상품명
code: string | null; // 상품코드
price: number | null; // 상품 단가 (DB: BIGINT)
category: string | null; // 상품 카테고리 (free text)
category_type: number; // 카테고리 조회용 정수 (DB: INTEGER NOT NULL DEFAULT 1, 자동증가 아님)
image_url: string | null; // 상품 이미지 URL
model_name: string | null; // 상품 모델명
spec: string | null; // 상품 규격
moq: string | null; // 상품 MOQ
lead_time: number | null; // 상품 리드타임 (DB: SMALLINT)
manufacturer: string | null; // 상품 제조사
made_in: string | null; // 상품 제조 국가
quantity_unit: string | null; // 상품 취급 단위 (DB: SMALLINT 코드)
delivery_type: string | null; // 상품 배송형태 (DB: SMALLINT 코드)
vat_yn: boolean | null; // VAT 포함 여부
delivery_fee_yn: boolean | null; // 배송비 포함 여부
internet_lowest_price_yn: boolean; // 인터넷 최저가 보조 플래그
}
// 6. Item Internet Lowest Prices — partner.item_internet_lowest_prices
// (※ 기존 url/crawled_at 필드는 DB 에 없어 제거. 실제 DB 컬럼에 맞춤.)
export interface ItemInternetLowestPrice extends BaseEntity {
lp_id: string; // uuid (Primary Key)
item_id: string; // 상품 아이디 (partner.items.item_id)
lp_price: number | null; // 크롤링한 최저가 (DB: BIGINT)
website: number; // 크롤링 대상 사이트 (DB: SMALLINT 코드)
success_yn: boolean; // 크롤링 성공 여부
fail_reason: string | null; // 실패 사유
ai_model: number | null; // 사용한 AI 모델 (DB: SMALLINT 코드)
crawl_duration_ms: number | null; // 크롤링 소요 시간(ms)
crawl_end_time: string; // 크롤링 종료 시각
}
// 7. Quotation Settings (견적 세팅) — quotation.quotation_settings
// (※ DB·API 모두 user_id 보유. 전역 기본 설정은 NULL 가능.)
export interface QuotationSetting extends BaseEntity {
qt_setting_id: string; // uuid (Primary Key)
user_id: string; // 유저 아이디 (company.users.user_id). DB 는 nullable(전역 기본=NULL)이나 UI 매퍼가 '' 로 정규화
target_margin: string; // 목표 마진율 (DB: NUMERIC(8,6))
anchoring_value: string; // 앵커링 설정 값 (DB: NUMERIC(8,6))
card_use_count: string; // 협상 카드 사용 횟수 (DB: card_count INTEGER)
}
// 8. Quotations (견적) - 기존 Estimate 대응 — quotation.quotations
// (※ company_id 는 DB/API 모두에 없어 제거. 회사 스코프는 user_id 경유.)
export interface Quotation extends BaseEntity {
qt_id: string; // uuid (Primary Key)
user_id: string; // 유저 아이디 (company.users.user_id)
qt_setting_id: string; // 견적 세팅 아이디 (quotation.quotation_settings.qt_setting_id)
version_id: string; // 협상전략 버전 (card.versions.version_id) — DB NOT NULL
name: string; // 견적 명
number: string; // 견적 번호
type: string; // 견적 타입 (DB: SMALLINT, 1=renego 2=requote)
round: number; // 견적 차수 (기본 1)
status: string; // 견적 상태 (DB: SMALLINT 코드)
start_time: string; // 견적 시작 시간
end_time: string; // 견적 마감 시간
manager_name: string | null; // 견적 담당자 명
manager_email: string | null; // 견적 담당자 이메일
manager_contact_number: string | null; // 견적 담당자 연락처
memo: string | null; // 견적 안내사항
iteration: number; // 반복 횟수 (DB NOT NULL DEFAULT 0)
preferred_sp_yn: boolean | null; // 선호 공급사 지정 여부
preferred_sp_id: string | null; // 선호 공급사 (partner.suppliers.supplier_id)
preferred_sp_name: string | null; // 선호 공급사명(스냅샷)
equal_bid_yn: boolean | null; // 동일가 입찰 발생 여부
equal_bid_data: any | null; // 동일가 입찰 상세 (JSONB)
}
// 9. Nego Cards (협상카드) — card.nego_cards
// (※ company_id 는 DB 에 없어 제거. 소유는 user_id(nullable) 만.)
export interface NegoCard extends BaseEntity {
nego_card_id: string; // uuid (Primary Key)
user_id: string | null; // 유저 아이디 (o2o 기본 카드는 NULL)
name: string | null; // 카드 이름
number: string | null; // 카드 번호 (식별번호)
script: string | null; // 스크립트
edit_script: any | null; // 편집 스크립트 (JSONB)
}
// 10. Wild Cards (와일드 카드) — card.wild_cards
// (※ company_id 는 DB 에 없어 제거.)
export interface WildCard extends BaseEntity {
wild_card_id: string; // uuid (Primary Key)
user_id: string | null; // 유저 아이디 (o2o 기본 카드는 NULL)
name: string | null; // 카드 이름
number: string | null; // 카드 번호 (식별번호)
script: string | null; // 스크립트
edit_script: any | null; // 편집 스크립트 (JSONB)
condition: string | null; // 카드 사용 조건
available: boolean; // 협상 적용 가능 여부
memo: string | null; // 메모
}
// 11. Sessions (세션) - 기존 ChatSession 대응 — negotiation.sessions
// (※ user_id, bid_summary_id 는 DB sessions 에 없어 제거. bid_summary 개념은 quotations 로 흡수됨.)
export interface Session extends BaseEntity {
session_id: string; // uuid (Primary Key)
quotation_id: string; // 소속 견적 (quotation.quotations.qt_id) — DB 컬럼명 quotation_id
item_id: string; // 상품 아이디 (partner.items.item_id)
supplier_id: string; // 협력사 아이디 (partner.suppliers.supplier_id)
qt_number: string; // 견적번호(스냅샷)
qt_round: number; // 견적 차수(스냅샷)
qt_type: string; // 견적 타입(스냅샷, DB: SMALLINT)
target_price: number; // 목표 가격 (DB: BIGINT)
status: string; // 협상 상태 (DB: SMALLINT 코드)
bid_price: number | null; // 최종 입찰 가격
bid_at: string | null; // 최종 입찰 시간
end_time: string; // 협상 종료 시간
reject_reason: string | null; // 협상 거부 사유
reject_price: number | null; // 협상 거부 가격
reject_delivery_type: string | null; // 협상 거부 시 배송 형태 (DB: SMALLINT 코드)
}
// 12. Bid Summary (견적 입찰 정보)
// ⚠ DB 없음: 별도 테이블이 없고 quotation.quotations 의 preferred_sp_* / equal_bid_* 컬럼으로 흡수됨.
// UI(QuotationDetailSheet) 의 입찰 요약 표시용 파생 모델로만 존재.
export interface BidSummary {
bid_summary_id: string; // (파생) UI 식별자
qt_id: string; // 견적 아이디 (quotation.quotations.qt_id)
qt_type: string; // 견적 타입 (재협상/재견적)
qt_iteration: number; // 견적 반복횟수 (← quotations.iteration)
status: string; // 입찰 상태 (← quotations.status)
has_preferred: boolean; // ← quotations.preferred_sp_yn
preferred_sp_id: string | null; // ← quotations.preferred_sp_id
preferred_sp_name: string | null; // ← quotations.preferred_sp_name
equal_data: any | null; // ← quotations.equal_bid_data (JSONB)
}
// 13. Results (세션 결과) — negotiation.results (스키마 미확정 스텁)
export interface Result extends BaseEntity {
result_id: string; // uuid (Primary Key) — DB 컬럼명 result_id
}
// 14. Quotation Cards (견적↔카드 연결)
// API(QuotationCardResponse) 로는 노출되나, 33KB DB 에는 quotation_cards 테이블이 없고
// card.version_nego_cards / card.version_wild_cards (버전↔카드) 매핑으로 실현된다
// (quotation.version_id → version_*_cards → nego/wild_cards).
export interface QuotationCard {
session_card_id: string; // uuid (Primary Key, API 기준)
wild_card_id: string | null; // 와일드 카드 아이디 (card.wild_cards.wild_card_id)
nego_card_id: string | null; // 협상카드 아이디 (card.nego_cards.nego_card_id)
qt_id: string | null; // 견적 아이디 (quotation.quotations.qt_id)
type: string | null; // 카드 타입 (DB: SMALLINT, 1=nego 2=wild)
}
// 15. Chats (채팅 내역) — negotiation.chats
// (※ API(ChatMessageResponse) 는 순번 컬럼을 index 로, DB 는 seq 로 부른다.)
export interface Chat extends BaseEntity {
chat_id: string; // uuid (Primary Key)
session_id: string; // 소속 세션 (negotiation.sessions.session_id)
card_id: string | null; // 사용된 카드 (card.nego_cards/wild_cards, 다형성)
seq: number; // 세션 내 메시지 순번 (API: index)
sender: string; // 발신자 구분 (DB: SMALLINT 코드)
target_price: number; // 제시 목표가 (DB: BIGINT)
card_used_yn: boolean | null; // 카드 사용 여부
indicator_value: number | null; // 지표값 (DB: NUMERIC(8,6))
card_type: string | null; // 카드 유형 (DB: SMALLINT, 1=nego 2=wild)
}
// 16. CopyOfChat (채팅 임시/백업)
// ⚠ DB 없음: 대응 테이블 없음 (프론트 임시 보관용).
export interface CopyOfChat extends BaseEntity {
id: string;
}
// ============================================================
// React UI Compatibility Helper Types
// ============================================================
export type Product = Partial<Item> & {
id?: string; // item_id back-compatibility map
minPrice?: number; // UI minimum reserve limit
status?: string; // UI lifecycle state
export type Product = ItemData & {
deleted?: boolean;
id?: string;
minPrice?: number;
status?: string;
};
export type Partner = Partial<Supplier> & {
id?: string; // supplier_id back-compatibility map
managerName?: string; // manager_name
managerEmail?: string; // manager_email
managerPhone?: string; // manager_contact_number
manager_contact_number?: string; // DB 컬럼 직접 매핑(철자 정상)
rank?: 'A' | 'B' | 'C' | 'S'; // computed from priority
status?: string; // mapped to deleted
memo?: string; // back-compatible details
export type Partner = SupplierData & {
deleted?: boolean;
id?: string;
managerName?: string;
managerEmail?: string;
managerPhone?: string;
rank?: 'A' | 'B' | 'C' | 'S';
status?: string;
memo?: string;
};
export type Estimate = Partial<Quotation> & {
id?: string; // qt_id back-compatibility map
dueDate?: string; // end_time
title?: string; // mapped to name in UI
productId?: string; // mapped to association
productName?: string; // 서버 목록 조인 상품명(products 목록에 없을 때 폴백)
partnerIds?: string[]; // mapped B2B suppliers
participationCount?: number;
winnerPartnerId?: string | null;
finalPrice?: number;
isEqualPrice?: boolean;
usedCardIds?: string[];
settingApplied?: boolean | string;
};
// UI Chat Message definition (corresponds to in-memory/rendered chats)
export interface ChatMessage {
id: string;
sender: 'BOT' | 'PARTNER' | 'SYSTEM';
timestamp: string;
content: string;
editorScript?: any; // Slate JSON structure
}
export interface ChatSession {
id: string; // matches supplier_id (or supplier.supplier_id)
partnerName: string;
status: 'NEGOTIATING' | 'COMPLETED' | 'REJECTED' | '협상생성' | '협상중' | '협상완료' | '미참여' | '협상거부';
currentBid: number;
bidTime: string;
messages: ChatMessage[];
}
export interface NegotiationCard {
id: string; // nego_card_id or wild_card_id
isWildcard: boolean; // mapping based on source table
code: string; // number (식별번호) or custom code
title: string; // name
scriptPreview: string; // script
editorScript: any; // edit_script (JSON)
id: string;
isWildcard: boolean;
code: string;
title: string;
scriptPreview: string;
editorScript: any;
status: 'ACTIVE' | 'INACTIVE';
triggerCondition?: string; // wild_card's condition
memo?: string; // wild_card's memo
triggerCondition?: string;
memo?: string;
}
export type PageType = 'PRODUCTS' | 'PARTNERS' | 'QUOTATION' | 'CARDS';