feat: implement production plan management functionality

- 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
This commit is contained in:
kjs
2026-03-16 09:28:22 +09:00
parent 28b7f196e0
commit 17a5d2ff9b
8 changed files with 1197 additions and 2 deletions
+112 -1
View File
@@ -59,7 +59,8 @@ export type ButtonActionType =
| "transferData" // 데이터 전달 (컴포넌트 간 or 화면 간)
| "quickInsert" // 즉시 저장 (선택한 데이터를 특정 테이블에 즉시 INSERT)
| "event" // 이벤트 버스로 이벤트 발송 (스케줄 생성 등)
| "approval"; // 결재 요청
| "approval" // 결재 요청
| "apiCall"; // 범용 API 호출 (생산계획 자동 스케줄 등)
/**
*
@@ -286,6 +287,18 @@ export interface ButtonActionConfig {
eventName: string; // 발송할 이벤트 이름 (V2_EVENTS 키)
eventPayload?: Record<string, any>; // 이벤트 페이로드 (requestId는 자동 생성)
};
// 범용 API 호출 관련 (apiCall 액션용)
apiCallConfig?: {
method: "GET" | "POST" | "PUT" | "DELETE";
endpoint: string; // 예: "/api/production/generate-schedule"
payloadMapping?: Record<string, string>; // formData 필드 → API body 필드 매핑
staticPayload?: Record<string, any>; // 고정 페이로드 값
useSelectedRows?: boolean; // true면 선택된 행 데이터를 body에 포함
selectedRowsKey?: string; // 선택된 행 데이터의 key (기본: "items")
refreshAfterSuccess?: boolean; // 성공 후 테이블 새로고침 (기본: true)
confirmMessage?: string; // 실행 전 확인 메시지
};
}
/**
@@ -457,6 +470,9 @@ export class ButtonActionExecutor {
case "event":
return await this.handleEvent(config, context);
case "apiCall":
return await this.handleApiCall(config, context);
case "approval":
return this.handleApproval(config, context);
@@ -7681,6 +7697,97 @@ export class ButtonActionExecutor {
}
}
/**
* API ( )
*/
private static async handleApiCall(config: ButtonActionConfig, context: ButtonActionContext): Promise<boolean> {
try {
const { apiCallConfig } = config;
if (!apiCallConfig?.endpoint) {
toast.error("API 엔드포인트가 설정되지 않았습니다.");
return false;
}
// 확인 메시지
if (apiCallConfig.confirmMessage) {
const confirmed = window.confirm(apiCallConfig.confirmMessage);
if (!confirmed) return false;
}
// 페이로드 구성
let payload: Record<string, any> = { ...(apiCallConfig.staticPayload || {}) };
// formData에서 매핑
if (apiCallConfig.payloadMapping && context.formData) {
for (const [formField, apiField] of Object.entries(apiCallConfig.payloadMapping)) {
if (context.formData[formField] !== undefined) {
payload[apiField] = context.formData[formField];
}
}
}
// 선택된 행 데이터 포함
if (apiCallConfig.useSelectedRows && context.selectedRowsData) {
const key = apiCallConfig.selectedRowsKey || "items";
payload[key] = context.selectedRowsData;
}
console.log("[handleApiCall] API 호출:", {
method: apiCallConfig.method,
endpoint: apiCallConfig.endpoint,
payload,
});
// API 호출
const { apiClient } = await import("@/lib/api/client");
let response: any;
switch (apiCallConfig.method) {
case "GET":
response = await apiClient.get(apiCallConfig.endpoint, { params: payload });
break;
case "POST":
response = await apiClient.post(apiCallConfig.endpoint, payload);
break;
case "PUT":
response = await apiClient.put(apiCallConfig.endpoint, payload);
break;
case "DELETE":
response = await apiClient.delete(apiCallConfig.endpoint, { data: payload });
break;
}
const result = response?.data;
if (result?.success === false) {
toast.error(result.message || "API 호출에 실패했습니다.");
return false;
}
// 성공 메시지
if (config.successMessage) {
toast.success(config.successMessage);
}
// 테이블 새로고침
if (apiCallConfig.refreshAfterSuccess !== false) {
const { v2EventBus, V2_EVENTS } = await import("@/lib/v2-core");
v2EventBus.emitSync(V2_EVENTS.TABLE_REFRESH, {
tableName: context.tableName,
target: "all",
});
}
return true;
} catch (error: any) {
console.error("[handleApiCall] API 호출 오류:", error);
const msg = error?.response?.data?.message || error?.message || "API 호출 중 오류가 발생했습니다.";
toast.error(msg);
return false;
}
}
/**
*
*/
@@ -7843,4 +7950,8 @@ export const DEFAULT_BUTTON_ACTIONS: Record<ButtonActionType, Partial<ButtonActi
approval: {
type: "approval",
},
apiCall: {
type: "apiCall",
successMessage: "처리되었습니다.",
},
};