fix(WEB): 건설 견적관리 API 연동 수정
- actions.ts: /quotes API 호출로 변경 (quote_type=construction 필터) - actions.ts: API 응답 파싱 수정 (response.data.data 구조 처리) - EstimateListClient.tsx: size 1000→100 (API 최대값 준수)
This commit is contained in:
@@ -104,7 +104,7 @@ export default function EstimateListClient({ initialData = [], initialStats }: E
|
||||
try {
|
||||
const [listResult, statsResult] = await Promise.all([
|
||||
getEstimateList({
|
||||
size: 1000,
|
||||
size: 100, // API 최대값 100
|
||||
startDate: startDate || undefined,
|
||||
endDate: endDate || undefined,
|
||||
}),
|
||||
|
||||
@@ -17,47 +17,73 @@ import type {
|
||||
import { apiClient } from '@/lib/api';
|
||||
|
||||
/**
|
||||
* 주일 기업 - 견적관리 Server Actions
|
||||
* 표준화된 apiClient 사용 버전
|
||||
* 건설 프로젝트 - 견적관리 Server Actions
|
||||
* quotes API 사용 (quote_type=construction 필터)
|
||||
*/
|
||||
|
||||
// ========================================
|
||||
// API 응답 타입
|
||||
// API 응답 타입 (Quotes API)
|
||||
// ========================================
|
||||
|
||||
interface ApiEstimate {
|
||||
interface ApiQuote {
|
||||
id: number;
|
||||
estimate_code: string;
|
||||
partner_id: number | null;
|
||||
partner_name: string | null;
|
||||
project_name: string;
|
||||
estimator_id: number | null;
|
||||
estimator_name: string | null;
|
||||
item_count: number;
|
||||
estimate_amount: number;
|
||||
completed_date: string | null;
|
||||
bid_date: string | null;
|
||||
status: 'pending' | 'approval_waiting' | 'completed' | 'rejected' | 'hold';
|
||||
quote_type: string;
|
||||
quote_number: string;
|
||||
registration_date: string;
|
||||
client_id: number | null;
|
||||
client_name: string | null;
|
||||
site_id: number | null;
|
||||
site_name: string | null;
|
||||
site_briefing_id: number | null;
|
||||
product_category: string | null;
|
||||
product_name: string | null;
|
||||
total_amount: number | string;
|
||||
status: string;
|
||||
author: string | null;
|
||||
manager: string | null;
|
||||
remarks: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
created_by: string | null;
|
||||
created_by: number | null;
|
||||
// 연관 데이터
|
||||
items?: ApiQuoteItem[];
|
||||
site_briefing?: ApiSiteBriefing;
|
||||
}
|
||||
|
||||
interface ApiEstimateStats {
|
||||
interface ApiQuoteItem {
|
||||
id: number;
|
||||
item_code: string;
|
||||
item_name: string;
|
||||
specification: string | null;
|
||||
unit: string;
|
||||
base_quantity: number;
|
||||
calculated_quantity: number;
|
||||
unit_price: number;
|
||||
total_price: number;
|
||||
formula: string | null;
|
||||
note: string | null;
|
||||
sort_order: number;
|
||||
}
|
||||
|
||||
interface ApiSiteBriefing {
|
||||
id: number;
|
||||
briefing_code: string;
|
||||
title: string;
|
||||
briefing_date: string;
|
||||
attendance_status: string;
|
||||
partner?: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface ApiQuoteStats {
|
||||
total_count: number;
|
||||
pending_count: number;
|
||||
completed_count: number;
|
||||
}
|
||||
|
||||
interface ApiEstimateDetail extends ApiEstimate {
|
||||
site_briefing?: ApiSiteBriefingInfo;
|
||||
bid_info?: ApiBidInfo;
|
||||
summary_items?: ApiSummaryItem[];
|
||||
expense_items?: ApiExpenseItem[];
|
||||
price_adjustments?: ApiPriceAdjustmentItem[];
|
||||
detail_items?: ApiDetailItem[];
|
||||
}
|
||||
|
||||
// Legacy API types (for backward compatibility)
|
||||
interface ApiSiteBriefingInfo {
|
||||
briefing_code: string;
|
||||
partner_name: string;
|
||||
@@ -85,202 +111,116 @@ interface ApiBidDocument {
|
||||
file_size: number;
|
||||
}
|
||||
|
||||
interface ApiSummaryItem {
|
||||
id: number;
|
||||
name: string;
|
||||
quantity: number;
|
||||
unit: string;
|
||||
material_cost: number;
|
||||
labor_cost: number;
|
||||
total_cost: number;
|
||||
remarks: string;
|
||||
}
|
||||
|
||||
interface ApiExpenseItem {
|
||||
id: number;
|
||||
name: string;
|
||||
amount: number;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
interface ApiPriceAdjustmentItem {
|
||||
id: number;
|
||||
category: string;
|
||||
unit_price: number;
|
||||
coating: number;
|
||||
batting: number;
|
||||
box_reinforce: number;
|
||||
painting: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
interface ApiDetailItem {
|
||||
id: number;
|
||||
no: number;
|
||||
name: string;
|
||||
material: string;
|
||||
width: number;
|
||||
height: number;
|
||||
quantity: number;
|
||||
box: number;
|
||||
assembly: number;
|
||||
coating: number;
|
||||
batting: number;
|
||||
mounting: number;
|
||||
fitting: number;
|
||||
controller: number;
|
||||
width_construction: number;
|
||||
height_construction: number;
|
||||
material_cost: number;
|
||||
labor_cost: number;
|
||||
quantity_price: number;
|
||||
expense_quantity: number;
|
||||
expense_total: number;
|
||||
total_cost: number;
|
||||
other_cost: number;
|
||||
margin_cost: number;
|
||||
total_price: number;
|
||||
unit_price: number;
|
||||
expense: number;
|
||||
margin_rate: number;
|
||||
unit_quantity: number;
|
||||
expense_result: number;
|
||||
margin_actual: number;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 타입 변환 함수
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* API 응답 → Estimate 타입 변환
|
||||
* API 응답 (Quote) → Estimate 타입 변환
|
||||
* 기존 프론트엔드 타입과 호환성 유지
|
||||
*/
|
||||
function transformEstimate(apiData: ApiEstimate): Estimate {
|
||||
function transformQuoteToEstimate(apiData: ApiQuote): Estimate {
|
||||
return {
|
||||
id: String(apiData.id),
|
||||
estimateCode: apiData.estimate_code || '',
|
||||
partnerId: apiData.partner_id ? String(apiData.partner_id) : '',
|
||||
partnerName: apiData.partner_name || '',
|
||||
projectName: apiData.project_name || '',
|
||||
estimatorId: apiData.estimator_id ? String(apiData.estimator_id) : '',
|
||||
estimatorName: apiData.estimator_name || '',
|
||||
itemCount: apiData.item_count || 0,
|
||||
estimateAmount: apiData.estimate_amount || 0,
|
||||
completedDate: apiData.completed_date || null,
|
||||
bidDate: apiData.bid_date || null,
|
||||
status: apiData.status || 'pending',
|
||||
estimateCode: apiData.quote_number || '',
|
||||
partnerId: apiData.client_id ? String(apiData.client_id) : '',
|
||||
partnerName: apiData.client_name || '',
|
||||
projectName: apiData.site_name || '',
|
||||
estimatorId: apiData.created_by ? String(apiData.created_by) : '',
|
||||
estimatorName: apiData.author || '',
|
||||
itemCount: apiData.items?.length || 0,
|
||||
estimateAmount: Number(apiData.total_amount) || 0,
|
||||
completedDate: null,
|
||||
bidDate: apiData.registration_date || null,
|
||||
status: mapQuoteStatusToEstimateStatus(apiData.status),
|
||||
createdAt: apiData.created_at || '',
|
||||
updatedAt: apiData.updated_at || '',
|
||||
createdBy: apiData.created_by || '',
|
||||
createdBy: apiData.created_by ? String(apiData.created_by) : '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Quote 상태 → Estimate 상태 매핑
|
||||
*/
|
||||
function mapQuoteStatusToEstimateStatus(
|
||||
quoteStatus: string
|
||||
): 'pending' | 'approval_waiting' | 'completed' | 'rejected' | 'hold' {
|
||||
const statusMap: Record<string, 'pending' | 'approval_waiting' | 'completed' | 'rejected' | 'hold'> = {
|
||||
pending: 'pending',
|
||||
draft: 'pending',
|
||||
sent: 'approval_waiting',
|
||||
approved: 'completed',
|
||||
rejected: 'rejected',
|
||||
finalized: 'completed',
|
||||
converted: 'completed',
|
||||
};
|
||||
return statusMap[quoteStatus] || 'pending';
|
||||
}
|
||||
|
||||
/**
|
||||
* API 응답 → EstimateDetail 타입 변환
|
||||
*/
|
||||
function transformEstimateDetail(apiData: ApiEstimateDetail): EstimateDetail {
|
||||
const base = transformEstimate(apiData);
|
||||
function transformQuoteToEstimateDetail(apiData: ApiQuote): EstimateDetail {
|
||||
const base = transformQuoteToEstimate(apiData);
|
||||
|
||||
const siteBriefing: SiteBriefingInfo = apiData.site_briefing
|
||||
? {
|
||||
briefingCode: apiData.site_briefing.briefing_code || '',
|
||||
partnerName: apiData.site_briefing.partner_name || '',
|
||||
companyName: apiData.site_briefing.company_name || '',
|
||||
partnerName: apiData.site_briefing.partner?.name || '',
|
||||
companyName: '',
|
||||
briefingDate: apiData.site_briefing.briefing_date || '',
|
||||
attendee: apiData.site_briefing.attendee || '',
|
||||
attendee: '',
|
||||
}
|
||||
: { briefingCode: '', partnerName: '', companyName: '', briefingDate: '', attendee: '' };
|
||||
|
||||
const bidInfo: BidInfo = apiData.bid_info
|
||||
? {
|
||||
projectName: apiData.bid_info.project_name || '',
|
||||
bidDate: apiData.bid_info.bid_date || '',
|
||||
siteCount: apiData.bid_info.site_count || 0,
|
||||
constructionPeriod: apiData.bid_info.construction_period || '',
|
||||
constructionStartDate: apiData.bid_info.construction_start_date || '',
|
||||
constructionEndDate: apiData.bid_info.construction_end_date || '',
|
||||
vatType: apiData.bid_info.vat_type || 'excluded',
|
||||
workReport: apiData.bid_info.work_report || '',
|
||||
documents: (apiData.bid_info.documents || []).map((d) => ({
|
||||
id: String(d.id),
|
||||
fileName: d.file_name || '',
|
||||
fileUrl: d.file_url || '',
|
||||
fileSize: d.file_size || 0,
|
||||
})),
|
||||
}
|
||||
: {
|
||||
projectName: '',
|
||||
bidDate: '',
|
||||
siteCount: 0,
|
||||
constructionPeriod: '',
|
||||
constructionStartDate: '',
|
||||
constructionEndDate: '',
|
||||
vatType: 'excluded',
|
||||
workReport: '',
|
||||
documents: [],
|
||||
};
|
||||
const bidInfo: BidInfo = {
|
||||
projectName: apiData.site_name || '',
|
||||
bidDate: apiData.registration_date || '',
|
||||
siteCount: 0,
|
||||
constructionPeriod: '',
|
||||
constructionStartDate: '',
|
||||
constructionEndDate: '',
|
||||
vatType: 'excluded',
|
||||
workReport: '',
|
||||
documents: [],
|
||||
};
|
||||
|
||||
const summaryItems: EstimateSummaryItem[] = (apiData.summary_items || []).map((item) => ({
|
||||
id: String(item.id),
|
||||
name: item.name || '',
|
||||
quantity: item.quantity || 0,
|
||||
unit: item.unit || '',
|
||||
materialCost: item.material_cost || 0,
|
||||
laborCost: item.labor_cost || 0,
|
||||
totalCost: item.total_cost || 0,
|
||||
remarks: item.remarks || '',
|
||||
}));
|
||||
const summaryItems: EstimateSummaryItem[] = [];
|
||||
const expenseItems: ExpenseItem[] = [];
|
||||
const priceAdjustments: PriceAdjustmentItem[] = [];
|
||||
|
||||
const expenseItems: ExpenseItem[] = (apiData.expense_items || []).map((item) => ({
|
||||
const detailItems: EstimateDetailItem[] = (apiData.items || []).map((item, index) => ({
|
||||
id: String(item.id),
|
||||
name: item.name || '',
|
||||
amount: item.amount || 0,
|
||||
selected: item.selected || false,
|
||||
}));
|
||||
|
||||
const priceAdjustments: PriceAdjustmentItem[] = (apiData.price_adjustments || []).map((item) => ({
|
||||
id: String(item.id),
|
||||
category: item.category || '',
|
||||
unitPrice: item.unit_price || 0,
|
||||
coating: item.coating || 0,
|
||||
batting: item.batting || 0,
|
||||
boxReinforce: item.box_reinforce || 0,
|
||||
painting: item.painting || 0,
|
||||
total: item.total || 0,
|
||||
}));
|
||||
|
||||
const detailItems: EstimateDetailItem[] = (apiData.detail_items || []).map((item) => ({
|
||||
id: String(item.id),
|
||||
no: item.no || 0,
|
||||
name: item.name || '',
|
||||
material: item.material || '',
|
||||
width: item.width || 0,
|
||||
height: item.height || 0,
|
||||
quantity: item.quantity || 0,
|
||||
box: item.box || 0,
|
||||
assembly: item.assembly || 0,
|
||||
coating: item.coating || 0,
|
||||
batting: item.batting || 0,
|
||||
mounting: item.mounting || 0,
|
||||
fitting: item.fitting || 0,
|
||||
controller: item.controller || 0,
|
||||
widthConstruction: item.width_construction || 0,
|
||||
heightConstruction: item.height_construction || 0,
|
||||
materialCost: item.material_cost || 0,
|
||||
laborCost: item.labor_cost || 0,
|
||||
quantityPrice: item.quantity_price || 0,
|
||||
expenseQuantity: item.expense_quantity || 0,
|
||||
expenseTotal: item.expense_total || 0,
|
||||
totalCost: item.total_cost || 0,
|
||||
otherCost: item.other_cost || 0,
|
||||
marginCost: item.margin_cost || 0,
|
||||
no: index + 1,
|
||||
name: item.item_name || '',
|
||||
material: item.specification || '',
|
||||
width: 0,
|
||||
height: 0,
|
||||
quantity: item.calculated_quantity || 0,
|
||||
box: 0,
|
||||
assembly: 0,
|
||||
coating: 0,
|
||||
batting: 0,
|
||||
mounting: 0,
|
||||
fitting: 0,
|
||||
controller: 0,
|
||||
widthConstruction: 0,
|
||||
heightConstruction: 0,
|
||||
materialCost: 0,
|
||||
laborCost: 0,
|
||||
quantityPrice: item.unit_price || 0,
|
||||
expenseQuantity: 0,
|
||||
expenseTotal: 0,
|
||||
totalCost: item.total_price || 0,
|
||||
otherCost: 0,
|
||||
marginCost: 0,
|
||||
totalPrice: item.total_price || 0,
|
||||
unitPrice: item.unit_price || 0,
|
||||
expense: item.expense || 0,
|
||||
marginRate: item.margin_rate || 0,
|
||||
unitQuantity: item.unit_quantity || 0,
|
||||
expenseResult: item.expense_result || 0,
|
||||
marginActual: item.margin_actual || 0,
|
||||
expense: 0,
|
||||
marginRate: 0,
|
||||
unitQuantity: item.base_quantity || 0,
|
||||
expenseResult: 0,
|
||||
marginActual: 0,
|
||||
}));
|
||||
|
||||
return {
|
||||
@@ -300,45 +240,37 @@ function transformEstimateDetail(apiData: ApiEstimateDetail): EstimateDetail {
|
||||
function transformToApiRequest(data: Partial<EstimateDetailFormData>): Record<string, unknown> {
|
||||
const apiData: Record<string, unknown> = {};
|
||||
|
||||
if (data.estimateCode !== undefined) apiData.estimate_code = data.estimateCode;
|
||||
if (data.estimatorId !== undefined) apiData.estimator_id = data.estimatorId || null;
|
||||
if (data.estimatorName !== undefined) apiData.estimator_name = data.estimatorName || null;
|
||||
if (data.estimateAmount !== undefined) apiData.estimate_amount = data.estimateAmount;
|
||||
if (data.status !== undefined) apiData.status = data.status;
|
||||
|
||||
if (data.siteBriefing !== undefined) {
|
||||
apiData.site_briefing = {
|
||||
briefing_code: data.siteBriefing.briefingCode,
|
||||
partner_name: data.siteBriefing.partnerName,
|
||||
company_name: data.siteBriefing.companyName,
|
||||
briefing_date: data.siteBriefing.briefingDate,
|
||||
attendee: data.siteBriefing.attendee,
|
||||
if (data.estimateCode !== undefined) apiData.quote_number = data.estimateCode;
|
||||
if (data.estimatorId !== undefined) apiData.created_by = data.estimatorId || null;
|
||||
if (data.estimatorName !== undefined) apiData.author = data.estimatorName || null;
|
||||
if (data.estimateAmount !== undefined) apiData.total_amount = data.estimateAmount;
|
||||
if (data.status !== undefined) {
|
||||
// Estimate 상태 → Quote 상태 역매핑
|
||||
const reverseStatusMap: Record<string, string> = {
|
||||
pending: 'pending',
|
||||
approval_waiting: 'sent',
|
||||
completed: 'finalized',
|
||||
rejected: 'rejected',
|
||||
hold: 'draft',
|
||||
};
|
||||
apiData.status = reverseStatusMap[data.status] || 'pending';
|
||||
}
|
||||
|
||||
if (data.bidInfo !== undefined) {
|
||||
apiData.bid_info = {
|
||||
project_name: data.bidInfo.projectName,
|
||||
bid_date: data.bidInfo.bidDate,
|
||||
site_count: data.bidInfo.siteCount,
|
||||
construction_period: data.bidInfo.constructionPeriod,
|
||||
construction_start_date: data.bidInfo.constructionStartDate,
|
||||
construction_end_date: data.bidInfo.constructionEndDate,
|
||||
vat_type: data.bidInfo.vatType,
|
||||
work_report: data.bidInfo.workReport,
|
||||
};
|
||||
apiData.site_name = data.bidInfo.projectName;
|
||||
apiData.registration_date = data.bidInfo.bidDate;
|
||||
}
|
||||
|
||||
return apiData;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// API 함수
|
||||
// API 함수 (quotes API 사용)
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* 견적 목록 조회
|
||||
* GET /api/v1/estimates
|
||||
* 건설 견적 목록 조회
|
||||
* GET /api/v1/quotes?quote_type=construction
|
||||
*/
|
||||
export async function getEstimateList(filter?: EstimateFilter): Promise<{
|
||||
success: boolean;
|
||||
@@ -346,62 +278,77 @@ export async function getEstimateList(filter?: EstimateFilter): Promise<{
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const queryParams: Record<string, string> = {};
|
||||
const queryParams: Record<string, string> = {
|
||||
quote_type: 'construction', // 건설 견적만 조회
|
||||
};
|
||||
|
||||
// 검색
|
||||
if (filter?.search) queryParams.search = filter.search;
|
||||
if (filter?.search) queryParams.q = filter.search;
|
||||
|
||||
// 필터
|
||||
if (filter?.status && filter.status !== 'all') queryParams.status = filter.status;
|
||||
if (filter?.partnerId) queryParams.partner_id = filter.partnerId;
|
||||
if (filter?.estimatorId) queryParams.estimator_id = filter.estimatorId;
|
||||
if (filter?.status && filter.status !== 'all') {
|
||||
// Estimate 상태 → Quote 상태로 변환
|
||||
const statusMap: Record<string, string> = {
|
||||
pending: 'pending',
|
||||
approval_waiting: 'sent',
|
||||
completed: 'finalized',
|
||||
rejected: 'rejected',
|
||||
hold: 'draft',
|
||||
};
|
||||
queryParams.status = statusMap[filter.status] || filter.status;
|
||||
}
|
||||
if (filter?.partnerId) queryParams.client_id = filter.partnerId;
|
||||
|
||||
// 날짜 범위
|
||||
if (filter?.startDate) queryParams.start_date = filter.startDate;
|
||||
if (filter?.endDate) queryParams.end_date = filter.endDate;
|
||||
if (filter?.startDate) queryParams.date_from = filter.startDate;
|
||||
if (filter?.endDate) queryParams.date_to = filter.endDate;
|
||||
|
||||
// 페이지네이션
|
||||
if (filter?.page) queryParams.page = String(filter.page);
|
||||
if (filter?.size) queryParams.per_page = String(filter.size);
|
||||
if (filter?.size) queryParams.size = String(filter.size);
|
||||
|
||||
// 정렬
|
||||
if (filter?.sortBy) {
|
||||
const sortMap: Record<string, { field: string; dir: string }> = {
|
||||
latest: { field: 'created_at', dir: 'desc' },
|
||||
oldest: { field: 'created_at', dir: 'asc' },
|
||||
amountDesc: { field: 'estimate_amount', dir: 'desc' },
|
||||
amountAsc: { field: 'estimate_amount', dir: 'asc' },
|
||||
bidDateDesc: { field: 'bid_date', dir: 'desc' },
|
||||
partnerNameAsc: { field: 'partner_name', dir: 'asc' },
|
||||
partnerNameDesc: { field: 'partner_name', dir: 'desc' },
|
||||
projectNameAsc: { field: 'project_name', dir: 'asc' },
|
||||
projectNameDesc: { field: 'project_name', dir: 'desc' },
|
||||
amountDesc: { field: 'total_amount', dir: 'desc' },
|
||||
amountAsc: { field: 'total_amount', dir: 'asc' },
|
||||
bidDateDesc: { field: 'registration_date', dir: 'desc' },
|
||||
partnerNameAsc: { field: 'client_name', dir: 'asc' },
|
||||
partnerNameDesc: { field: 'client_name', dir: 'desc' },
|
||||
projectNameAsc: { field: 'site_name', dir: 'asc' },
|
||||
projectNameDesc: { field: 'site_name', dir: 'desc' },
|
||||
};
|
||||
const sort = sortMap[filter.sortBy];
|
||||
if (sort) {
|
||||
queryParams.sort_by = sort.field;
|
||||
queryParams.sort_dir = sort.dir;
|
||||
queryParams.sort_order = sort.dir;
|
||||
}
|
||||
}
|
||||
|
||||
const response = await apiClient.get<{
|
||||
data: ApiEstimate[];
|
||||
current_page: number;
|
||||
per_page: number;
|
||||
total: number;
|
||||
last_page: number;
|
||||
}>('/estimates', { params: queryParams });
|
||||
success: boolean;
|
||||
data: {
|
||||
data: ApiQuote[];
|
||||
current_page: number;
|
||||
per_page: number;
|
||||
total: number;
|
||||
last_page: number;
|
||||
};
|
||||
}>('/quotes', { params: queryParams });
|
||||
|
||||
const items = (response.data || []).map(transformEstimate);
|
||||
const paginatedData = response.data;
|
||||
const items = (paginatedData.data || []).map(transformQuoteToEstimate);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
items,
|
||||
total: response.total || 0,
|
||||
page: response.current_page || 1,
|
||||
size: response.per_page || 20,
|
||||
totalPages: response.last_page || 1,
|
||||
total: paginatedData.total || 0,
|
||||
page: paginatedData.current_page || 1,
|
||||
size: paginatedData.per_page || 20,
|
||||
totalPages: paginatedData.last_page || 1,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
@@ -412,7 +359,7 @@ export async function getEstimateList(filter?: EstimateFilter): Promise<{
|
||||
|
||||
/**
|
||||
* 견적 단건 조회
|
||||
* GET /api/v1/estimates/{id}
|
||||
* GET /api/v1/quotes/{id}
|
||||
*/
|
||||
export async function getEstimate(id: string): Promise<{
|
||||
success: boolean;
|
||||
@@ -420,8 +367,8 @@ export async function getEstimate(id: string): Promise<{
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const response = await apiClient.get<ApiEstimate>(`/estimates/${id}`);
|
||||
return { success: true, data: transformEstimate(response) };
|
||||
const response = await apiClient.get<{ success: boolean; data: ApiQuote }>(`/quotes/${id}`);
|
||||
return { success: true, data: transformQuoteToEstimate(response.data) };
|
||||
} catch (error) {
|
||||
console.error('견적 조회 오류:', error);
|
||||
return { success: false, error: '견적 정보를 찾을 수 없습니다.' };
|
||||
@@ -430,7 +377,7 @@ export async function getEstimate(id: string): Promise<{
|
||||
|
||||
/**
|
||||
* 견적 상세 조회 (첨부 정보 포함)
|
||||
* GET /api/v1/estimates/{id}/detail
|
||||
* GET /api/v1/quotes/{id}
|
||||
*/
|
||||
export async function getEstimateDetail(id: string): Promise<{
|
||||
success: boolean;
|
||||
@@ -438,8 +385,8 @@ export async function getEstimateDetail(id: string): Promise<{
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const response = await apiClient.get<ApiEstimateDetail>(`/estimates/${id}`);
|
||||
return { success: true, data: transformEstimateDetail(response) };
|
||||
const response = await apiClient.get<{ success: boolean; data: ApiQuote }>(`/quotes/${id}`);
|
||||
return { success: true, data: transformQuoteToEstimateDetail(response.data) };
|
||||
} catch (error) {
|
||||
console.error('견적 상세 조회 오류:', error);
|
||||
return { success: false, error: '견적 상세 정보를 불러오는데 실패했습니다.' };
|
||||
@@ -448,7 +395,8 @@ export async function getEstimateDetail(id: string): Promise<{
|
||||
|
||||
/**
|
||||
* 견적 통계 조회
|
||||
* GET /api/v1/estimates/stats
|
||||
* GET /api/v1/quotes/stats (건설용)
|
||||
* 현재는 목록 조회로 대체
|
||||
*/
|
||||
export async function getEstimateStats(): Promise<{
|
||||
success: boolean;
|
||||
@@ -456,14 +404,25 @@ export async function getEstimateStats(): Promise<{
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const response = await apiClient.get<ApiEstimateStats>('/estimates/stats');
|
||||
// 통계 API가 없으므로 목록 조회로 대체
|
||||
const [allResponse, pendingResponse] = await Promise.all([
|
||||
apiClient.get<{ success: boolean; data: { total: number } }>('/quotes', {
|
||||
params: { quote_type: 'construction', size: '1' },
|
||||
}),
|
||||
apiClient.get<{ success: boolean; data: { total: number } }>('/quotes', {
|
||||
params: { quote_type: 'construction', status: 'pending', size: '1' },
|
||||
}),
|
||||
]);
|
||||
|
||||
const total = allResponse.data?.total || 0;
|
||||
const pending = pendingResponse.data?.total || 0;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
total: response.total_count || 0,
|
||||
pending: response.pending_count || 0,
|
||||
completed: response.completed_count || 0,
|
||||
total,
|
||||
pending,
|
||||
completed: total - pending,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
@@ -474,7 +433,7 @@ export async function getEstimateStats(): Promise<{
|
||||
|
||||
/**
|
||||
* 견적 등록
|
||||
* POST /api/v1/estimates
|
||||
* POST /api/v1/quotes
|
||||
*/
|
||||
export async function createEstimate(data: EstimateDetailFormData): Promise<{
|
||||
success: boolean;
|
||||
@@ -482,9 +441,12 @@ export async function createEstimate(data: EstimateDetailFormData): Promise<{
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const apiData = transformToApiRequest(data);
|
||||
const response = await apiClient.post<ApiEstimate>('/estimates', apiData);
|
||||
return { success: true, data: transformEstimate(response) };
|
||||
const apiData = {
|
||||
...transformToApiRequest(data),
|
||||
quote_type: 'construction', // 건설 견적으로 생성
|
||||
};
|
||||
const response = await apiClient.post<ApiQuote>('/quotes', apiData);
|
||||
return { success: true, data: transformQuoteToEstimate(response) };
|
||||
} catch (error) {
|
||||
console.error('견적 등록 오류:', error);
|
||||
return { success: false, error: '견적 등록에 실패했습니다.' };
|
||||
@@ -493,7 +455,7 @@ export async function createEstimate(data: EstimateDetailFormData): Promise<{
|
||||
|
||||
/**
|
||||
* 견적 수정
|
||||
* PUT /api/v1/estimates/{id}
|
||||
* PUT /api/v1/quotes/{id}
|
||||
*/
|
||||
export async function updateEstimate(
|
||||
id: string,
|
||||
@@ -505,8 +467,8 @@ export async function updateEstimate(
|
||||
}> {
|
||||
try {
|
||||
const apiData = transformToApiRequest(data);
|
||||
const response = await apiClient.put<ApiEstimate>(`/estimates/${id}`, apiData);
|
||||
return { success: true, data: transformEstimate(response) };
|
||||
const response = await apiClient.put<ApiQuote>(`/quotes/${id}`, apiData);
|
||||
return { success: true, data: transformQuoteToEstimate(response) };
|
||||
} catch (error) {
|
||||
console.error('견적 수정 오류:', error);
|
||||
return { success: false, error: '견적 수정에 실패했습니다.' };
|
||||
@@ -515,14 +477,14 @@ export async function updateEstimate(
|
||||
|
||||
/**
|
||||
* 견적 삭제
|
||||
* DELETE /api/v1/estimates/{id}
|
||||
* DELETE /api/v1/quotes/{id}
|
||||
*/
|
||||
export async function deleteEstimate(id: string): Promise<{
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
await apiClient.delete(`/estimates/${id}`);
|
||||
await apiClient.delete(`/quotes/${id}`);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('견적 삭제 오류:', error);
|
||||
@@ -532,7 +494,7 @@ export async function deleteEstimate(id: string): Promise<{
|
||||
|
||||
/**
|
||||
* 견적 일괄 삭제
|
||||
* DELETE /api/v1/estimates/bulk
|
||||
* DELETE /api/v1/quotes/bulk
|
||||
*/
|
||||
export async function deleteEstimates(ids: string[]): Promise<{
|
||||
success: boolean;
|
||||
@@ -540,7 +502,7 @@ export async function deleteEstimates(ids: string[]): Promise<{
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
await apiClient.delete('/estimates/bulk', {
|
||||
await apiClient.delete('/quotes/bulk', {
|
||||
data: { ids: ids.map((id) => Number(id)) },
|
||||
});
|
||||
return { success: true, deletedCount: ids.length };
|
||||
|
||||
Reference in New Issue
Block a user