feat: [생산지시] 목록/상세 페이지 API 연동
- types.ts: API/프론트 타입 정의 (ProductionOrder, Detail, BOM 타입) - actions.ts: Server Actions (getProductionOrders, getProductionOrderStats, getProductionOrderDetail) - executePaginatedAction + buildApiUrl 패턴 적용 - snake_case → camelCase 변환 함수 - 목록 page.tsx: 샘플데이터 → API 연동 - 서버사이드 페이지네이션 (clientSideFiltering: false) - stats API로 탭 카운트 동적 반영 - ProgressSteps 동적화 (statusCode 기반) - 생산지시번호 → 수주번호로 변경 (별도 PO 번호 없음) - 상세 page.tsx: 샘플데이터 → API 연동 - getProductionOrderDetail() API 호출 - createProductionOrder() orders/actions.ts에서 재사용 - BOM null 처리 (빈 상태 표시) - WorkOrder 상태 배지 확장 (6종: unassigned~shipped)
This commit is contained in:
105
src/components/production/ProductionOrders/actions.ts
Normal file
105
src/components/production/ProductionOrders/actions.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
'use server';
|
||||
|
||||
import { executePaginatedAction } from '@/lib/api/execute-paginated-action';
|
||||
import { executeServerAction } from '@/lib/api/execute-server-action';
|
||||
import { buildApiUrl } from '@/lib/api/query-params';
|
||||
import type {
|
||||
ApiProductionOrder,
|
||||
ApiProductionOrderDetail,
|
||||
ProductionOrder,
|
||||
ProductionOrderDetail,
|
||||
ProductionOrderStats,
|
||||
ProductionOrderListParams,
|
||||
ProductionStatus,
|
||||
} from './types';
|
||||
|
||||
// ===== 변환 함수 =====
|
||||
|
||||
function transformApiToFrontend(data: ApiProductionOrder): ProductionOrder {
|
||||
return {
|
||||
id: String(data.id),
|
||||
orderNumber: data.order_no,
|
||||
siteName: data.site_name || '',
|
||||
clientName: data.client_name || data.client?.name || '',
|
||||
quantity: data.quantity,
|
||||
deliveryDate: data.delivery_date || '',
|
||||
productionOrderedAt: data.production_ordered_at || '',
|
||||
productionStatus: data.production_status,
|
||||
workOrderCount: data.work_orders_count,
|
||||
workOrderProgress: {
|
||||
total: data.work_order_progress?.total || 0,
|
||||
completed: data.work_order_progress?.completed || 0,
|
||||
inProgress: data.work_order_progress?.in_progress || 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function transformDetailApiToFrontend(data: ApiProductionOrderDetail): ProductionOrderDetail {
|
||||
const order = transformApiToFrontend(data.order);
|
||||
return {
|
||||
...order,
|
||||
productionOrderedAt: data.production_ordered_at || order.productionOrderedAt,
|
||||
productionStatus: data.production_status || order.productionStatus,
|
||||
workOrderProgress: {
|
||||
total: data.work_order_progress?.total || 0,
|
||||
completed: data.work_order_progress?.completed || 0,
|
||||
inProgress: data.work_order_progress?.in_progress || 0,
|
||||
},
|
||||
workOrders: (data.work_orders || []).map((wo) => ({
|
||||
id: wo.id,
|
||||
workOrderNo: wo.work_order_no,
|
||||
processName: wo.process_name,
|
||||
quantity: wo.quantity,
|
||||
status: wo.status,
|
||||
assignees: wo.assignees || [],
|
||||
})),
|
||||
bomProcessGroups: (data.bom_process_groups || []).map((group) => ({
|
||||
processName: group.process_name,
|
||||
sizeSpec: group.size_spec,
|
||||
items: (group.items || []).map((item) => ({
|
||||
id: item.id,
|
||||
itemCode: item.item_code,
|
||||
itemName: item.item_name,
|
||||
spec: item.spec,
|
||||
lotNo: item.lot_no,
|
||||
requiredQty: item.required_qty,
|
||||
qty: item.qty,
|
||||
})),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
// ===== Server Actions =====
|
||||
|
||||
// 목록 조회
|
||||
export async function getProductionOrders(params: ProductionOrderListParams) {
|
||||
return executePaginatedAction<ApiProductionOrder, ProductionOrder>({
|
||||
url: buildApiUrl('/api/v1/production-orders', {
|
||||
search: params.search,
|
||||
production_status: params.productionStatus,
|
||||
sort_by: params.sortBy,
|
||||
sort_dir: params.sortDir,
|
||||
page: params.page,
|
||||
per_page: params.perPage,
|
||||
}),
|
||||
transform: transformApiToFrontend,
|
||||
errorMessage: '생산지시 목록 조회에 실패했습니다.',
|
||||
});
|
||||
}
|
||||
|
||||
// 상태별 통계
|
||||
export async function getProductionOrderStats() {
|
||||
return executeServerAction<ProductionOrderStats>({
|
||||
url: buildApiUrl('/api/v1/production-orders/stats'),
|
||||
errorMessage: '생산지시 통계 조회에 실패했습니다.',
|
||||
});
|
||||
}
|
||||
|
||||
// 상세 조회
|
||||
export async function getProductionOrderDetail(orderId: string) {
|
||||
return executeServerAction<ApiProductionOrderDetail, ProductionOrderDetail>({
|
||||
url: buildApiUrl(`/api/v1/production-orders/${orderId}`),
|
||||
transform: transformDetailApiToFrontend,
|
||||
errorMessage: '생산지시 상세 조회에 실패했습니다.',
|
||||
});
|
||||
}
|
||||
133
src/components/production/ProductionOrders/types.ts
Normal file
133
src/components/production/ProductionOrders/types.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
// 생산지시 상태 (프론트 탭용)
|
||||
export type ProductionStatus = 'waiting' | 'in_production' | 'completed';
|
||||
|
||||
// API 응답 타입 (snake_case)
|
||||
export interface ApiProductionOrder {
|
||||
id: number;
|
||||
order_no: string;
|
||||
site_name: string;
|
||||
client_name: string;
|
||||
quantity: number;
|
||||
delivery_date: string | null;
|
||||
status_code: string;
|
||||
production_ordered_at: string | null;
|
||||
production_status: ProductionStatus;
|
||||
work_orders_count: number;
|
||||
work_order_progress: {
|
||||
total: number;
|
||||
completed: number;
|
||||
in_progress: number;
|
||||
};
|
||||
client?: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
// 프론트 타입 (camelCase)
|
||||
export interface ProductionOrder {
|
||||
id: string;
|
||||
orderNumber: string;
|
||||
siteName: string;
|
||||
clientName: string;
|
||||
quantity: number;
|
||||
deliveryDate: string;
|
||||
productionOrderedAt: string;
|
||||
productionStatus: ProductionStatus;
|
||||
workOrderCount: number;
|
||||
workOrderProgress: {
|
||||
total: number;
|
||||
completed: number;
|
||||
inProgress: number;
|
||||
};
|
||||
}
|
||||
|
||||
// 생산지시 통계
|
||||
export interface ProductionOrderStats {
|
||||
total: number;
|
||||
waiting: number;
|
||||
in_production: number;
|
||||
completed: number;
|
||||
}
|
||||
|
||||
// 생산지시 상세 API 응답
|
||||
export interface ApiProductionOrderDetail {
|
||||
order: ApiProductionOrder;
|
||||
production_ordered_at: string | null;
|
||||
production_status: ProductionStatus;
|
||||
work_order_progress: {
|
||||
total: number;
|
||||
completed: number;
|
||||
in_progress: number;
|
||||
};
|
||||
work_orders: ApiProductionWorkOrder[];
|
||||
bom_process_groups: ApiBomProcessGroup[];
|
||||
}
|
||||
|
||||
// 상세 내 작업지시 정보
|
||||
export interface ApiProductionWorkOrder {
|
||||
id: number;
|
||||
work_order_no: string;
|
||||
process_name: string;
|
||||
quantity: number;
|
||||
status: string;
|
||||
assignees: string[];
|
||||
}
|
||||
|
||||
// BOM 공정 분류
|
||||
export interface ApiBomProcessGroup {
|
||||
process_name: string;
|
||||
size_spec?: string;
|
||||
items: ApiBomItem[];
|
||||
}
|
||||
|
||||
export interface ApiBomItem {
|
||||
id: number | null;
|
||||
item_code: string;
|
||||
item_name: string;
|
||||
spec: string;
|
||||
lot_no: string;
|
||||
required_qty: number;
|
||||
qty: number;
|
||||
}
|
||||
|
||||
// 프론트 상세 타입
|
||||
export interface ProductionOrderDetail extends ProductionOrder {
|
||||
workOrders: ProductionWorkOrder[];
|
||||
bomProcessGroups: BomProcessGroup[];
|
||||
}
|
||||
|
||||
export interface ProductionWorkOrder {
|
||||
id: number;
|
||||
workOrderNo: string;
|
||||
processName: string;
|
||||
quantity: number;
|
||||
status: string;
|
||||
assignees: string[];
|
||||
}
|
||||
|
||||
export interface BomProcessGroup {
|
||||
processName: string;
|
||||
sizeSpec?: string;
|
||||
items: BomItem[];
|
||||
}
|
||||
|
||||
export interface BomItem {
|
||||
id: number | null;
|
||||
itemCode: string;
|
||||
itemName: string;
|
||||
spec: string;
|
||||
lotNo: string;
|
||||
requiredQty: number;
|
||||
qty: number;
|
||||
}
|
||||
|
||||
// 조회 파라미터
|
||||
export interface ProductionOrderListParams {
|
||||
search?: string;
|
||||
productionStatus?: ProductionStatus;
|
||||
sortBy?: string;
|
||||
sortDir?: 'asc' | 'desc';
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
}
|
||||
Reference in New Issue
Block a user