-
+
{item.itemCode}
- {item.itemName}
+ {item.itemName}
+ {item.hasInspectionTemplate && (
+
+ 수입검사
+
+ )}
{item.unit && (
diff --git a/src/components/quotes/QuoteFooterBar.tsx b/src/components/quotes/QuoteFooterBar.tsx
index da0319ad..a6dd9d66 100644
--- a/src/components/quotes/QuoteFooterBar.tsx
+++ b/src/components/quotes/QuoteFooterBar.tsx
@@ -19,7 +19,7 @@ import { Button } from "../ui/button";
interface QuoteFooterBarProps {
totalLocations: number;
totalAmount: number;
- status: "draft" | "temporary" | "final";
+ status: "draft" | "temporary" | "final" | "converted";
/** 견적서 보기 */
onQuoteView: () => void;
/** 거래명세서 보기 */
@@ -34,6 +34,10 @@ interface QuoteFooterBarProps {
onEdit?: () => void;
/** 수주등록 */
onOrderRegister?: () => void;
+ /** 수주 보기 (이미 수주가 있는 경우) */
+ onOrderView?: () => void;
+ /** 연결된 수주 ID (있으면 수주 보기, 없으면 수주등록) */
+ orderId?: number | null;
/** 할인하기 */
onDiscount?: () => void;
/** 수식보기 */
@@ -61,6 +65,8 @@ export function QuoteFooterBar({
onBack,
onEdit,
onOrderRegister,
+ onOrderView,
+ orderId,
onDiscount,
onFormulaView,
hasBomResult = false,
@@ -132,8 +138,8 @@ export function QuoteFooterBar({
)}
- {/* 수정 - view 모드에서만 표시 */}
- {isViewMode && onEdit && (
+ {/* 수정 - view 모드에서만 표시, 수주 등록된 경우 숨김 */}
+ {isViewMode && onEdit && !orderId && (
diff --git a/src/components/quotes/QuoteManagementClient.tsx b/src/components/quotes/QuoteManagementClient.tsx
index ee5efb5f..35f3a040 100644
--- a/src/components/quotes/QuoteManagementClient.tsx
+++ b/src/components/quotes/QuoteManagementClient.tsx
@@ -382,7 +382,7 @@ export function QuoteManagementClient({
전체
최초작성
N차수정
- 최종확정
+ 견적완료
diff --git a/src/components/quotes/QuoteRegistrationV2.tsx b/src/components/quotes/QuoteRegistrationV2.tsx
index 7cc5b506..833ec07c 100644
--- a/src/components/quotes/QuoteRegistrationV2.tsx
+++ b/src/components/quotes/QuoteRegistrationV2.tsx
@@ -95,10 +95,11 @@ export interface QuoteFormDataV2 {
contact: string; // 연락처
vatType: "included" | "excluded"; // 부가세 (포함/별도)
remarks: string; // 비고
- status: "draft" | "temporary" | "final"; // 작성중, 임시저장, 최종저장
+ status: "draft" | "temporary" | "final" | "converted"; // 작성중, 임시저장, 최종저장, 수주전환
discountRate: number; // 할인율 (%)
discountAmount: number; // 할인 금액
locations: LocationItem[];
+ orderId?: number | null; // 연결된 수주 ID (수주전환 시 설정)
}
// =============================================================================
@@ -151,6 +152,8 @@ interface QuoteRegistrationV2Props {
onCalculate?: () => void;
onEdit?: () => void;
onOrderRegister?: () => void;
+ /** 수주 보기 (이미 수주가 있는 경우) */
+ onOrderView?: () => void;
initialData?: QuoteFormDataV2 | null;
isLoading?: boolean;
/** IntegratedDetailTemplate 사용 시 타이틀 영역 숨김 */
@@ -168,6 +171,7 @@ export function QuoteRegistrationV2({
onCalculate,
onEdit,
onOrderRegister,
+ onOrderView,
initialData,
isLoading = false,
hideHeader = false,
@@ -844,7 +848,7 @@ export function QuoteRegistrationV2({
@@ -968,6 +972,8 @@ export function QuoteRegistrationV2({
onBack={onBack}
onEdit={onEdit}
onOrderRegister={onOrderRegister}
+ onOrderView={onOrderView}
+ orderId={formData.orderId}
onDiscount={() => setDiscountModalOpen(true)}
onFormulaView={() => setFormulaViewOpen(true)}
hasBomResult={hasBomResult}
diff --git a/src/components/quotes/types.ts b/src/components/quotes/types.ts
index 548251b3..5ea304ce 100644
--- a/src/components/quotes/types.ts
+++ b/src/components/quotes/types.ts
@@ -22,7 +22,7 @@ export const QUOTE_STATUS_LABELS: Record = {
sent: '발송완료',
approved: '승인',
rejected: '거절',
- finalized: '최종확정',
+ finalized: '견적완료',
converted: '수주전환',
};
@@ -97,6 +97,7 @@ export interface Quote {
updatedBy?: string;
finalizedAt?: string;
finalizedBy?: string;
+ orderId?: number | null; // 연결된 수주 ID (수주전환 시 설정)
}
// ===== API 응답 타입 =====
@@ -182,6 +183,8 @@ export interface QuoteApiData {
updated_by: number | null;
finalized_at: string | null;
finalized_by: number | null;
+ // 연결된 수주 ID (수주전환 시 설정)
+ order_id?: number | null;
// 관계 데이터 (with 로드 시)
creator?: {
id: number;
@@ -269,6 +272,7 @@ export function transformApiToFrontend(apiData: QuoteApiData): Quote {
updatedBy: apiData.updater?.name || undefined,
finalizedAt: apiData.finalized_at || undefined,
finalizedBy: apiData.finalizer?.name || undefined,
+ orderId: apiData.order_id ?? undefined, // 연결된 수주 ID
};
}
@@ -348,7 +352,7 @@ export const QUOTE_FILTER_OPTIONS: { value: QuoteFilterType; label: string }[] =
{ value: 'all', label: '전체' },
{ value: 'initial', label: '최초작성' },
{ value: 'revising', label: '수정중' },
- { value: 'final', label: '최종확정' },
+ { value: 'final', label: '견적완료' },
{ value: 'converted', label: '수주전환' },
];
@@ -694,10 +698,11 @@ export interface QuoteFormDataV2 {
contact: string;
dueDate: string;
remarks: string;
- status: 'draft' | 'temporary' | 'final'; // 작성중, 임시저장, 최종저장
+ status: 'draft' | 'temporary' | 'final' | 'converted'; // 작성중, 임시저장, 최종저장, 수주전환
discountRate: number; // 할인율 (%)
discountAmount: number; // 할인 금액
locations: LocationItem[];
+ orderId?: number | null; // 연결된 수주 ID (수주전환 시 설정)
}
// =============================================================================
@@ -709,7 +714,7 @@ export interface QuoteFormDataV2 {
*
* 핵심 차이점:
* - V2는 locations[] 배열, API는 items[] + calculation_inputs.items[] 구조
- * - V2 status는 3가지 (draft/temporary/final), API status는 6가지
+ * - V2 status는 4가지 (draft/temporary/final/converted), API status는 6가지
* - BOM 산출 결과가 있으면 items에 자재 상세 포함
*/
export function transformV2ToApi(
@@ -964,6 +969,7 @@ export function transformApiToV2(apiData: QuoteApiData): QuoteFormDataV2 {
description?: string;
discountRate?: number;
discountAmount?: number;
+ orderId?: number | null; // 연결된 수주 ID (camelCase 버전)
};
return {
@@ -991,6 +997,8 @@ export function transformApiToV2(apiData: QuoteApiData): QuoteFormDataV2 {
discountRate: Number(apiData.discount_rate) || transformed.discountRate || 0,
discountAmount: Number(apiData.discount_amount) || transformed.discountAmount || 0,
locations: locations,
+ // 연결된 수주 ID (raw API: order_id, transformed: orderId)
+ orderId: apiData.order_id ?? transformed.orderId ?? null,
};
}
diff --git a/src/lib/api/items.ts b/src/lib/api/items.ts
index 5d775ffa..4aa3830d 100644
--- a/src/lib/api/items.ts
+++ b/src/lib/api/items.ts
@@ -104,6 +104,9 @@ interface ApiItemResponse {
itemName?: string;
itemType?: string;
isActive?: boolean;
+ // 수입검사 양식 연결 여부
+ has_inspection_template?: boolean;
+ hasInspectionTemplate?: boolean;
}
function transformItemFromApi(apiItem: ApiItemResponse): ItemMaster {
@@ -117,6 +120,7 @@ function transformItemFromApi(apiItem: ApiItemResponse): ItemMaster {
unit: apiItem.unit || '',
specification: apiItem.specification || '',
isActive: apiItem.is_active === true || apiItem.is_active === 1 || apiItem.isActive === true,
+ hasInspectionTemplate: apiItem.has_inspection_template === true || apiItem.hasInspectionTemplate === true,
currentRevision: 0,
isFinal: false,
createdAt: '',
diff --git a/src/types/item.ts b/src/types/item.ts
index c8b4e830..b6b5fdda 100644
--- a/src/types/item.ts
+++ b/src/types/item.ts
@@ -132,6 +132,7 @@ export interface ItemMaster {
unit: string; // 단위 (EA, SET, KG, M 등)
specification?: string; // 규격
isActive?: boolean; // 활성/비활성
+ hasInspectionTemplate?: boolean; // 수입검사 양식 연결 여부
// === 분류 ===
category1?: string; // 대분류