- Added production plan management routes and controller to handle various operations including order summary retrieval, stock shortage checks, and CRUD operations for production plans. - Introduced service layer for production plan management, encapsulating business logic for handling production-related data. - Created API client for production plan management, enabling frontend interaction with the new backend endpoints. - Enhanced button actions to support API calls for production scheduling and management tasks. These changes aim to improve the management of production plans, enhancing usability and functionality within the ERP system. Made-with: Cursor
179 lines
5.0 KiB
TypeScript
179 lines
5.0 KiB
TypeScript
/**
|
|
* 생산계획 API 클라이언트
|
|
*/
|
|
|
|
import apiClient from "./client";
|
|
|
|
// ─── 타입 정의 ───
|
|
|
|
export interface OrderSummaryItem {
|
|
item_code: string;
|
|
item_name: string;
|
|
total_order_qty: number;
|
|
total_ship_qty: number;
|
|
total_balance_qty: number;
|
|
order_count: number;
|
|
earliest_due_date: string | null;
|
|
current_stock: number;
|
|
safety_stock: number;
|
|
existing_plan_qty: number;
|
|
in_progress_qty: number;
|
|
required_plan_qty: number;
|
|
orders: OrderDetail[];
|
|
}
|
|
|
|
export interface OrderDetail {
|
|
id: string;
|
|
order_no: string;
|
|
part_code: string;
|
|
part_name: string;
|
|
order_qty: number;
|
|
ship_qty: number;
|
|
balance_qty: number;
|
|
due_date: string | null;
|
|
status: string;
|
|
customer_name: string | null;
|
|
}
|
|
|
|
export interface StockShortageItem {
|
|
item_code: string;
|
|
item_name: string;
|
|
current_qty: number;
|
|
safety_qty: number;
|
|
shortage_qty: number;
|
|
recommended_qty: number;
|
|
last_in_date: string | null;
|
|
}
|
|
|
|
export interface ProductionPlan {
|
|
id: number;
|
|
company_code: string;
|
|
plan_no: string;
|
|
plan_date: string;
|
|
item_code: string;
|
|
item_name: string;
|
|
product_type: string;
|
|
plan_qty: number;
|
|
completed_qty: number;
|
|
progress_rate: number;
|
|
start_date: string;
|
|
end_date: string;
|
|
due_date: string | null;
|
|
equipment_id: number | null;
|
|
equipment_code: string | null;
|
|
equipment_name: string | null;
|
|
status: string;
|
|
priority: string | null;
|
|
order_no: string | null;
|
|
parent_plan_id: number | null;
|
|
remarks: string | null;
|
|
}
|
|
|
|
export interface GenerateScheduleRequest {
|
|
items: {
|
|
item_code: string;
|
|
item_name: string;
|
|
required_qty: number;
|
|
earliest_due_date: string;
|
|
hourly_capacity?: number;
|
|
daily_capacity?: number;
|
|
lead_time?: number;
|
|
}[];
|
|
options?: {
|
|
safety_lead_time?: number;
|
|
recalculate_unstarted?: boolean;
|
|
product_type?: string;
|
|
};
|
|
}
|
|
|
|
export interface GenerateScheduleResponse {
|
|
summary: {
|
|
total: number;
|
|
new_count: number;
|
|
kept_count: number;
|
|
deleted_count: number;
|
|
};
|
|
schedules: ProductionPlan[];
|
|
}
|
|
|
|
// ─── API 함수 ───
|
|
|
|
/** 수주 데이터 조회 (품목별 그룹핑) */
|
|
export async function getOrderSummary(params?: {
|
|
excludePlanned?: boolean;
|
|
itemCode?: string;
|
|
itemName?: string;
|
|
}) {
|
|
const queryParams = new URLSearchParams();
|
|
if (params?.excludePlanned) queryParams.set("excludePlanned", "true");
|
|
if (params?.itemCode) queryParams.set("itemCode", params.itemCode);
|
|
if (params?.itemName) queryParams.set("itemName", params.itemName);
|
|
|
|
const qs = queryParams.toString();
|
|
const url = `/api/production/order-summary${qs ? `?${qs}` : ""}`;
|
|
const response = await apiClient.get(url);
|
|
return response.data as { success: boolean; data: OrderSummaryItem[] };
|
|
}
|
|
|
|
/** 안전재고 부족분 조회 */
|
|
export async function getStockShortage() {
|
|
const response = await apiClient.get("/api/production/stock-shortage");
|
|
return response.data as { success: boolean; data: StockShortageItem[] };
|
|
}
|
|
|
|
/** 생산계획 상세 조회 */
|
|
export async function getPlanById(planId: number) {
|
|
const response = await apiClient.get(`/api/production/plan/${planId}`);
|
|
return response.data as { success: boolean; data: ProductionPlan };
|
|
}
|
|
|
|
/** 생산계획 수정 */
|
|
export async function updatePlan(planId: number, data: Partial<ProductionPlan>) {
|
|
const response = await apiClient.put(`/api/production/plan/${planId}`, data);
|
|
return response.data as { success: boolean; data: ProductionPlan };
|
|
}
|
|
|
|
/** 생산계획 삭제 */
|
|
export async function deletePlan(planId: number) {
|
|
const response = await apiClient.delete(`/api/production/plan/${planId}`);
|
|
return response.data as { success: boolean; message: string };
|
|
}
|
|
|
|
/** 자동 스케줄 생성 */
|
|
export async function generateSchedule(request: GenerateScheduleRequest) {
|
|
const response = await apiClient.post("/api/production/generate-schedule", request);
|
|
return response.data as { success: boolean; data: GenerateScheduleResponse };
|
|
}
|
|
|
|
/** 스케줄 병합 */
|
|
export async function mergeSchedules(scheduleIds: number[], productType?: string) {
|
|
const response = await apiClient.post("/api/production/merge-schedules", {
|
|
schedule_ids: scheduleIds,
|
|
product_type: productType || "완제품",
|
|
});
|
|
return response.data as { success: boolean; data: ProductionPlan };
|
|
}
|
|
|
|
/** 반제품 계획 자동 생성 */
|
|
export async function generateSemiSchedule(
|
|
planIds: number[],
|
|
options?: { considerStock?: boolean; excludeUsed?: boolean }
|
|
) {
|
|
const response = await apiClient.post("/api/production/generate-semi-schedule", {
|
|
plan_ids: planIds,
|
|
options: options || {},
|
|
});
|
|
return response.data as { success: boolean; data: { count: number; schedules: ProductionPlan[] } };
|
|
}
|
|
|
|
/** 스케줄 분할 */
|
|
export async function splitSchedule(planId: number, splitQty: number) {
|
|
const response = await apiClient.post(`/api/production/plan/${planId}/split`, {
|
|
split_qty: splitQty,
|
|
});
|
|
return response.data as {
|
|
success: boolean;
|
|
data: { original: { id: number; plan_qty: number }; split: ProductionPlan };
|
|
};
|
|
}
|