품질검사관리: - InspectionCreate 생성 폼 대폭 개선 (+269줄) - InspectionDetail 상세 페이지 확장 (+424줄) - InspectionReportModal 검사성적서 모달 기능 강화 - InspectionReportDocument 문서 구조 개선 - ProductInspectionInputModal 제품검사 입력 모달 신규 추가 - types, mockData, actions 확장 자재입고: - ReceivingDetail 수입검사 연동 기능 추가 - ImportInspectionInputModal 수입검사 입력 모달 신규 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
806 lines
26 KiB
TypeScript
806 lines
26 KiB
TypeScript
'use server';
|
|
|
|
/**
|
|
* 제품검사 관리 Server Actions
|
|
*
|
|
* API Endpoints:
|
|
* - GET /api/v1/product-inspections - 목록 조회
|
|
* - GET /api/v1/product-inspections/stats - 통계 조회
|
|
* - GET /api/v1/product-inspections/calendar - 캘린더 스케줄 조회
|
|
* - GET /api/v1/product-inspections/{id} - 상세 조회
|
|
* - POST /api/v1/product-inspections - 등록
|
|
* - PUT /api/v1/product-inspections/{id} - 수정
|
|
* - DELETE /api/v1/product-inspections/{id} - 삭제
|
|
* - PATCH /api/v1/product-inspections/{id}/complete - 검사 완료 처리
|
|
* - GET /api/v1/orders/select - 수주 선택 목록 조회
|
|
*/
|
|
|
|
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
|
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
|
import type {
|
|
ProductInspection,
|
|
InspectionStats,
|
|
InspectionStatus,
|
|
InspectionCalendarItem,
|
|
OrderSelectItem,
|
|
InspectionFormData,
|
|
} from './types';
|
|
import {
|
|
mockInspections,
|
|
mockStats,
|
|
mockCalendarItems,
|
|
mockOrderSelectItems,
|
|
} from './mockData';
|
|
|
|
// 개발환경 Mock 데이터 fallback 플래그
|
|
const USE_MOCK_FALLBACK = true;
|
|
|
|
// ===== API 응답 타입 =====
|
|
|
|
interface ProductInspectionApi {
|
|
id: number;
|
|
quality_doc_number: string;
|
|
site_name: string;
|
|
client: string;
|
|
location_count: number;
|
|
required_info: string;
|
|
inspection_period: string;
|
|
inspector: string;
|
|
status: 'reception' | 'in_progress' | 'completed';
|
|
author: string;
|
|
reception_date: string;
|
|
manager: string;
|
|
manager_contact: string;
|
|
construction_site: {
|
|
site_name: string;
|
|
land_location: string;
|
|
lot_number: string;
|
|
};
|
|
material_distributor: {
|
|
company_name: string;
|
|
company_address: string;
|
|
representative_name: string;
|
|
phone: string;
|
|
};
|
|
constructor_info: {
|
|
company_name: string;
|
|
company_address: string;
|
|
name: string;
|
|
phone: string;
|
|
};
|
|
supervisor: {
|
|
office_name: string;
|
|
office_address: string;
|
|
name: string;
|
|
phone: string;
|
|
};
|
|
schedule_info: {
|
|
visit_request_date: string;
|
|
start_date: string;
|
|
end_date: string;
|
|
inspector: string;
|
|
site_postal_code: string;
|
|
site_address: string;
|
|
site_address_detail: string;
|
|
};
|
|
order_items: Array<{
|
|
id: string;
|
|
order_number: string;
|
|
site_name: string;
|
|
delivery_date: string;
|
|
floor: string;
|
|
symbol: string;
|
|
order_width: number;
|
|
order_height: number;
|
|
construction_width: number;
|
|
construction_height: number;
|
|
change_reason: string;
|
|
}>;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
interface PaginatedResponse {
|
|
items: ProductInspectionApi[];
|
|
current_page: number;
|
|
last_page: number;
|
|
per_page: number;
|
|
total: number;
|
|
}
|
|
|
|
interface InspectionStatsApi {
|
|
reception_count: number;
|
|
in_progress_count: number;
|
|
completed_count: number;
|
|
}
|
|
|
|
interface CalendarItemApi {
|
|
id: number;
|
|
start_date: string;
|
|
end_date: string;
|
|
inspector: string;
|
|
site_name: string;
|
|
status: 'reception' | 'in_progress' | 'completed';
|
|
}
|
|
|
|
interface OrderSelectItemApi {
|
|
id: number;
|
|
order_number: string;
|
|
site_name: string;
|
|
delivery_date: string;
|
|
location_count: number;
|
|
}
|
|
|
|
// ===== 페이지네이션 =====
|
|
|
|
interface PaginationMeta {
|
|
currentPage: number;
|
|
lastPage: number;
|
|
perPage: number;
|
|
total: number;
|
|
}
|
|
|
|
// ===== 상태 변환 =====
|
|
|
|
function mapApiStatus(status: ProductInspectionApi['status']): InspectionStatus {
|
|
switch (status) {
|
|
case 'reception':
|
|
return '접수';
|
|
case 'in_progress':
|
|
return '진행중';
|
|
case 'completed':
|
|
return '완료';
|
|
default:
|
|
return '접수';
|
|
}
|
|
}
|
|
|
|
function mapFrontendStatus(status: InspectionStatus): string {
|
|
switch (status) {
|
|
case '접수':
|
|
return 'reception';
|
|
case '진행중':
|
|
return 'in_progress';
|
|
case '완료':
|
|
return 'completed';
|
|
default:
|
|
return 'reception';
|
|
}
|
|
}
|
|
|
|
// ===== API → Frontend 변환 =====
|
|
|
|
function transformApiToFrontend(api: ProductInspectionApi): ProductInspection {
|
|
return {
|
|
id: String(api.id),
|
|
qualityDocNumber: api.quality_doc_number,
|
|
siteName: api.site_name,
|
|
client: api.client,
|
|
locationCount: api.location_count,
|
|
requiredInfo: api.required_info,
|
|
inspectionPeriod: api.inspection_period,
|
|
inspector: api.inspector,
|
|
status: mapApiStatus(api.status),
|
|
author: api.author,
|
|
receptionDate: api.reception_date,
|
|
manager: api.manager,
|
|
managerContact: api.manager_contact,
|
|
constructionSite: {
|
|
siteName: api.construction_site?.site_name || '',
|
|
landLocation: api.construction_site?.land_location || '',
|
|
lotNumber: api.construction_site?.lot_number || '',
|
|
},
|
|
materialDistributor: {
|
|
companyName: api.material_distributor?.company_name || '',
|
|
companyAddress: api.material_distributor?.company_address || '',
|
|
representativeName: api.material_distributor?.representative_name || '',
|
|
phone: api.material_distributor?.phone || '',
|
|
},
|
|
constructorInfo: {
|
|
companyName: api.constructor_info?.company_name || '',
|
|
companyAddress: api.constructor_info?.company_address || '',
|
|
name: api.constructor_info?.name || '',
|
|
phone: api.constructor_info?.phone || '',
|
|
},
|
|
supervisor: {
|
|
officeName: api.supervisor?.office_name || '',
|
|
officeAddress: api.supervisor?.office_address || '',
|
|
name: api.supervisor?.name || '',
|
|
phone: api.supervisor?.phone || '',
|
|
},
|
|
scheduleInfo: {
|
|
visitRequestDate: api.schedule_info?.visit_request_date || '',
|
|
startDate: api.schedule_info?.start_date || '',
|
|
endDate: api.schedule_info?.end_date || '',
|
|
inspector: api.schedule_info?.inspector || '',
|
|
sitePostalCode: api.schedule_info?.site_postal_code || '',
|
|
siteAddress: api.schedule_info?.site_address || '',
|
|
siteAddressDetail: api.schedule_info?.site_address_detail || '',
|
|
},
|
|
orderItems: (api.order_items || []).map((item) => ({
|
|
id: item.id,
|
|
orderNumber: item.order_number,
|
|
siteName: item.site_name || '',
|
|
deliveryDate: item.delivery_date || '',
|
|
floor: item.floor,
|
|
symbol: item.symbol,
|
|
orderWidth: item.order_width,
|
|
orderHeight: item.order_height,
|
|
constructionWidth: item.construction_width,
|
|
constructionHeight: item.construction_height,
|
|
changeReason: item.change_reason,
|
|
})),
|
|
};
|
|
}
|
|
|
|
// ===== Frontend → API 변환 =====
|
|
|
|
function transformFormToApi(data: InspectionFormData): Record<string, unknown> {
|
|
return {
|
|
quality_doc_number: data.qualityDocNumber,
|
|
site_name: data.siteName,
|
|
client: data.client,
|
|
manager: data.manager,
|
|
manager_contact: data.managerContact,
|
|
construction_site: {
|
|
site_name: data.constructionSite.siteName,
|
|
land_location: data.constructionSite.landLocation,
|
|
lot_number: data.constructionSite.lotNumber,
|
|
},
|
|
material_distributor: {
|
|
company_name: data.materialDistributor.companyName,
|
|
company_address: data.materialDistributor.companyAddress,
|
|
representative_name: data.materialDistributor.representativeName,
|
|
phone: data.materialDistributor.phone,
|
|
},
|
|
constructor_info: {
|
|
company_name: data.constructorInfo.companyName,
|
|
company_address: data.constructorInfo.companyAddress,
|
|
name: data.constructorInfo.name,
|
|
phone: data.constructorInfo.phone,
|
|
},
|
|
supervisor: {
|
|
office_name: data.supervisor.officeName,
|
|
office_address: data.supervisor.officeAddress,
|
|
name: data.supervisor.name,
|
|
phone: data.supervisor.phone,
|
|
},
|
|
schedule_info: {
|
|
visit_request_date: data.scheduleInfo.visitRequestDate,
|
|
start_date: data.scheduleInfo.startDate,
|
|
end_date: data.scheduleInfo.endDate,
|
|
inspector: data.scheduleInfo.inspector,
|
|
site_postal_code: data.scheduleInfo.sitePostalCode,
|
|
site_address: data.scheduleInfo.siteAddress,
|
|
site_address_detail: data.scheduleInfo.siteAddressDetail,
|
|
},
|
|
order_items: data.orderItems.map((item) => ({
|
|
order_number: item.orderNumber,
|
|
floor: item.floor,
|
|
symbol: item.symbol,
|
|
order_width: item.orderWidth,
|
|
order_height: item.orderHeight,
|
|
construction_width: item.constructionWidth,
|
|
construction_height: item.constructionHeight,
|
|
change_reason: item.changeReason,
|
|
})),
|
|
};
|
|
}
|
|
|
|
const API_BASE = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/product-inspections`;
|
|
|
|
// ===== 제품검사 목록 조회 =====
|
|
|
|
export async function getInspections(params?: {
|
|
page?: number;
|
|
size?: number;
|
|
q?: string;
|
|
status?: InspectionStatus | '전체';
|
|
dateFrom?: string;
|
|
dateTo?: string;
|
|
}): Promise<{
|
|
success: boolean;
|
|
data: ProductInspection[];
|
|
pagination: PaginationMeta;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
const defaultPagination = { currentPage: 1, lastPage: 1, perPage: 20, total: 0 };
|
|
|
|
try {
|
|
const searchParams = new URLSearchParams();
|
|
if (params?.page) searchParams.set('page', String(params.page));
|
|
if (params?.size) searchParams.set('per_page', String(params.size));
|
|
if (params?.q) searchParams.set('q', params.q);
|
|
if (params?.status && params.status !== '전체') {
|
|
searchParams.set('status', mapFrontendStatus(params.status));
|
|
}
|
|
if (params?.dateFrom) searchParams.set('date_from', params.dateFrom);
|
|
if (params?.dateTo) searchParams.set('date_to', params.dateTo);
|
|
|
|
const queryString = searchParams.toString();
|
|
const url = `${API_BASE}${queryString ? `?${queryString}` : ''}`;
|
|
|
|
const { response, error } = await serverFetch(url, { method: 'GET' });
|
|
|
|
if (error || !response || !response.ok) {
|
|
if (USE_MOCK_FALLBACK) {
|
|
console.warn('[InspectionActions] API 실패, Mock 데이터 사용');
|
|
let filtered = [...mockInspections];
|
|
if (params?.status && params.status !== '전체') {
|
|
filtered = filtered.filter(i => i.status === params.status);
|
|
}
|
|
if (params?.q) {
|
|
const q = params.q.toLowerCase();
|
|
filtered = filtered.filter(i =>
|
|
i.siteName.toLowerCase().includes(q) ||
|
|
i.client.toLowerCase().includes(q) ||
|
|
i.qualityDocNumber.toLowerCase().includes(q) ||
|
|
i.inspector.toLowerCase().includes(q)
|
|
);
|
|
}
|
|
const page = params?.page || 1;
|
|
const size = params?.size || 20;
|
|
const start = (page - 1) * size;
|
|
const paged = filtered.slice(start, start + size);
|
|
return {
|
|
success: true,
|
|
data: paged,
|
|
pagination: { currentPage: page, lastPage: Math.ceil(filtered.length / size), perPage: size, total: filtered.length },
|
|
};
|
|
}
|
|
const errMsg = error ? error.message : `API 오류: ${response?.status || 'no response'}`;
|
|
return { success: false, data: [], pagination: defaultPagination, error: errMsg, __authError: error?.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
if (!result.success) {
|
|
return { success: false, data: [], pagination: defaultPagination, error: result.message || '목록 조회 실패' };
|
|
}
|
|
|
|
const paginatedData: PaginatedResponse = result.data || { items: [], current_page: 1, last_page: 1, per_page: 20, total: 0 };
|
|
|
|
return {
|
|
success: true,
|
|
data: paginatedData.items.map(transformApiToFrontend),
|
|
pagination: {
|
|
currentPage: paginatedData.current_page,
|
|
lastPage: paginatedData.last_page,
|
|
perPage: paginatedData.per_page,
|
|
total: paginatedData.total,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
if (isNextRedirectError(error)) throw error;
|
|
console.error('[InspectionActions] getInspections error:', error);
|
|
if (USE_MOCK_FALLBACK) {
|
|
return {
|
|
success: true,
|
|
data: mockInspections,
|
|
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: mockInspections.length },
|
|
};
|
|
}
|
|
return { success: false, data: [], pagination: defaultPagination, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 통계 조회 =====
|
|
|
|
export async function getInspectionStats(params?: {
|
|
dateFrom?: string;
|
|
dateTo?: string;
|
|
}): Promise<{
|
|
success: boolean;
|
|
data?: InspectionStats;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const searchParams = new URLSearchParams();
|
|
if (params?.dateFrom) searchParams.set('date_from', params.dateFrom);
|
|
if (params?.dateTo) searchParams.set('date_to', params.dateTo);
|
|
|
|
const queryString = searchParams.toString();
|
|
const url = `${API_BASE}/stats${queryString ? `?${queryString}` : ''}`;
|
|
|
|
const { response, error } = await serverFetch(url, { method: 'GET' });
|
|
|
|
if (error || !response || !response.ok) {
|
|
if (USE_MOCK_FALLBACK) {
|
|
console.warn('[InspectionActions] Stats API 실패, Mock 데이터 사용');
|
|
return { success: true, data: mockStats };
|
|
}
|
|
const errMsg = error ? error.message : `API 오류: ${response?.status || 'no response'}`;
|
|
return { success: false, error: errMsg, __authError: error?.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
if (!result.success) {
|
|
return { success: false, error: result.message || '통계 조회 실패' };
|
|
}
|
|
|
|
const statsApi: InspectionStatsApi = result.data;
|
|
return {
|
|
success: true,
|
|
data: {
|
|
receptionCount: statsApi.reception_count,
|
|
inProgressCount: statsApi.in_progress_count,
|
|
completedCount: statsApi.completed_count,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
if (isNextRedirectError(error)) throw error;
|
|
console.error('[InspectionActions] getInspectionStats error:', error);
|
|
if (USE_MOCK_FALLBACK) return { success: true, data: mockStats };
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 캘린더 스케줄 조회 =====
|
|
|
|
export async function getInspectionCalendar(params?: {
|
|
year?: number;
|
|
month?: number;
|
|
inspector?: string;
|
|
status?: InspectionStatus | '전체';
|
|
}): Promise<{
|
|
success: boolean;
|
|
data: InspectionCalendarItem[];
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const searchParams = new URLSearchParams();
|
|
if (params?.year) searchParams.set('year', String(params.year));
|
|
if (params?.month) searchParams.set('month', String(params.month));
|
|
if (params?.inspector) searchParams.set('inspector', params.inspector);
|
|
if (params?.status && params.status !== '전체') {
|
|
searchParams.set('status', mapFrontendStatus(params.status));
|
|
}
|
|
|
|
const queryString = searchParams.toString();
|
|
const url = `${API_BASE}/calendar${queryString ? `?${queryString}` : ''}`;
|
|
|
|
const { response, error } = await serverFetch(url, { method: 'GET' });
|
|
|
|
if (error || !response || !response.ok) {
|
|
if (USE_MOCK_FALLBACK) {
|
|
console.warn('[InspectionActions] Calendar API 실패, Mock 데이터 사용');
|
|
return { success: true, data: mockCalendarItems };
|
|
}
|
|
const errMsg = error ? error.message : `API 오류: ${response?.status || 'no response'}`;
|
|
return { success: false, data: [], error: errMsg, __authError: error?.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
if (!result.success) {
|
|
return { success: false, data: [], error: result.message || '캘린더 조회 실패' };
|
|
}
|
|
|
|
const items: CalendarItemApi[] = result.data || [];
|
|
return {
|
|
success: true,
|
|
data: items.map((item) => ({
|
|
id: String(item.id),
|
|
startDate: item.start_date,
|
|
endDate: item.end_date,
|
|
inspector: item.inspector,
|
|
siteName: item.site_name,
|
|
status: mapApiStatus(item.status as ProductInspectionApi['status']),
|
|
})),
|
|
};
|
|
} catch (error) {
|
|
if (isNextRedirectError(error)) throw error;
|
|
console.error('[InspectionActions] getInspectionCalendar error:', error);
|
|
if (USE_MOCK_FALLBACK) return { success: true, data: mockCalendarItems };
|
|
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 상세 조회 =====
|
|
|
|
export async function getInspectionById(id: string): Promise<{
|
|
success: boolean;
|
|
data?: ProductInspection;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const url = `${API_BASE}/${id}`;
|
|
const { response, error } = await serverFetch(url, { method: 'GET' });
|
|
|
|
if (error || !response || !response.ok) {
|
|
if (USE_MOCK_FALLBACK) {
|
|
console.warn('[InspectionActions] Detail API 실패, Mock 데이터 사용');
|
|
const mockItem = mockInspections.find(i => i.id === id);
|
|
if (mockItem) return { success: true, data: mockItem };
|
|
return { success: false, error: '해당 데이터를 찾을 수 없습니다.' };
|
|
}
|
|
const errMsg = error ? error.message : `API 오류: ${response?.status || 'no response'}`;
|
|
return { success: false, error: errMsg, __authError: error?.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
if (!result.success || !result.data) {
|
|
return { success: false, error: result.message || '상세 조회 실패' };
|
|
}
|
|
|
|
return { success: true, data: transformApiToFrontend(result.data) };
|
|
} catch (error) {
|
|
if (isNextRedirectError(error)) throw error;
|
|
console.error('[InspectionActions] getInspectionById error:', error);
|
|
if (USE_MOCK_FALLBACK) {
|
|
const mockItem = mockInspections.find(i => i.id === id);
|
|
if (mockItem) return { success: true, data: mockItem };
|
|
}
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 등록 =====
|
|
|
|
export async function createInspection(data: InspectionFormData): Promise<{
|
|
success: boolean;
|
|
data?: ProductInspection;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const apiData = transformFormToApi(data);
|
|
|
|
const { response, error } = await serverFetch(API_BASE, {
|
|
method: 'POST',
|
|
body: JSON.stringify(apiData),
|
|
});
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
if (!response) {
|
|
return { success: false, error: '등록에 실패했습니다.' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '등록에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true, data: transformApiToFrontend(result.data) };
|
|
} catch (error) {
|
|
if (isNextRedirectError(error)) throw error;
|
|
console.error('[InspectionActions] createInspection error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 수정 =====
|
|
|
|
export async function updateInspection(
|
|
id: string,
|
|
data: Partial<InspectionFormData>
|
|
): Promise<{
|
|
success: boolean;
|
|
data?: ProductInspection;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const apiData: Record<string, unknown> = {};
|
|
|
|
if (data.qualityDocNumber !== undefined) apiData.quality_doc_number = data.qualityDocNumber;
|
|
if (data.siteName !== undefined) apiData.site_name = data.siteName;
|
|
if (data.client !== undefined) apiData.client = data.client;
|
|
if (data.manager !== undefined) apiData.manager = data.manager;
|
|
if (data.managerContact !== undefined) apiData.manager_contact = data.managerContact;
|
|
|
|
if (data.constructionSite) {
|
|
apiData.construction_site = {
|
|
site_name: data.constructionSite.siteName,
|
|
land_location: data.constructionSite.landLocation,
|
|
lot_number: data.constructionSite.lotNumber,
|
|
};
|
|
}
|
|
if (data.materialDistributor) {
|
|
apiData.material_distributor = {
|
|
company_name: data.materialDistributor.companyName,
|
|
company_address: data.materialDistributor.companyAddress,
|
|
representative_name: data.materialDistributor.representativeName,
|
|
phone: data.materialDistributor.phone,
|
|
};
|
|
}
|
|
if (data.constructorInfo) {
|
|
apiData.constructor_info = {
|
|
company_name: data.constructorInfo.companyName,
|
|
company_address: data.constructorInfo.companyAddress,
|
|
name: data.constructorInfo.name,
|
|
phone: data.constructorInfo.phone,
|
|
};
|
|
}
|
|
if (data.supervisor) {
|
|
apiData.supervisor = {
|
|
office_name: data.supervisor.officeName,
|
|
office_address: data.supervisor.officeAddress,
|
|
name: data.supervisor.name,
|
|
phone: data.supervisor.phone,
|
|
};
|
|
}
|
|
if (data.scheduleInfo) {
|
|
apiData.schedule_info = {
|
|
visit_request_date: data.scheduleInfo.visitRequestDate,
|
|
start_date: data.scheduleInfo.startDate,
|
|
end_date: data.scheduleInfo.endDate,
|
|
inspector: data.scheduleInfo.inspector,
|
|
site_postal_code: data.scheduleInfo.sitePostalCode,
|
|
site_address: data.scheduleInfo.siteAddress,
|
|
site_address_detail: data.scheduleInfo.siteAddressDetail,
|
|
};
|
|
}
|
|
if (data.orderItems) {
|
|
apiData.order_items = data.orderItems.map((item) => ({
|
|
order_number: item.orderNumber,
|
|
floor: item.floor,
|
|
symbol: item.symbol,
|
|
order_width: item.orderWidth,
|
|
order_height: item.orderHeight,
|
|
construction_width: item.constructionWidth,
|
|
construction_height: item.constructionHeight,
|
|
change_reason: item.changeReason,
|
|
}));
|
|
}
|
|
|
|
const { response, error } = await serverFetch(`${API_BASE}/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(apiData),
|
|
});
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
if (!response) {
|
|
return { success: false, error: '수정에 실패했습니다.' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '수정에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true, data: transformApiToFrontend(result.data) };
|
|
} catch (error) {
|
|
if (isNextRedirectError(error)) throw error;
|
|
console.error('[InspectionActions] updateInspection error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 삭제 =====
|
|
|
|
export async function deleteInspection(id: string): Promise<{
|
|
success: boolean;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const { response, error } = await serverFetch(`${API_BASE}/${id}`, { method: 'DELETE' });
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
if (!response) {
|
|
return { success: false, error: '삭제에 실패했습니다.' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '삭제에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
if (isNextRedirectError(error)) throw error;
|
|
console.error('[InspectionActions] deleteInspection error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 검사 완료 처리 =====
|
|
|
|
export async function completeInspection(
|
|
id: string,
|
|
data?: { result?: '합격' | '불합격' }
|
|
): Promise<{
|
|
success: boolean;
|
|
data?: ProductInspection;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const apiData: Record<string, unknown> = {};
|
|
if (data?.result) {
|
|
apiData.result = data.result === '합격' ? 'pass' : 'fail';
|
|
}
|
|
|
|
const { response, error } = await serverFetch(`${API_BASE}/${id}/complete`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify(apiData),
|
|
});
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
if (!response) {
|
|
return { success: false, error: '검사 완료 처리에 실패했습니다.' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '검사 완료 처리에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true, data: transformApiToFrontend(result.data) };
|
|
} catch (error) {
|
|
if (isNextRedirectError(error)) throw error;
|
|
console.error('[InspectionActions] completeInspection error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 수주 선택 목록 조회 =====
|
|
|
|
export async function getOrderSelectList(params?: {
|
|
q?: string;
|
|
}): Promise<{
|
|
success: boolean;
|
|
data: OrderSelectItem[];
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const searchParams = new URLSearchParams();
|
|
if (params?.q) searchParams.set('q', params.q);
|
|
|
|
const queryString = searchParams.toString();
|
|
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/orders/select${queryString ? `?${queryString}` : ''}`;
|
|
|
|
const { response, error } = await serverFetch(url, { method: 'GET' });
|
|
|
|
if (error || !response || !response.ok) {
|
|
if (USE_MOCK_FALLBACK) {
|
|
console.warn('[InspectionActions] OrderSelect API 실패, Mock 데이터 사용');
|
|
let filtered = [...mockOrderSelectItems];
|
|
if (params?.q) {
|
|
const q = params.q.toLowerCase();
|
|
filtered = filtered.filter(i =>
|
|
i.orderNumber.toLowerCase().includes(q) || i.siteName.toLowerCase().includes(q)
|
|
);
|
|
}
|
|
return { success: true, data: filtered };
|
|
}
|
|
const errMsg = error ? error.message : `API 오류: ${response?.status || 'no response'}`;
|
|
return { success: false, data: [], error: errMsg, __authError: error?.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
if (!result.success) {
|
|
return { success: false, data: [], error: result.message || '수주 목록 조회 실패' };
|
|
}
|
|
|
|
const items: OrderSelectItemApi[] = result.data || [];
|
|
return {
|
|
success: true,
|
|
data: items.map((item) => ({
|
|
id: String(item.id),
|
|
orderNumber: item.order_number,
|
|
siteName: item.site_name,
|
|
deliveryDate: item.delivery_date,
|
|
locationCount: item.location_count,
|
|
})),
|
|
};
|
|
} catch (error) {
|
|
if (isNextRedirectError(error)) throw error;
|
|
console.error('[InspectionActions] getOrderSelectList error:', error);
|
|
if (USE_MOCK_FALLBACK) return { success: true, data: mockOrderSelectItems };
|
|
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|