- createOrderFromQuote(): 견적→수주 변환 API 호출 - createProductionOrder(): 생산지시 생성 API 호출 - WorkOrder 타입 및 변환 함수 추가 - 변경 내역 문서 작성
811 lines
22 KiB
TypeScript
811 lines
22 KiB
TypeScript
'use server';
|
|
|
|
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
|
|
|
// ============================================================================
|
|
// API 타입 정의
|
|
// ============================================================================
|
|
|
|
interface ApiOrder {
|
|
id: number;
|
|
tenant_id: number;
|
|
quote_id: number | null;
|
|
order_no: string;
|
|
order_type_code: string;
|
|
status_code: string;
|
|
category_code: string | null;
|
|
client_id: number | null;
|
|
client_name: string | null;
|
|
client_contact: string | null;
|
|
site_name: string | null;
|
|
quantity: number;
|
|
supply_amount: number;
|
|
tax_amount: number;
|
|
total_amount: number;
|
|
discount_rate: number;
|
|
discount_amount: number;
|
|
delivery_date: string | null;
|
|
delivery_method_code: string | null;
|
|
received_at: string | null;
|
|
memo: string | null;
|
|
remarks: string | null;
|
|
note: string | null;
|
|
created_by: number | null;
|
|
updated_by: number | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
client?: ApiClient | null;
|
|
items?: ApiOrderItem[];
|
|
quote?: ApiQuote | null;
|
|
}
|
|
|
|
interface ApiOrderItem {
|
|
id: number;
|
|
order_id: number;
|
|
item_id: number | null;
|
|
item_name: string;
|
|
specification: string | null;
|
|
quantity: number;
|
|
unit: string | null;
|
|
unit_price: number;
|
|
supply_amount: number;
|
|
tax_amount: number;
|
|
total_amount: number;
|
|
sort_order: number;
|
|
}
|
|
|
|
interface ApiClient {
|
|
id: number;
|
|
name: string;
|
|
business_no?: string;
|
|
representative?: string;
|
|
phone?: string;
|
|
email?: string;
|
|
}
|
|
|
|
interface ApiQuote {
|
|
id: number;
|
|
quote_no: string;
|
|
quote_number?: string;
|
|
site_name: string | null;
|
|
}
|
|
|
|
interface ApiWorkOrder {
|
|
id: number;
|
|
tenant_id: number;
|
|
work_order_no: string;
|
|
sales_order_id: number;
|
|
project_name: string | null;
|
|
process_type: string;
|
|
status: string;
|
|
assignee_id: number | null;
|
|
team_id: number | null;
|
|
scheduled_date: string | null;
|
|
memo: string | null;
|
|
is_active: boolean;
|
|
created_at: string;
|
|
updated_at: string;
|
|
assignee?: { id: number; name: string } | null;
|
|
team?: { id: number; name: string } | null;
|
|
}
|
|
|
|
interface ApiProductionOrderResponse {
|
|
work_order: ApiWorkOrder;
|
|
order: ApiOrder;
|
|
}
|
|
|
|
interface ApiOrderStats {
|
|
total: number;
|
|
draft: number;
|
|
confirmed: number;
|
|
in_progress: number;
|
|
completed: number;
|
|
cancelled: number;
|
|
total_amount: number;
|
|
confirmed_amount: number;
|
|
}
|
|
|
|
interface ApiResponse<T> {
|
|
success: boolean;
|
|
message: string;
|
|
data: T;
|
|
}
|
|
|
|
interface PaginatedResponse<T> {
|
|
current_page: number;
|
|
data: T[];
|
|
last_page: number;
|
|
per_page: number;
|
|
total: number;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Frontend 타입 정의
|
|
// ============================================================================
|
|
|
|
// 수주 상태 타입 (API와 매핑)
|
|
export type OrderStatus =
|
|
| 'order_registered' // DRAFT
|
|
| 'order_confirmed' // CONFIRMED
|
|
| 'production_ordered' // IN_PROGRESS
|
|
| 'in_production' // IN_PROGRESS (세부)
|
|
| 'rework' // IN_PROGRESS (세부)
|
|
| 'work_completed' // IN_PROGRESS (세부)
|
|
| 'shipped' // COMPLETED
|
|
| 'cancelled'; // CANCELLED
|
|
|
|
export interface Order {
|
|
id: string;
|
|
lotNumber: string; // order_no
|
|
quoteNumber: string; // quote.quote_no
|
|
quoteId?: number;
|
|
orderDate: string; // received_at
|
|
client: string; // client_name
|
|
clientId?: number;
|
|
siteName: string; // site_name
|
|
status: OrderStatus;
|
|
statusCode: string; // 원본 status_code
|
|
expectedShipDate?: string; // delivery_date
|
|
deliveryMethod?: string; // delivery_method_code
|
|
amount: number; // total_amount
|
|
supplyAmount: number;
|
|
taxAmount: number;
|
|
itemCount: number; // items.length
|
|
hasReceivable?: boolean; // 미수 여부 (추후 구현)
|
|
memo?: string;
|
|
remarks?: string;
|
|
note?: string;
|
|
items?: OrderItem[];
|
|
}
|
|
|
|
export interface OrderItem {
|
|
id: string;
|
|
itemId?: number;
|
|
itemName: string;
|
|
specification?: string;
|
|
quantity: number;
|
|
unit?: string;
|
|
unitPrice: number;
|
|
supplyAmount: number;
|
|
taxAmount: number;
|
|
totalAmount: number;
|
|
sortOrder: number;
|
|
}
|
|
|
|
export interface OrderFormData {
|
|
orderTypeCode?: string;
|
|
categoryCode?: string;
|
|
clientId?: number;
|
|
clientName?: string;
|
|
clientContact?: string;
|
|
siteName?: string;
|
|
supplyAmount?: number;
|
|
taxAmount?: number;
|
|
totalAmount?: number;
|
|
discountRate?: number;
|
|
discountAmount?: number;
|
|
deliveryDate?: string;
|
|
deliveryMethodCode?: string;
|
|
receivedAt?: string;
|
|
memo?: string;
|
|
remarks?: string;
|
|
note?: string;
|
|
items?: OrderItemFormData[];
|
|
}
|
|
|
|
export interface OrderItemFormData {
|
|
itemId?: number;
|
|
itemName: string;
|
|
specification?: string;
|
|
quantity: number;
|
|
unit?: string;
|
|
unitPrice: number;
|
|
}
|
|
|
|
export interface OrderStats {
|
|
total: number;
|
|
draft: number;
|
|
confirmed: number;
|
|
inProgress: number;
|
|
completed: number;
|
|
cancelled: number;
|
|
totalAmount: number;
|
|
confirmedAmount: number;
|
|
}
|
|
|
|
// 견적→수주 변환용
|
|
export interface CreateFromQuoteData {
|
|
deliveryDate?: string;
|
|
memo?: string;
|
|
}
|
|
|
|
// 생산지시 생성용
|
|
export interface CreateProductionOrderData {
|
|
processType?: 'screen' | 'slat' | 'bending';
|
|
assigneeId?: number;
|
|
teamId?: number;
|
|
scheduledDate?: string;
|
|
memo?: string;
|
|
}
|
|
|
|
// 생산지시(작업지시) 타입
|
|
export interface WorkOrder {
|
|
id: string;
|
|
workOrderNo: string;
|
|
salesOrderId: number;
|
|
projectName: string | null;
|
|
processType: string;
|
|
status: string;
|
|
assigneeId?: number;
|
|
assigneeName?: string;
|
|
teamId?: number;
|
|
teamName?: string;
|
|
scheduledDate?: string;
|
|
memo?: string;
|
|
isActive: boolean;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
// 생산지시 생성 결과
|
|
export interface ProductionOrderResult {
|
|
workOrder: WorkOrder;
|
|
order: Order;
|
|
}
|
|
|
|
// ============================================================================
|
|
// 상태 매핑
|
|
// ============================================================================
|
|
|
|
const API_TO_FRONTEND_STATUS: Record<string, OrderStatus> = {
|
|
'DRAFT': 'order_registered',
|
|
'CONFIRMED': 'order_confirmed',
|
|
'IN_PROGRESS': 'production_ordered',
|
|
'COMPLETED': 'shipped',
|
|
'CANCELLED': 'cancelled',
|
|
};
|
|
|
|
const FRONTEND_TO_API_STATUS: Record<OrderStatus, string> = {
|
|
'order_registered': 'DRAFT',
|
|
'order_confirmed': 'CONFIRMED',
|
|
'production_ordered': 'IN_PROGRESS',
|
|
'in_production': 'IN_PROGRESS',
|
|
'rework': 'IN_PROGRESS',
|
|
'work_completed': 'IN_PROGRESS',
|
|
'shipped': 'COMPLETED',
|
|
'cancelled': 'CANCELLED',
|
|
};
|
|
|
|
// ============================================================================
|
|
// 데이터 변환 함수
|
|
// ============================================================================
|
|
|
|
function transformApiToFrontend(apiData: ApiOrder): Order {
|
|
return {
|
|
id: String(apiData.id),
|
|
lotNumber: apiData.order_no,
|
|
quoteNumber: apiData.quote?.quote_no || '',
|
|
quoteId: apiData.quote_id ?? undefined,
|
|
orderDate: apiData.received_at || apiData.created_at.split('T')[0],
|
|
client: apiData.client_name || apiData.client?.name || '',
|
|
clientId: apiData.client_id ?? undefined,
|
|
siteName: apiData.site_name || '',
|
|
status: API_TO_FRONTEND_STATUS[apiData.status_code] || 'order_registered',
|
|
statusCode: apiData.status_code,
|
|
expectedShipDate: apiData.delivery_date ?? undefined,
|
|
deliveryMethod: apiData.delivery_method_code ?? undefined,
|
|
amount: apiData.total_amount,
|
|
supplyAmount: apiData.supply_amount,
|
|
taxAmount: apiData.tax_amount,
|
|
itemCount: apiData.items?.length || 0,
|
|
hasReceivable: false, // 추후 구현
|
|
memo: apiData.memo ?? undefined,
|
|
remarks: apiData.remarks ?? undefined,
|
|
note: apiData.note ?? undefined,
|
|
items: apiData.items?.map(transformItemApiToFrontend),
|
|
};
|
|
}
|
|
|
|
function transformItemApiToFrontend(apiItem: ApiOrderItem): OrderItem {
|
|
return {
|
|
id: String(apiItem.id),
|
|
itemId: apiItem.item_id ?? undefined,
|
|
itemName: apiItem.item_name,
|
|
specification: apiItem.specification ?? undefined,
|
|
quantity: apiItem.quantity,
|
|
unit: apiItem.unit ?? undefined,
|
|
unitPrice: apiItem.unit_price,
|
|
supplyAmount: apiItem.supply_amount,
|
|
taxAmount: apiItem.tax_amount,
|
|
totalAmount: apiItem.total_amount,
|
|
sortOrder: apiItem.sort_order,
|
|
};
|
|
}
|
|
|
|
function transformFrontendToApi(data: OrderFormData): Record<string, unknown> {
|
|
return {
|
|
order_type_code: data.orderTypeCode || 'ORDER',
|
|
category_code: data.categoryCode || null,
|
|
client_id: data.clientId || null,
|
|
client_name: data.clientName || null,
|
|
client_contact: data.clientContact || null,
|
|
site_name: data.siteName || null,
|
|
supply_amount: data.supplyAmount || 0,
|
|
tax_amount: data.taxAmount || 0,
|
|
total_amount: data.totalAmount || 0,
|
|
discount_rate: data.discountRate || 0,
|
|
discount_amount: data.discountAmount || 0,
|
|
delivery_date: data.deliveryDate || null,
|
|
delivery_method_code: data.deliveryMethodCode || null,
|
|
received_at: data.receivedAt || null,
|
|
memo: data.memo || null,
|
|
remarks: data.remarks || null,
|
|
note: data.note || null,
|
|
items: data.items?.map((item) => ({
|
|
item_id: item.itemId || null,
|
|
item_name: item.itemName,
|
|
specification: item.specification || null,
|
|
quantity: item.quantity,
|
|
unit: item.unit || null,
|
|
unit_price: item.unitPrice,
|
|
})) || [],
|
|
};
|
|
}
|
|
|
|
function transformWorkOrderApiToFrontend(apiData: ApiWorkOrder): WorkOrder {
|
|
return {
|
|
id: String(apiData.id),
|
|
workOrderNo: apiData.work_order_no,
|
|
salesOrderId: apiData.sales_order_id,
|
|
projectName: apiData.project_name,
|
|
processType: apiData.process_type,
|
|
status: apiData.status,
|
|
assigneeId: apiData.assignee_id ?? undefined,
|
|
assigneeName: apiData.assignee?.name ?? undefined,
|
|
teamId: apiData.team_id ?? undefined,
|
|
teamName: apiData.team?.name ?? undefined,
|
|
scheduledDate: apiData.scheduled_date ?? undefined,
|
|
memo: apiData.memo ?? undefined,
|
|
isActive: apiData.is_active,
|
|
createdAt: apiData.created_at,
|
|
updatedAt: apiData.updated_at,
|
|
};
|
|
}
|
|
|
|
// ============================================================================
|
|
// API 함수
|
|
// ============================================================================
|
|
|
|
/**
|
|
* 수주 목록 조회
|
|
*/
|
|
export async function getOrders(params?: {
|
|
page?: number;
|
|
size?: number;
|
|
q?: string;
|
|
status?: string;
|
|
order_type?: string;
|
|
client_id?: number;
|
|
date_from?: string;
|
|
date_to?: string;
|
|
}): Promise<{
|
|
success: boolean;
|
|
data?: { items: Order[]; total: number; page: number; totalPages: number };
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const searchParams = new URLSearchParams();
|
|
|
|
if (params?.page) searchParams.set('page', String(params.page));
|
|
if (params?.size) searchParams.set('size', String(params.size));
|
|
if (params?.q) searchParams.set('q', params.q);
|
|
if (params?.status) {
|
|
// Frontend status를 API status로 변환
|
|
const apiStatus = FRONTEND_TO_API_STATUS[params.status as OrderStatus];
|
|
if (apiStatus) searchParams.set('status', apiStatus);
|
|
}
|
|
if (params?.order_type) searchParams.set('order_type', params.order_type);
|
|
if (params?.client_id) searchParams.set('client_id', String(params.client_id));
|
|
if (params?.date_from) searchParams.set('date_from', params.date_from);
|
|
if (params?.date_to) searchParams.set('date_to', params.date_to);
|
|
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/orders?${searchParams.toString()}`,
|
|
{ method: 'GET', cache: 'no-store' }
|
|
);
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '목록 조회에 실패했습니다.' };
|
|
}
|
|
|
|
const result: ApiResponse<PaginatedResponse<ApiOrder>> = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '목록 조회에 실패했습니다.' };
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
items: result.data.data.map(transformApiToFrontend),
|
|
total: result.data.total,
|
|
page: result.data.current_page,
|
|
totalPages: result.data.last_page,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
console.error('[getOrders] Error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 수주 상세 조회
|
|
*/
|
|
export async function getOrderById(id: string): Promise<{
|
|
success: boolean;
|
|
data?: Order;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/orders/${id}`,
|
|
{ method: 'GET', cache: 'no-store' }
|
|
);
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '조회에 실패했습니다.' };
|
|
}
|
|
|
|
const result: ApiResponse<ApiOrder> = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '조회에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true, data: transformApiToFrontend(result.data) };
|
|
} catch (error) {
|
|
console.error('[getOrderById] Error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 수주 생성
|
|
*/
|
|
export async function createOrder(data: OrderFormData): Promise<{
|
|
success: boolean;
|
|
data?: Order;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const apiData = transformFrontendToApi(data);
|
|
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/orders`,
|
|
{ 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: ApiResponse<ApiOrder> = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '등록에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true, data: transformApiToFrontend(result.data) };
|
|
} catch (error) {
|
|
console.error('[createOrder] Error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 수주 수정
|
|
*/
|
|
export async function updateOrder(id: string, data: OrderFormData): Promise<{
|
|
success: boolean;
|
|
data?: Order;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const apiData = transformFrontendToApi(data);
|
|
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/orders/${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: ApiResponse<ApiOrder> = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '수정에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true, data: transformApiToFrontend(result.data) };
|
|
} catch (error) {
|
|
console.error('[updateOrder] Error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 수주 삭제
|
|
*/
|
|
export async function deleteOrder(id: string): Promise<{
|
|
success: boolean;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/orders/${id}`,
|
|
{ method: 'DELETE' }
|
|
);
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '삭제에 실패했습니다.' };
|
|
}
|
|
|
|
const result: ApiResponse<string> = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '삭제에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error('[deleteOrder] Error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 수주 상태 변경
|
|
*/
|
|
export async function updateOrderStatus(id: string, status: OrderStatus): Promise<{
|
|
success: boolean;
|
|
data?: Order;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const apiStatus = FRONTEND_TO_API_STATUS[status];
|
|
if (!apiStatus) {
|
|
return { success: false, error: '유효하지 않은 상태입니다.' };
|
|
}
|
|
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/orders/${id}/status`,
|
|
{ method: 'PATCH', body: JSON.stringify({ status: apiStatus }) }
|
|
);
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '상태 변경에 실패했습니다.' };
|
|
}
|
|
|
|
const result: ApiResponse<ApiOrder> = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '상태 변경에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true, data: transformApiToFrontend(result.data) };
|
|
} catch (error) {
|
|
console.error('[updateOrderStatus] Error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 수주 통계 조회
|
|
*/
|
|
export async function getOrderStats(): Promise<{
|
|
success: boolean;
|
|
data?: OrderStats;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/orders/stats`,
|
|
{ method: 'GET', cache: 'no-store' }
|
|
);
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '통계 조회에 실패했습니다.' };
|
|
}
|
|
|
|
const result: ApiResponse<ApiOrderStats> = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '통계 조회에 실패했습니다.' };
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
total: result.data.total,
|
|
draft: result.data.draft,
|
|
confirmed: result.data.confirmed,
|
|
inProgress: result.data.in_progress,
|
|
completed: result.data.completed,
|
|
cancelled: result.data.cancelled,
|
|
totalAmount: result.data.total_amount,
|
|
confirmedAmount: result.data.confirmed_amount,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
console.error('[getOrderStats] Error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 수주 일괄 삭제
|
|
*/
|
|
export async function deleteOrders(ids: string[]): Promise<{
|
|
success: boolean;
|
|
deletedCount?: number;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
// 순차적으로 삭제 (API에 bulk delete가 없으므로)
|
|
let deletedCount = 0;
|
|
const errors: string[] = [];
|
|
|
|
for (const id of ids) {
|
|
const result = await deleteOrder(id);
|
|
if (result.success) {
|
|
deletedCount++;
|
|
} else {
|
|
errors.push(result.error || `ID ${id} 삭제 실패`);
|
|
}
|
|
}
|
|
|
|
if (deletedCount === 0 && errors.length > 0) {
|
|
return { success: false, error: errors[0] };
|
|
}
|
|
|
|
return { success: true, deletedCount };
|
|
} catch (error) {
|
|
console.error('[deleteOrders] Error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 견적에서 수주 생성
|
|
*/
|
|
export async function createOrderFromQuote(
|
|
quoteId: number,
|
|
data?: CreateFromQuoteData
|
|
): Promise<{
|
|
success: boolean;
|
|
data?: Order;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const apiData: Record<string, unknown> = {};
|
|
if (data?.deliveryDate) apiData.delivery_date = data.deliveryDate;
|
|
if (data?.memo) apiData.memo = data.memo;
|
|
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/orders/from-quote/${quoteId}`,
|
|
{ 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: ApiResponse<ApiOrder> = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '수주 생성에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true, data: transformApiToFrontend(result.data) };
|
|
} catch (error) {
|
|
console.error('[createOrderFromQuote] Error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 생산지시 생성
|
|
*/
|
|
export async function createProductionOrder(
|
|
orderId: string,
|
|
data?: CreateProductionOrderData
|
|
): Promise<{
|
|
success: boolean;
|
|
data?: ProductionOrderResult;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const apiData: Record<string, unknown> = {};
|
|
if (data?.processType) apiData.process_type = data.processType;
|
|
if (data?.assigneeId) apiData.assignee_id = data.assigneeId;
|
|
if (data?.teamId) apiData.team_id = data.teamId;
|
|
if (data?.scheduledDate) apiData.scheduled_date = data.scheduledDate;
|
|
if (data?.memo) apiData.memo = data.memo;
|
|
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/orders/${orderId}/production-order`,
|
|
{ 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: ApiResponse<ApiProductionOrderResponse> = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '생산지시 생성에 실패했습니다.' };
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
workOrder: transformWorkOrderApiToFrontend(result.data.work_order),
|
|
order: transformApiToFrontend(result.data.order),
|
|
},
|
|
};
|
|
} catch (error) {
|
|
console.error('[createProductionOrder] Error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|