- Next.js Server Actions는 undefined 직렬화 불가 - bendingLot: undefined → null - rawLotNo/fabricLotNo/material: undefined → null (via ??) - itemId: undefined → null
841 lines
24 KiB
TypeScript
841 lines
24 KiB
TypeScript
'use server';
|
|
|
|
import { executeServerAction } from '@/lib/api/execute-server-action';
|
|
import { buildApiUrl } from '@/lib/api/query-params';
|
|
import type { PaginatedApiResponse } from '@/lib/api/types';
|
|
import { formatDate } from '@/lib/utils/date';
|
|
|
|
// ============================================================================
|
|
// API 타입 정의
|
|
// ============================================================================
|
|
|
|
interface ApiStockOrder {
|
|
id: number;
|
|
tenant_id: number;
|
|
order_no: string;
|
|
order_type_code: string;
|
|
status_code: string;
|
|
site_name: string | null;
|
|
quantity: number;
|
|
supply_amount: number;
|
|
tax_amount: number;
|
|
total_amount: number;
|
|
memo: string | null;
|
|
remarks: string | null;
|
|
options: {
|
|
production_reason?: string;
|
|
target_stock_qty?: number;
|
|
manager_name?: string;
|
|
bending_lot?: {
|
|
lot_number?: string;
|
|
prod_code?: string;
|
|
spec_code?: string;
|
|
length_code?: string;
|
|
raw_lot_no?: string;
|
|
fabric_lot_no?: string;
|
|
material?: string;
|
|
};
|
|
} | null;
|
|
created_by: number | null;
|
|
updated_by: number | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
items?: ApiStockOrderItem[];
|
|
}
|
|
|
|
interface ApiStockOrderItem {
|
|
id: number;
|
|
order_id: number;
|
|
item_id: number | null;
|
|
item_code: string | 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 ApiStockOrderStats {
|
|
total: number;
|
|
draft: number;
|
|
confirmed: number;
|
|
in_progress: number;
|
|
completed: number;
|
|
cancelled: number;
|
|
total_amount: number;
|
|
confirmed_amount: number;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Frontend 타입 정의
|
|
// ============================================================================
|
|
|
|
export type StockStatus =
|
|
| 'draft'
|
|
| 'confirmed'
|
|
| 'in_progress'
|
|
| 'in_production'
|
|
| 'produced'
|
|
| 'completed'
|
|
| 'cancelled';
|
|
|
|
export interface StockOrder {
|
|
id: string;
|
|
orderNo: string;
|
|
statusCode: string;
|
|
status: StockStatus;
|
|
siteName: string;
|
|
quantity: number;
|
|
memo: string;
|
|
remarks: string;
|
|
productionReason: string;
|
|
targetStockQty: number;
|
|
manager: string;
|
|
itemCount: number;
|
|
itemSummary: string;
|
|
createdAt: string;
|
|
items: StockOrderItem[];
|
|
bendingLot?: {
|
|
lotNumber: string;
|
|
prodCode: string;
|
|
specCode: string;
|
|
lengthCode: string;
|
|
rawLotNo?: string | null;
|
|
fabricLotNo?: string | null;
|
|
material?: string | null;
|
|
} | null;
|
|
}
|
|
|
|
export interface StockOrderItem {
|
|
id: string;
|
|
itemId?: number | null;
|
|
itemCode: string;
|
|
itemName: string;
|
|
specification: string;
|
|
quantity: number;
|
|
unit: string;
|
|
unitPrice: number;
|
|
supplyAmount: number;
|
|
taxAmount: number;
|
|
totalAmount: number;
|
|
sortOrder: number;
|
|
}
|
|
|
|
export interface StockOrderFormData {
|
|
orderTypeCode: string;
|
|
memo: string;
|
|
remarks: string;
|
|
productionReason: string;
|
|
targetStockQty: number;
|
|
items: StockOrderItemFormData[];
|
|
}
|
|
|
|
export interface StockOrderItemFormData {
|
|
itemId?: number;
|
|
itemCode?: string;
|
|
itemName: string;
|
|
specification?: string;
|
|
quantity: number;
|
|
unit?: string;
|
|
unitPrice: number;
|
|
}
|
|
|
|
export interface StockOrderStats {
|
|
total: number;
|
|
draft: number;
|
|
confirmed: number;
|
|
inProgress: number;
|
|
completed: number;
|
|
cancelled: number;
|
|
}
|
|
|
|
// ============================================================================
|
|
// 상태 매핑
|
|
// ============================================================================
|
|
|
|
const API_TO_FRONTEND_STATUS: Record<string, StockStatus> = {
|
|
'DRAFT': 'draft',
|
|
'CONFIRMED': 'confirmed',
|
|
'IN_PROGRESS': 'in_progress',
|
|
'IN_PRODUCTION': 'in_production',
|
|
'PRODUCED': 'produced',
|
|
'COMPLETED': 'completed',
|
|
'CANCELLED': 'cancelled',
|
|
};
|
|
|
|
const FRONTEND_TO_API_STATUS: Record<StockStatus, string> = {
|
|
'draft': 'DRAFT',
|
|
'confirmed': 'CONFIRMED',
|
|
'in_progress': 'IN_PROGRESS',
|
|
'in_production': 'IN_PRODUCTION',
|
|
'produced': 'PRODUCED',
|
|
'completed': 'COMPLETED',
|
|
'cancelled': 'CANCELLED',
|
|
};
|
|
|
|
// ============================================================================
|
|
// 데이터 변환 함수
|
|
// ============================================================================
|
|
|
|
function transformApiToFrontend(apiData: ApiStockOrder): StockOrder {
|
|
const items = apiData.items?.map(transformItemApiToFrontend) || [];
|
|
const firstItemName = items[0]?.itemName || '';
|
|
const extraCount = items.length > 1 ? ` 외 ${items.length - 1}건` : '';
|
|
|
|
const bendingLotData = apiData.options?.bending_lot;
|
|
|
|
return {
|
|
id: String(apiData.id),
|
|
orderNo: apiData.order_no,
|
|
statusCode: apiData.status_code,
|
|
status: API_TO_FRONTEND_STATUS[apiData.status_code] || 'draft',
|
|
siteName: apiData.site_name || '재고생산',
|
|
quantity: Math.floor(Number(apiData.quantity) || 0),
|
|
memo: apiData.memo || '',
|
|
remarks: apiData.remarks || '',
|
|
productionReason: apiData.options?.production_reason || '',
|
|
targetStockQty: apiData.options?.target_stock_qty || 0,
|
|
manager: apiData.options?.manager_name || '',
|
|
itemCount: items.length,
|
|
itemSummary: firstItemName ? `${firstItemName}${extraCount}` : '-',
|
|
createdAt: formatDate(apiData.created_at),
|
|
items,
|
|
bendingLot: bendingLotData ? {
|
|
lotNumber: bendingLotData.lot_number || '',
|
|
prodCode: bendingLotData.prod_code || '',
|
|
specCode: bendingLotData.spec_code || '',
|
|
lengthCode: bendingLotData.length_code || '',
|
|
rawLotNo: bendingLotData.raw_lot_no ?? null,
|
|
fabricLotNo: bendingLotData.fabric_lot_no ?? null,
|
|
material: bendingLotData.material ?? null,
|
|
} : null,
|
|
};
|
|
}
|
|
|
|
function transformItemApiToFrontend(apiItem: ApiStockOrderItem): StockOrderItem {
|
|
return {
|
|
id: String(apiItem.id),
|
|
itemId: apiItem.item_id ?? null,
|
|
itemCode: apiItem.item_code || '',
|
|
itemName: apiItem.item_name,
|
|
specification: apiItem.specification || '',
|
|
quantity: Math.floor(Number(apiItem.quantity) || 0),
|
|
unit: apiItem.unit || 'EA',
|
|
unitPrice: Number(apiItem.unit_price) || 0,
|
|
supplyAmount: Number(apiItem.supply_amount) || 0,
|
|
taxAmount: Number(apiItem.tax_amount) || 0,
|
|
totalAmount: Number(apiItem.total_amount) || 0,
|
|
sortOrder: apiItem.sort_order,
|
|
};
|
|
}
|
|
|
|
function transformFrontendToApi(data: StockOrderFormData): Record<string, unknown> {
|
|
return {
|
|
order_type_code: 'STOCK',
|
|
memo: data.memo || null,
|
|
remarks: data.remarks || null,
|
|
options: {
|
|
production_reason: data.productionReason || null,
|
|
target_stock_qty: data.targetStockQty || null,
|
|
},
|
|
// STOCK 전용: 불필요 필드 명시적 null
|
|
client_id: null,
|
|
client_name: null,
|
|
site_name: null, // API 자동 설정 '재고생산'
|
|
delivery_date: null,
|
|
delivery_method_code: null,
|
|
discount_rate: 0,
|
|
discount_amount: 0,
|
|
supply_amount: 0,
|
|
tax_amount: 0,
|
|
total_amount: 0,
|
|
items: data.items.map((item) => {
|
|
const quantity = Number(item.quantity) || 0;
|
|
const unitPrice = Number(item.unitPrice) || 0;
|
|
const supplyAmount = quantity * unitPrice;
|
|
const taxAmount = Math.round(supplyAmount * 0.1);
|
|
return {
|
|
item_id: item.itemId || null,
|
|
item_code: item.itemCode || null,
|
|
item_name: item.itemName,
|
|
specification: item.specification || null,
|
|
quantity,
|
|
unit: item.unit || 'EA',
|
|
unit_price: unitPrice,
|
|
supply_amount: supplyAmount,
|
|
tax_amount: taxAmount,
|
|
total_amount: supplyAmount + taxAmount,
|
|
};
|
|
}),
|
|
};
|
|
}
|
|
|
|
// ============================================================================
|
|
// API 함수
|
|
// ============================================================================
|
|
|
|
/**
|
|
* 재고생산 목록 조회
|
|
*/
|
|
export async function getStockOrders(params?: {
|
|
page?: number;
|
|
size?: number;
|
|
q?: string;
|
|
status?: string;
|
|
date_from?: string;
|
|
date_to?: string;
|
|
}): Promise<{
|
|
success: boolean;
|
|
data?: { items: StockOrder[]; total: number; page: number; totalPages: number };
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
const apiStatus = params?.status ? FRONTEND_TO_API_STATUS[params.status as StockStatus] : undefined;
|
|
const result = await executeServerAction<PaginatedApiResponse<ApiStockOrder>>({
|
|
url: buildApiUrl('/api/v1/orders', {
|
|
order_type: 'STOCK',
|
|
page: params?.page,
|
|
size: params?.size,
|
|
q: params?.q,
|
|
status: apiStatus,
|
|
date_from: params?.date_from,
|
|
date_to: params?.date_to,
|
|
}),
|
|
errorMessage: '재고생산 목록 조회에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
if (!result.success || !result.data) return { success: false, error: result.error };
|
|
return {
|
|
success: true,
|
|
data: {
|
|
items: result.data.data.map(transformApiToFrontend),
|
|
total: result.data.total,
|
|
page: result.data.current_page,
|
|
totalPages: result.data.last_page,
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 재고생산 상세 조회
|
|
*/
|
|
export async function getStockOrderById(id: string): Promise<{
|
|
success: boolean;
|
|
data?: StockOrder;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl(`/api/v1/orders/${id}`),
|
|
transform: (data: ApiStockOrder) => transformApiToFrontend(data),
|
|
errorMessage: '재고생산 조회에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|
|
|
|
/**
|
|
* 재고생산 생성
|
|
*/
|
|
export async function createStockOrder(data: StockOrderFormData): Promise<{
|
|
success: boolean;
|
|
data?: StockOrder;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
const apiData = transformFrontendToApi(data);
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl('/api/v1/orders'),
|
|
method: 'POST',
|
|
body: apiData,
|
|
transform: (d: ApiStockOrder) => transformApiToFrontend(d),
|
|
errorMessage: '재고생산 등록에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|
|
|
|
/**
|
|
* 재고생산 수정
|
|
*/
|
|
export async function updateStockOrder(id: string, data: StockOrderFormData): Promise<{
|
|
success: boolean;
|
|
data?: StockOrder;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
const apiData = transformFrontendToApi(data);
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl(`/api/v1/orders/${id}`),
|
|
method: 'PUT',
|
|
body: apiData,
|
|
transform: (d: ApiStockOrder) => transformApiToFrontend(d),
|
|
errorMessage: '재고생산 수정에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|
|
|
|
/**
|
|
* 재고생산 삭제
|
|
*/
|
|
export async function deleteStockOrder(id: string): Promise<{
|
|
success: boolean;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl(`/api/v1/orders/${id}`),
|
|
method: 'DELETE',
|
|
errorMessage: '재고생산 삭제에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, error: result.error };
|
|
}
|
|
|
|
/**
|
|
* 재고생산 일괄 삭제
|
|
*/
|
|
export async function deleteStockOrders(ids: string[]): Promise<{
|
|
success: boolean;
|
|
deletedCount?: number;
|
|
skippedCount?: number;
|
|
skippedIds?: number[];
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
interface BulkDeleteResponse {
|
|
deleted_count: number;
|
|
skipped_count: number;
|
|
skipped_ids: number[];
|
|
}
|
|
const result = await executeServerAction<BulkDeleteResponse>({
|
|
url: buildApiUrl('/api/v1/orders/bulk'),
|
|
method: 'DELETE',
|
|
body: { ids: ids.map(Number) },
|
|
errorMessage: '재고생산 일괄 삭제에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
if (!result.success || !result.data) return { success: false, error: result.error };
|
|
return {
|
|
success: true,
|
|
deletedCount: result.data.deleted_count,
|
|
skippedCount: result.data.skipped_count,
|
|
skippedIds: result.data.skipped_ids,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 재고생산 상태 변경
|
|
*/
|
|
export async function updateStockOrderStatus(id: string, status: StockStatus): Promise<{
|
|
success: boolean;
|
|
data?: StockOrder;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
const apiStatus = FRONTEND_TO_API_STATUS[status];
|
|
if (!apiStatus) {
|
|
return { success: false, error: '유효하지 않은 상태입니다.' };
|
|
}
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl(`/api/v1/orders/${id}/status`),
|
|
method: 'PATCH',
|
|
body: { status: apiStatus },
|
|
transform: (d: ApiStockOrder) => transformApiToFrontend(d),
|
|
errorMessage: '상태 변경에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|
|
|
|
/**
|
|
* 재고생산 통계 조회
|
|
*/
|
|
export async function getStockOrderStats(): Promise<{
|
|
success: boolean;
|
|
data?: StockOrderStats;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
const result = await executeServerAction<ApiStockOrderStats>({
|
|
url: buildApiUrl('/api/v1/orders/stats', { order_type: 'STOCK' }),
|
|
errorMessage: '재고생산 통계 조회에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
if (!result.success || !result.data) return { success: false, error: result.error };
|
|
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,
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 재고생산 → 생산지시 생성
|
|
*/
|
|
export async function createStockProductionOrder(
|
|
orderId: string,
|
|
data?: { priority?: string; memo?: string }
|
|
): Promise<{
|
|
success: boolean;
|
|
data?: StockOrder;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
const apiData: Record<string, unknown> = {};
|
|
if (data?.priority) apiData.priority = data.priority;
|
|
if (data?.memo) apiData.memo = data.memo;
|
|
|
|
const result = await executeServerAction<{ order: ApiStockOrder }>({
|
|
url: buildApiUrl(`/api/v1/orders/${orderId}/production-order`),
|
|
method: 'POST',
|
|
body: apiData,
|
|
errorMessage: '생산지시 생성에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
if (!result.success || !result.data) return { success: false, error: result.error };
|
|
return { success: true, data: transformApiToFrontend(result.data.order) };
|
|
}
|
|
|
|
// ============================================================================
|
|
// 절곡품 LOT API 타입 정의
|
|
// ============================================================================
|
|
|
|
export interface BendingProduct {
|
|
code: string;
|
|
name: string;
|
|
}
|
|
|
|
export interface BendingSpec {
|
|
code: string;
|
|
name: string;
|
|
products: string[];
|
|
}
|
|
|
|
export interface BendingLength {
|
|
code: string;
|
|
name: string;
|
|
}
|
|
|
|
export interface BendingCodeMap {
|
|
products: BendingProduct[];
|
|
specs: BendingSpec[];
|
|
lengths: {
|
|
smoke_barrier: BendingLength[];
|
|
general: BendingLength[];
|
|
};
|
|
material_map: Record<string, string>;
|
|
}
|
|
|
|
export interface BendingResolvedItem {
|
|
item_id: number;
|
|
item_code: string;
|
|
item_name: string;
|
|
specification: string;
|
|
unit: string;
|
|
expected_code?: string;
|
|
}
|
|
|
|
export interface BendingLotResult {
|
|
lot_base: string;
|
|
lot_number: string;
|
|
date_code: string;
|
|
material: string;
|
|
}
|
|
|
|
export interface BendingLotFormData {
|
|
lot_number: string;
|
|
prod_code: string;
|
|
spec_code: string;
|
|
length_code: string;
|
|
raw_lot_no?: string;
|
|
fabric_lot_no?: string;
|
|
material?: string;
|
|
}
|
|
|
|
export interface MaterialLot {
|
|
id: number;
|
|
lot_no: string;
|
|
supplier_lot: string;
|
|
item_name: string;
|
|
specification: string;
|
|
receiving_qty: string;
|
|
receiving_date: string;
|
|
supplier: string;
|
|
options?: {
|
|
inspection_status?: string;
|
|
inspection_result?: string;
|
|
};
|
|
}
|
|
|
|
// ============================================================================
|
|
// 절곡품 LOT API 함수
|
|
// ============================================================================
|
|
|
|
/**
|
|
* 절곡품 코드맵 조회 (캐스케이딩 드롭다운 옵션)
|
|
*/
|
|
export async function getBendingCodeMap(): Promise<{
|
|
success: boolean;
|
|
data?: BendingCodeMap;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
const result = await executeServerAction<BendingCodeMap>({
|
|
url: buildApiUrl('/api/v1/bending/code-map'),
|
|
errorMessage: '절곡품 코드맵 조회에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|
|
|
|
/**
|
|
* 절곡품 품목 매핑 조회 (드롭다운 3개 선택 후)
|
|
*/
|
|
export async function resolveBendingItem(
|
|
prod: string,
|
|
spec: string,
|
|
length: string
|
|
): Promise<{
|
|
success: boolean;
|
|
data?: BendingResolvedItem;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
const { serverFetch } = await import('@/lib/api/fetch-wrapper');
|
|
const { safeResponseJson } = await import('@/lib/api/safe-json-parse');
|
|
const url = buildApiUrl('/api/v1/bending/resolve-item', { prod, spec, length });
|
|
const { response, error: fetchError } = await serverFetch(url, { method: 'GET', cache: 'no-store' });
|
|
|
|
if (fetchError) {
|
|
return { success: false, error: fetchError.message, __authError: fetchError.__authError };
|
|
}
|
|
if (!response) {
|
|
return { success: false, error: '품목 매핑 조회에 실패했습니다.' };
|
|
}
|
|
|
|
const raw = await safeResponseJson(response) as Record<string, unknown>;
|
|
|
|
if (!response.ok || !raw.success) {
|
|
// NOT_MAPPED: error.expected_code 추출
|
|
const errorObj = raw.error as Record<string, unknown> | undefined;
|
|
const expectedCode = (errorObj?.expected_code as string) || undefined;
|
|
return {
|
|
success: false,
|
|
data: expectedCode ? { expected_code: expectedCode } as BendingResolvedItem : undefined,
|
|
error: (raw.message as string) || '해당 조합에 매핑된 품목이 없습니다.',
|
|
};
|
|
}
|
|
|
|
// 성공: data에 expected_code 포함
|
|
const data = raw.data as BendingResolvedItem;
|
|
return { success: true, data };
|
|
}
|
|
|
|
/**
|
|
* 절곡품 LOT 번호 생성
|
|
*/
|
|
export async function generateBendingLot(
|
|
prodCode: string,
|
|
specCode: string,
|
|
lengthCode: string,
|
|
regDate?: string
|
|
): Promise<{
|
|
success: boolean;
|
|
data?: BendingLotResult;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
const result = await executeServerAction<BendingLotResult>({
|
|
url: buildApiUrl('/api/v1/bending/generate-lot'),
|
|
method: 'POST',
|
|
body: {
|
|
prod_code: prodCode,
|
|
spec_code: specCode,
|
|
length_code: lengthCode,
|
|
reg_date: regDate,
|
|
},
|
|
errorMessage: 'LOT 번호 생성에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|
|
|
|
/**
|
|
* 절곡품 재고생산 저장 (기존 orders API + bending_lot 확장)
|
|
*/
|
|
export async function createBendingStockOrder(params: {
|
|
memo?: string;
|
|
targetStockQty: number;
|
|
bendingLot: BendingLotFormData;
|
|
item: {
|
|
itemId?: number;
|
|
itemCode?: string;
|
|
itemName: string;
|
|
specification?: string;
|
|
quantity: number;
|
|
unit?: string;
|
|
};
|
|
}): Promise<{
|
|
success: boolean;
|
|
data?: StockOrder;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
const apiData = {
|
|
order_type_code: 'STOCK',
|
|
memo: params.memo || null,
|
|
remarks: null,
|
|
options: {
|
|
production_reason: '절곡품 재고생산',
|
|
target_stock_qty: params.targetStockQty || null,
|
|
bending_lot: {
|
|
lot_number: params.bendingLot.lot_number,
|
|
prod_code: params.bendingLot.prod_code,
|
|
spec_code: params.bendingLot.spec_code,
|
|
length_code: params.bendingLot.length_code,
|
|
raw_lot_no: params.bendingLot.raw_lot_no || null,
|
|
fabric_lot_no: params.bendingLot.fabric_lot_no || null,
|
|
material: params.bendingLot.material || null,
|
|
},
|
|
},
|
|
client_id: null,
|
|
client_name: null,
|
|
site_name: null,
|
|
delivery_date: null,
|
|
delivery_method_code: null,
|
|
discount_rate: 0,
|
|
discount_amount: 0,
|
|
supply_amount: 0,
|
|
tax_amount: 0,
|
|
total_amount: 0,
|
|
items: [
|
|
{
|
|
item_id: params.item.itemId || null,
|
|
item_code: params.item.itemCode || null,
|
|
item_name: params.item.itemName,
|
|
specification: params.item.specification || null,
|
|
quantity: params.item.quantity,
|
|
unit: params.item.unit || 'EA',
|
|
unit_price: 0,
|
|
supply_amount: 0,
|
|
tax_amount: 0,
|
|
total_amount: 0,
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl('/api/v1/orders'),
|
|
method: 'POST',
|
|
body: apiData,
|
|
transform: (d: ApiStockOrder) => transformApiToFrontend(d),
|
|
errorMessage: '절곡품 재고생산 등록에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|
|
|
|
/**
|
|
* 절곡품 재고생산 수정 (기존 orders API + bending_lot 확장)
|
|
*/
|
|
export async function updateBendingStockOrder(id: string, params: {
|
|
memo?: string;
|
|
targetStockQty: number;
|
|
bendingLot: BendingLotFormData;
|
|
item: {
|
|
itemId?: number;
|
|
itemCode?: string;
|
|
itemName: string;
|
|
specification?: string;
|
|
quantity: number;
|
|
unit?: string;
|
|
};
|
|
}): Promise<{
|
|
success: boolean;
|
|
data?: StockOrder;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
const apiData = {
|
|
order_type_code: 'STOCK',
|
|
memo: params.memo || null,
|
|
remarks: null,
|
|
options: {
|
|
production_reason: '절곡품 재고생산',
|
|
target_stock_qty: params.targetStockQty || null,
|
|
bending_lot: {
|
|
lot_number: params.bendingLot.lot_number,
|
|
prod_code: params.bendingLot.prod_code,
|
|
spec_code: params.bendingLot.spec_code,
|
|
length_code: params.bendingLot.length_code,
|
|
raw_lot_no: params.bendingLot.raw_lot_no || null,
|
|
fabric_lot_no: params.bendingLot.fabric_lot_no || null,
|
|
material: params.bendingLot.material || null,
|
|
},
|
|
},
|
|
client_id: null,
|
|
client_name: null,
|
|
site_name: null,
|
|
delivery_date: null,
|
|
delivery_method_code: null,
|
|
discount_rate: 0,
|
|
discount_amount: 0,
|
|
supply_amount: 0,
|
|
tax_amount: 0,
|
|
total_amount: 0,
|
|
items: [
|
|
{
|
|
item_id: params.item.itemId || null,
|
|
item_code: params.item.itemCode || null,
|
|
item_name: params.item.itemName,
|
|
specification: params.item.specification || null,
|
|
quantity: params.item.quantity,
|
|
unit: params.item.unit || 'EA',
|
|
unit_price: 0,
|
|
supply_amount: 0,
|
|
tax_amount: 0,
|
|
total_amount: 0,
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl(`/api/v1/orders/${id}`),
|
|
method: 'PUT',
|
|
body: apiData,
|
|
transform: (d: ApiStockOrder) => transformApiToFrontend(d),
|
|
errorMessage: '절곡품 재고생산 수정에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|
|
|
|
/**
|
|
* 원자재 LOT 목록 조회 (수입검사 완료 입고 건)
|
|
*/
|
|
export async function getMaterialLots(material: string): Promise<{
|
|
success: boolean;
|
|
data?: MaterialLot[];
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
const result = await executeServerAction<MaterialLot[]>({
|
|
url: buildApiUrl('/api/v1/bending/material-lots', { material }),
|
|
errorMessage: '원자재 LOT 목록 조회에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|