docs: 자동 스케줄 생성 기능 추가 및 관련 문서 업데이트
- 생산계획 목록에 자동 스케줄 생성 기능에 대한 상세 가이드를 추가하였습니다. - 스케줄 생성의 데이터 흐름과 설정을 명확히 설명하였으며, JSON 형식의 설정 예시를 포함하였습니다. - 버튼 설정 및 연결 필터 설정에 대한 정보를 추가하여 사용자가 기능을 쉽게 이해할 수 있도록 하였습니다. - 구현 상태를 체크리스트 형식으로 정리하여 각 항목의 진행 상황을 명시하였습니다.
This commit is contained in:
parent
7043f26ac8
commit
61b67c3619
|
|
@ -0,0 +1,894 @@
|
||||||
|
# 스케줄 자동 생성 기능 구현 가이드
|
||||||
|
|
||||||
|
> 버전: 2.0
|
||||||
|
> 최종 수정: 2025-02-02
|
||||||
|
> 적용 화면: 생산계획관리, 설비계획관리, 출하계획관리 등
|
||||||
|
|
||||||
|
## 1. 개요
|
||||||
|
|
||||||
|
### 1.1 기능 설명
|
||||||
|
|
||||||
|
좌측 테이블에서 선택한 데이터(수주, 작업지시 등)를 기반으로 우측 타임라인에 스케줄을 자동 생성하는 기능입니다.
|
||||||
|
|
||||||
|
### 1.2 주요 특징
|
||||||
|
|
||||||
|
- **범용성**: 설정 기반으로 다양한 화면에서 재사용 가능
|
||||||
|
- **미리보기**: 적용 전 변경사항 확인 가능
|
||||||
|
- **소스 추적**: 스케줄이 어디서 생성되었는지 추적 가능
|
||||||
|
- **연결 필터**: 좌측 선택 시 우측 타임라인 자동 필터링
|
||||||
|
- **이벤트 버스 기반**: 컴포넌트 간 느슨한 결합 (Loose Coupling)
|
||||||
|
|
||||||
|
### 1.3 아키텍처 원칙
|
||||||
|
|
||||||
|
**이벤트 버스 패턴**을 활용하여 컴포넌트 간 직접 참조를 제거합니다:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐ 이벤트 발송 ┌─────────────────┐
|
||||||
|
│ v2-button │ ──────────────────▶ │ EventBus │
|
||||||
|
│ (발송만 함) │ │ (중재자) │
|
||||||
|
└─────────────────┘ └────────┬────────┘
|
||||||
|
│
|
||||||
|
┌────────────────────────────┼────────────────────────────┐
|
||||||
|
│ │ │
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||||
|
│ ScheduleService │ │ v2-timeline │ │ 기타 리스너 │
|
||||||
|
│ (처리 담당) │ │ (갱신) │ │ │
|
||||||
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**장점**:
|
||||||
|
- 버튼은 데이터가 어디서 오는지 알 필요 없음
|
||||||
|
- 테이블은 누가 데이터를 사용하는지 알 필요 없음
|
||||||
|
- 컴포넌트 교체/추가 시 기존 코드 수정 불필요
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 데이터 흐름
|
||||||
|
|
||||||
|
### 2.1 전체 흐름도
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ 분할 패널 (SplitPanelLayout) │
|
||||||
|
├───────────────────────────────┬─────────────────────────────────────────────┤
|
||||||
|
│ 좌측 패널 │ 우측 패널 │
|
||||||
|
│ │ │
|
||||||
|
│ ┌─────────────────────────┐ │ ┌─────────────────────────────────────┐ │
|
||||||
|
│ │ v2-table-grouped │ │ │ 자동 스케줄 생성 버튼 │ │
|
||||||
|
│ │ (수주 목록) │ │ │ ↓ │ │
|
||||||
|
│ │ │ │ │ ① 좌측 선택 데이터 가져오기 │ │
|
||||||
|
│ │ ☑ ITEM-001 (탕핑 A) │──┼──│ ② 백엔드 API 호출 (미리보기) │ │
|
||||||
|
│ │ └ SO-2025-101 │ │ │ ③ 변경사항 다이얼로그 표시 │ │
|
||||||
|
│ │ └ SO-2025-102 │ │ │ ④ 적용 API 호출 │ │
|
||||||
|
│ │ ☐ ITEM-002 (탕핑 B) │ │ │ ⑤ 타임라인 새로고침 │ │
|
||||||
|
│ │ └ SO-2025-201 │ │ └─────────────────────────────────────┘ │
|
||||||
|
│ └─────────────────────────┘ │ │
|
||||||
|
│ │ │ ┌─────────────────────────────────────┐ │
|
||||||
|
│ │ linkedFilter │ │ v2-timeline-scheduler │ │
|
||||||
|
│ └──────────────────┼──│ (생산 타임라인) │ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ │ part_code = 선택된 품목 필터링 │ │
|
||||||
|
│ │ └─────────────────────────────────────┘ │
|
||||||
|
└───────────────────────────────┴─────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 단계별 데이터 흐름
|
||||||
|
|
||||||
|
| 단계 | 동작 | 데이터 |
|
||||||
|
|------|------|--------|
|
||||||
|
| 1 | 좌측 테이블에서 품목 선택 | `selectedItems[]` (그룹 선택 시 자식 포함) |
|
||||||
|
| 2 | 자동 스케줄 생성 버튼 클릭 | 버튼 액션 실행 |
|
||||||
|
| 3 | 미리보기 API 호출 | `{ config, sourceData, period }` |
|
||||||
|
| 4 | 변경사항 다이얼로그 표시 | `{ toCreate, toDelete, summary }` |
|
||||||
|
| 5 | 적용 API 호출 | `{ config, preview, options }` |
|
||||||
|
| 6 | 타임라인 새로고침 | `TABLE_REFRESH` 이벤트 발송 |
|
||||||
|
| 7 | 다음 방문 시 좌측 선택 | `linkedFilter`로 우측 자동 필터링 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 테이블 구조 설계
|
||||||
|
|
||||||
|
### 3.1 범용 스케줄 테이블 (schedule_mng)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE schedule_mng (
|
||||||
|
schedule_id SERIAL PRIMARY KEY,
|
||||||
|
company_code VARCHAR(20) NOT NULL,
|
||||||
|
|
||||||
|
-- 스케줄 기본 정보
|
||||||
|
schedule_type VARCHAR(50) NOT NULL, -- 'PRODUCTION', 'SHIPPING', 'MAINTENANCE' 등
|
||||||
|
schedule_name VARCHAR(200),
|
||||||
|
|
||||||
|
-- 리소스 연결 (타임라인 Y축)
|
||||||
|
resource_type VARCHAR(50) NOT NULL, -- 'ITEM', 'MACHINE', 'WORKER' 등
|
||||||
|
resource_id VARCHAR(50) NOT NULL, -- 품목코드, 설비코드 등
|
||||||
|
resource_name VARCHAR(200),
|
||||||
|
|
||||||
|
-- 일정
|
||||||
|
start_date TIMESTAMP NOT NULL,
|
||||||
|
end_date TIMESTAMP NOT NULL,
|
||||||
|
|
||||||
|
-- 수량/값
|
||||||
|
plan_qty NUMERIC(15,3),
|
||||||
|
actual_qty NUMERIC(15,3),
|
||||||
|
|
||||||
|
-- 상태
|
||||||
|
status VARCHAR(20) DEFAULT 'PLANNED', -- PLANNED, IN_PROGRESS, COMPLETED, CANCELLED
|
||||||
|
|
||||||
|
-- 소스 추적 (어디서 생성되었는지)
|
||||||
|
source_table VARCHAR(100), -- 'sales_order_mng', 'work_order_mng' 등
|
||||||
|
source_id VARCHAR(50), -- 소스 테이블의 PK
|
||||||
|
source_group_key VARCHAR(100), -- 그룹 키 (품목코드 등)
|
||||||
|
|
||||||
|
-- 자동 생성 여부
|
||||||
|
auto_generated BOOLEAN DEFAULT FALSE,
|
||||||
|
generated_at TIMESTAMP,
|
||||||
|
generated_by VARCHAR(50),
|
||||||
|
|
||||||
|
-- 메타데이터 (추가 정보 JSON)
|
||||||
|
metadata JSONB,
|
||||||
|
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
|
||||||
|
CONSTRAINT fk_schedule_company FOREIGN KEY (company_code)
|
||||||
|
REFERENCES company_mng(company_code)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 인덱스
|
||||||
|
CREATE INDEX idx_schedule_company ON schedule_mng(company_code);
|
||||||
|
CREATE INDEX idx_schedule_type ON schedule_mng(schedule_type);
|
||||||
|
CREATE INDEX idx_schedule_resource ON schedule_mng(resource_type, resource_id);
|
||||||
|
CREATE INDEX idx_schedule_source ON schedule_mng(source_table, source_id);
|
||||||
|
CREATE INDEX idx_schedule_date ON schedule_mng(start_date, end_date);
|
||||||
|
CREATE INDEX idx_schedule_status ON schedule_mng(status);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 소스-스케줄 매핑 테이블 (N:M 관계)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 하나의 스케줄이 여러 소스에서 생성될 수 있음
|
||||||
|
CREATE TABLE schedule_source_mapping (
|
||||||
|
mapping_id SERIAL PRIMARY KEY,
|
||||||
|
company_code VARCHAR(20) NOT NULL,
|
||||||
|
schedule_id INTEGER REFERENCES schedule_mng(schedule_id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- 소스 정보
|
||||||
|
source_table VARCHAR(100) NOT NULL,
|
||||||
|
source_id VARCHAR(50) NOT NULL,
|
||||||
|
source_qty NUMERIC(15,3), -- 해당 소스에서 기여한 수량
|
||||||
|
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
|
||||||
|
CONSTRAINT fk_mapping_company FOREIGN KEY (company_code)
|
||||||
|
REFERENCES company_mng(company_code)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_mapping_schedule ON schedule_source_mapping(schedule_id);
|
||||||
|
CREATE INDEX idx_mapping_source ON schedule_source_mapping(source_table, source_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 테이블 관계도
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
|
||||||
|
│ sales_order_mng │ │ schedule_mng │ │ schedule_source_ │
|
||||||
|
│ (소스 테이블) │ │ (스케줄 테이블) │ │ mapping │
|
||||||
|
├─────────────────────┤ ├─────────────────────┤ ├─────────────────────┤
|
||||||
|
│ order_id (PK) │───────│ source_id │ │ mapping_id (PK) │
|
||||||
|
│ part_code │ │ schedule_id (PK) │──1:N──│ schedule_id (FK) │
|
||||||
|
│ order_qty │ │ resource_id │ │ source_table │
|
||||||
|
│ balance_qty │ │ start_date │ │ source_id │
|
||||||
|
│ due_date │ │ end_date │ │ source_qty │
|
||||||
|
└─────────────────────┘ │ plan_qty │ └─────────────────────┘
|
||||||
|
│ status │
|
||||||
|
│ auto_generated │
|
||||||
|
└─────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 스케줄 생성 설정 구조
|
||||||
|
|
||||||
|
### 4.1 TypeScript 인터페이스
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 화면 레벨 설정 (screen_definitions 또는 screen_layouts_v2에 저장)
|
||||||
|
interface ScheduleGenerationConfig {
|
||||||
|
// 스케줄 타입
|
||||||
|
scheduleType: "PRODUCTION" | "SHIPPING" | "MAINTENANCE" | "WORK_ASSIGN";
|
||||||
|
|
||||||
|
// 소스 설정 (컴포넌트 ID 불필요 - 이벤트로 데이터 수신)
|
||||||
|
source: {
|
||||||
|
tableName: string; // 소스 테이블명
|
||||||
|
groupByField: string; // 그룹화 기준 필드 (part_code)
|
||||||
|
quantityField: string; // 수량 필드 (order_qty, balance_qty)
|
||||||
|
dueDateField?: string; // 납기일 필드 (선택)
|
||||||
|
};
|
||||||
|
|
||||||
|
// 리소스 매핑 (타임라인 Y축)
|
||||||
|
resource: {
|
||||||
|
type: string; // 'ITEM', 'MACHINE', 'WORKER' 등
|
||||||
|
idField: string; // part_code, machine_code 등
|
||||||
|
nameField: string; // part_name, machine_name 등
|
||||||
|
};
|
||||||
|
|
||||||
|
// 생성 규칙
|
||||||
|
rules: {
|
||||||
|
leadTimeDays?: number; // 리드타임 (일)
|
||||||
|
dailyCapacity?: number; // 일일 생산능력
|
||||||
|
workingDays?: number[]; // 작업일 [1,2,3,4,5] = 월~금
|
||||||
|
considerStock?: boolean; // 재고 고려 여부
|
||||||
|
stockTableName?: string; // 재고 테이블명
|
||||||
|
stockQtyField?: string; // 재고 수량 필드
|
||||||
|
safetyStockField?: string; // 안전재고 필드
|
||||||
|
};
|
||||||
|
|
||||||
|
// 타겟 설정
|
||||||
|
target: {
|
||||||
|
tableName: string; // 스케줄 테이블명 (schedule_mng 또는 전용 테이블)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **주의**: 기존 설계와 달리 `source.componentId`와 `target.timelineComponentId`가 제거되었습니다.
|
||||||
|
> 이벤트 버스를 통해 데이터가 전달되므로 컴포넌트 ID를 직접 참조할 필요가 없습니다.
|
||||||
|
|
||||||
|
### 4.2 화면별 설정 예시
|
||||||
|
|
||||||
|
#### 생산계획관리 화면
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scheduleType": "PRODUCTION",
|
||||||
|
"source": {
|
||||||
|
"tableName": "sales_order_mng",
|
||||||
|
"groupByField": "part_code",
|
||||||
|
"quantityField": "balance_qty",
|
||||||
|
"dueDateField": "due_date"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"type": "ITEM",
|
||||||
|
"idField": "part_code",
|
||||||
|
"nameField": "part_name"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"leadTimeDays": 3,
|
||||||
|
"dailyCapacity": 100,
|
||||||
|
"workingDays": [1, 2, 3, 4, 5],
|
||||||
|
"considerStock": true,
|
||||||
|
"stockTableName": "inventory_mng",
|
||||||
|
"stockQtyField": "current_qty",
|
||||||
|
"safetyStockField": "safety_stock"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"tableName": "schedule_mng"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 설비계획관리 화면
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scheduleType": "MAINTENANCE",
|
||||||
|
"source": {
|
||||||
|
"tableName": "work_order_mng",
|
||||||
|
"groupByField": "machine_code",
|
||||||
|
"quantityField": "work_hours"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"type": "MACHINE",
|
||||||
|
"idField": "machine_code",
|
||||||
|
"nameField": "machine_name"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"workingDays": [1, 2, 3, 4, 5, 6]
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"tableName": "schedule_mng"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 백엔드 API 설계
|
||||||
|
|
||||||
|
### 5.1 미리보기 API
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// POST /api/schedule/preview
|
||||||
|
interface PreviewRequest {
|
||||||
|
config: ScheduleGenerationConfig;
|
||||||
|
sourceData: any[]; // 선택된 소스 데이터
|
||||||
|
period: {
|
||||||
|
start: string; // ISO 날짜 문자열
|
||||||
|
end: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PreviewResponse {
|
||||||
|
success: boolean;
|
||||||
|
preview: {
|
||||||
|
toCreate: ScheduleItem[]; // 생성될 스케줄
|
||||||
|
toDelete: ScheduleItem[]; // 삭제될 기존 스케줄
|
||||||
|
toUpdate: ScheduleItem[]; // 수정될 스케줄
|
||||||
|
summary: {
|
||||||
|
createCount: number;
|
||||||
|
deleteCount: number;
|
||||||
|
updateCount: number;
|
||||||
|
totalQty: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 적용 API
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// POST /api/schedule/apply
|
||||||
|
interface ApplyRequest {
|
||||||
|
config: ScheduleGenerationConfig;
|
||||||
|
preview: PreviewResponse["preview"];
|
||||||
|
options: {
|
||||||
|
deleteExisting: boolean; // 기존 스케줄 삭제 여부
|
||||||
|
updateMode: "replace" | "merge";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ApplyResponse {
|
||||||
|
success: boolean;
|
||||||
|
applied: {
|
||||||
|
created: number;
|
||||||
|
deleted: number;
|
||||||
|
updated: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 스케줄 조회 API (타임라인용)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// GET /api/schedule/list
|
||||||
|
interface ListQuery {
|
||||||
|
scheduleType: string;
|
||||||
|
resourceType: string;
|
||||||
|
resourceId?: string; // 필터링 (linkedFilter에서 사용)
|
||||||
|
startDate: string;
|
||||||
|
endDate: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ListResponse {
|
||||||
|
success: boolean;
|
||||||
|
data: ScheduleItem[];
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 이벤트 버스 기반 구현
|
||||||
|
|
||||||
|
### 6.1 이벤트 타입 정의
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// frontend/lib/v2-core/events/types.ts에 추가
|
||||||
|
|
||||||
|
export const V2_EVENTS = {
|
||||||
|
// ... 기존 이벤트들
|
||||||
|
|
||||||
|
// 스케줄 생성 이벤트
|
||||||
|
SCHEDULE_GENERATE_REQUEST: "v2:schedule:generate:request",
|
||||||
|
SCHEDULE_GENERATE_PREVIEW: "v2:schedule:generate:preview",
|
||||||
|
SCHEDULE_GENERATE_APPLY: "v2:schedule:generate:apply",
|
||||||
|
SCHEDULE_GENERATE_COMPLETE: "v2:schedule:generate:complete",
|
||||||
|
SCHEDULE_GENERATE_ERROR: "v2:schedule:generate:error",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/** 스케줄 생성 요청 이벤트 */
|
||||||
|
export interface V2ScheduleGenerateRequestEvent {
|
||||||
|
requestId: string;
|
||||||
|
scheduleType: "PRODUCTION" | "MAINTENANCE" | "SHIPPING" | "WORK_ASSIGN";
|
||||||
|
sourceData?: any[]; // 선택 데이터 (없으면 TABLE_SELECTION_CHANGE로 받은 데이터 사용)
|
||||||
|
period?: { start: string; end: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 스케줄 미리보기 결과 이벤트 */
|
||||||
|
export interface V2ScheduleGeneratePreviewEvent {
|
||||||
|
requestId: string;
|
||||||
|
preview: {
|
||||||
|
toCreate: any[];
|
||||||
|
toDelete: any[];
|
||||||
|
summary: { createCount: number; deleteCount: number; totalQty: number };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 스케줄 적용 이벤트 */
|
||||||
|
export interface V2ScheduleGenerateApplyEvent {
|
||||||
|
requestId: string;
|
||||||
|
confirmed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 스케줄 생성 완료 이벤트 */
|
||||||
|
export interface V2ScheduleGenerateCompleteEvent {
|
||||||
|
requestId: string;
|
||||||
|
success: boolean;
|
||||||
|
applied: { created: number; deleted: number };
|
||||||
|
scheduleType: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 버튼 설정 (간소화)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"componentType": "v2-button-primary",
|
||||||
|
"componentId": "btn_auto_schedule",
|
||||||
|
"componentConfig": {
|
||||||
|
"label": "자동 스케줄 생성",
|
||||||
|
"variant": "default",
|
||||||
|
"icon": "Calendar",
|
||||||
|
"action": {
|
||||||
|
"type": "event",
|
||||||
|
"eventName": "SCHEDULE_GENERATE_REQUEST",
|
||||||
|
"eventPayload": {
|
||||||
|
"scheduleType": "PRODUCTION"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **핵심**: 버튼은 이벤트만 발송하고, 데이터가 어디서 오는지 알 필요 없음
|
||||||
|
|
||||||
|
### 6.3 스케줄 생성 서비스 (이벤트 리스너)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// frontend/lib/v2-core/services/ScheduleGeneratorService.ts
|
||||||
|
|
||||||
|
import { v2EventBus, V2_EVENTS } from "@/lib/v2-core";
|
||||||
|
import apiClient from "@/lib/api/client";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
export function useScheduleGenerator(scheduleConfig: ScheduleGenerationConfig) {
|
||||||
|
const [selectedData, setSelectedData] = useState<any[]>([]);
|
||||||
|
const [previewResult, setPreviewResult] = useState<any>(null);
|
||||||
|
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
|
||||||
|
const [currentRequestId, setCurrentRequestId] = useState<string>("");
|
||||||
|
|
||||||
|
// 1. 테이블 선택 데이터 추적 (TABLE_SELECTION_CHANGE 이벤트 수신)
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribe = v2EventBus.on(
|
||||||
|
V2_EVENTS.TABLE_SELECTION_CHANGE,
|
||||||
|
(payload) => {
|
||||||
|
// 설정된 소스 테이블과 일치하는 경우에만 저장
|
||||||
|
if (payload.tableName === scheduleConfig.source.tableName) {
|
||||||
|
setSelectedData(payload.selectedRows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return unsubscribe;
|
||||||
|
}, [scheduleConfig.source.tableName]);
|
||||||
|
|
||||||
|
// 2. 스케줄 생성 요청 처리 (SCHEDULE_GENERATE_REQUEST 수신)
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribe = v2EventBus.on(
|
||||||
|
V2_EVENTS.SCHEDULE_GENERATE_REQUEST,
|
||||||
|
async (payload) => {
|
||||||
|
// 스케줄 타입이 일치하는 경우에만 처리
|
||||||
|
if (payload.scheduleType !== scheduleConfig.scheduleType) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataToUse = payload.sourceData || selectedData;
|
||||||
|
|
||||||
|
if (dataToUse.length === 0) {
|
||||||
|
toast.warning("품목을 선택해주세요.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentRequestId(payload.requestId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 미리보기 API 호출
|
||||||
|
const response = await apiClient.post("/api/schedule/preview", {
|
||||||
|
config: scheduleConfig,
|
||||||
|
sourceData: dataToUse,
|
||||||
|
period: payload.period || getDefaultPeriod(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.data.success) {
|
||||||
|
toast.error(response.data.message);
|
||||||
|
v2EventBus.emit(V2_EVENTS.SCHEDULE_GENERATE_ERROR, {
|
||||||
|
requestId: payload.requestId,
|
||||||
|
error: response.data.message,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPreviewResult(response.data.preview);
|
||||||
|
setShowConfirmDialog(true);
|
||||||
|
|
||||||
|
// 미리보기 결과 이벤트 발송 (다른 컴포넌트가 필요할 수 있음)
|
||||||
|
v2EventBus.emit(V2_EVENTS.SCHEDULE_GENERATE_PREVIEW, {
|
||||||
|
requestId: payload.requestId,
|
||||||
|
preview: response.data.preview,
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
toast.error("스케줄 생성 중 오류가 발생했습니다.");
|
||||||
|
v2EventBus.emit(V2_EVENTS.SCHEDULE_GENERATE_ERROR, {
|
||||||
|
requestId: payload.requestId,
|
||||||
|
error: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return unsubscribe;
|
||||||
|
}, [selectedData, scheduleConfig]);
|
||||||
|
|
||||||
|
// 3. 스케줄 적용 처리 (SCHEDULE_GENERATE_APPLY 수신)
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribe = v2EventBus.on(
|
||||||
|
V2_EVENTS.SCHEDULE_GENERATE_APPLY,
|
||||||
|
async (payload) => {
|
||||||
|
if (payload.requestId !== currentRequestId) return;
|
||||||
|
if (!payload.confirmed) {
|
||||||
|
setShowConfirmDialog(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await apiClient.post("/api/schedule/apply", {
|
||||||
|
config: scheduleConfig,
|
||||||
|
preview: previewResult,
|
||||||
|
options: { deleteExisting: true, updateMode: "replace" },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.data.success) {
|
||||||
|
toast.error(response.data.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 완료 이벤트 발송
|
||||||
|
v2EventBus.emit(V2_EVENTS.SCHEDULE_GENERATE_COMPLETE, {
|
||||||
|
requestId: payload.requestId,
|
||||||
|
success: true,
|
||||||
|
applied: response.data.applied,
|
||||||
|
scheduleType: scheduleConfig.scheduleType,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 테이블 새로고침 이벤트 발송
|
||||||
|
v2EventBus.emit(V2_EVENTS.TABLE_REFRESH, {
|
||||||
|
tableName: scheduleConfig.target.tableName,
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.success(`${response.data.applied.created}건의 스케줄이 생성되었습니다.`);
|
||||||
|
setShowConfirmDialog(false);
|
||||||
|
} catch (error: any) {
|
||||||
|
toast.error("스케줄 적용 중 오류가 발생했습니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return unsubscribe;
|
||||||
|
}, [currentRequestId, previewResult, scheduleConfig]);
|
||||||
|
|
||||||
|
// 확인 다이얼로그 핸들러
|
||||||
|
const handleConfirm = (confirmed: boolean) => {
|
||||||
|
v2EventBus.emit(V2_EVENTS.SCHEDULE_GENERATE_APPLY, {
|
||||||
|
requestId: currentRequestId,
|
||||||
|
confirmed,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
showConfirmDialog,
|
||||||
|
previewResult,
|
||||||
|
handleConfirm,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultPeriod() {
|
||||||
|
const now = new Date();
|
||||||
|
const start = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||||
|
const end = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||||
|
return {
|
||||||
|
start: start.toISOString().split("T")[0],
|
||||||
|
end: end.toISOString().split("T")[0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.4 타임라인 컴포넌트 (이벤트 수신)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// v2-timeline-scheduler에서 이벤트 수신
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 스케줄 생성 완료 시 자동 새로고침
|
||||||
|
const unsubscribe1 = v2EventBus.on(
|
||||||
|
V2_EVENTS.SCHEDULE_GENERATE_COMPLETE,
|
||||||
|
(payload) => {
|
||||||
|
if (payload.success && payload.scheduleType === config.scheduleType) {
|
||||||
|
fetchSchedules();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// TABLE_REFRESH 이벤트로도 새로고침
|
||||||
|
const unsubscribe2 = v2EventBus.on(
|
||||||
|
V2_EVENTS.TABLE_REFRESH,
|
||||||
|
(payload) => {
|
||||||
|
if (payload.tableName === config.selectedTable) {
|
||||||
|
fetchSchedules();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribe1();
|
||||||
|
unsubscribe2();
|
||||||
|
};
|
||||||
|
}, [config.selectedTable, config.scheduleType]);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.5 버튼 액션 핸들러 (이벤트 발송)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// frontend/lib/utils/buttonActions.ts
|
||||||
|
|
||||||
|
// 기존 handleButtonAction에 추가
|
||||||
|
case "event":
|
||||||
|
const eventName = action.eventName as keyof typeof V2_EVENTS;
|
||||||
|
const eventPayload = {
|
||||||
|
requestId: crypto.randomUUID(),
|
||||||
|
...action.eventPayload,
|
||||||
|
};
|
||||||
|
|
||||||
|
v2EventBus.emit(V2_EVENTS[eventName], eventPayload);
|
||||||
|
return true;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 컴포넌트 연동 설정
|
||||||
|
|
||||||
|
### 7.1 분할 패널 연결 필터 (linkedFilters)
|
||||||
|
|
||||||
|
좌측 테이블 선택 시 우측 타임라인 자동 필터링:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"componentType": "v2-split-panel-layout",
|
||||||
|
"componentConfig": {
|
||||||
|
"linkedFilters": [
|
||||||
|
{
|
||||||
|
"sourceComponentId": "order_table",
|
||||||
|
"sourceField": "part_code",
|
||||||
|
"targetColumn": "resource_id"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 타임라인 설정
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"componentType": "v2-timeline-scheduler",
|
||||||
|
"componentId": "production_timeline",
|
||||||
|
"componentConfig": {
|
||||||
|
"selectedTable": "production_plan_mng",
|
||||||
|
"fieldMapping": {
|
||||||
|
"id": "schedule_id",
|
||||||
|
"resourceId": "resource_id",
|
||||||
|
"title": "schedule_name",
|
||||||
|
"startDate": "start_date",
|
||||||
|
"endDate": "end_date",
|
||||||
|
"status": "status"
|
||||||
|
},
|
||||||
|
"useLinkedFilter": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 이벤트 흐름도 (Event-Driven)
|
||||||
|
|
||||||
|
```
|
||||||
|
[좌측 테이블 선택]
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
v2-table-grouped.onSelectionChange
|
||||||
|
│
|
||||||
|
▼ emit(TABLE_SELECTION_CHANGE)
|
||||||
|
│
|
||||||
|
├───────────────────────────────────────────────────┐
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
ScheduleGeneratorService SplitPanelContext
|
||||||
|
(selectedData 저장) (linkedFilter 업데이트)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
v2-timeline-scheduler
|
||||||
|
(자동 필터링)
|
||||||
|
|
||||||
|
|
||||||
|
[자동 스케줄 생성 버튼 클릭]
|
||||||
|
│
|
||||||
|
▼ emit(SCHEDULE_GENERATE_REQUEST)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
ScheduleGeneratorService (이벤트 리스너)
|
||||||
|
│
|
||||||
|
├─── selectedData (이미 저장됨)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
POST /api/schedule/preview
|
||||||
|
│
|
||||||
|
▼ emit(SCHEDULE_GENERATE_PREVIEW)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
확인 다이얼로그 표시
|
||||||
|
│
|
||||||
|
▼ (확인 클릭) emit(SCHEDULE_GENERATE_APPLY)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
POST /api/schedule/apply
|
||||||
|
│
|
||||||
|
├─── emit(SCHEDULE_GENERATE_COMPLETE)
|
||||||
|
│
|
||||||
|
├─── emit(TABLE_REFRESH)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
v2-timeline-scheduler (on TABLE_REFRESH)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
fetchSchedules() → 화면 갱신
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.4 이벤트 시퀀스 다이어그램
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────┐ ┌──────────┐ ┌──────────────┐ ┌──────────┐ ┌──────────┐
|
||||||
|
│ Table │ │ Button │ │ ScheduleSvc │ │ Backend │ │ Timeline │
|
||||||
|
└────┬─────┘ └────┬─────┘ └──────┬───────┘ └────┬─────┘ └────┬─────┘
|
||||||
|
│ │ │ │ │
|
||||||
|
│ SELECT │ │ │ │
|
||||||
|
├──────────────────────────────▶ │ │ │
|
||||||
|
│ TABLE_SELECTION_CHANGE │ │ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ CLICK │ │ │
|
||||||
|
│ ├────────────────▶│ │ │
|
||||||
|
│ │ SCHEDULE_GENERATE_REQUEST │ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ ├────────────────▶│ │
|
||||||
|
│ │ │ POST /preview │ │
|
||||||
|
│ │ │◀────────────────┤ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ │ CONFIRM DIALOG │ │
|
||||||
|
│ │ │─────────────────│ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ ├────────────────▶│ │
|
||||||
|
│ │ │ POST /apply │ │
|
||||||
|
│ │ │◀────────────────┤ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ ├─────────────────────────────────▶
|
||||||
|
│ │ │ SCHEDULE_GENERATE_COMPLETE │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ├─────────────────────────────────▶
|
||||||
|
│ │ │ TABLE_REFRESH │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │ ├──▶ refresh
|
||||||
|
│ │ │ │ │
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 범용성 활용 가이드
|
||||||
|
|
||||||
|
### 8.1 다른 화면에서 재사용
|
||||||
|
|
||||||
|
| 화면 | 소스 테이블 | 그룹 필드 | 스케줄 타입 | 리소스 타입 |
|
||||||
|
|------|------------|----------|------------|------------|
|
||||||
|
| 생산계획 | sales_order_mng | part_code | PRODUCTION | ITEM |
|
||||||
|
| 설비계획 | work_order_mng | machine_code | MAINTENANCE | MACHINE |
|
||||||
|
| 출하계획 | shipment_order_mng | customer_code | SHIPPING | CUSTOMER |
|
||||||
|
| 작업자 배치 | task_mng | worker_id | WORK_ASSIGN | WORKER |
|
||||||
|
|
||||||
|
### 8.2 새 화면 추가 시 체크리스트
|
||||||
|
|
||||||
|
- [ ] 소스 테이블 정의 (어떤 데이터를 선택할 것인지)
|
||||||
|
- [ ] 그룹화 기준 필드 정의 (품목, 설비, 고객 등)
|
||||||
|
- [ ] 스케줄 테이블 생성 또는 기존 schedule_mng 사용
|
||||||
|
- [ ] ScheduleGenerationConfig 작성
|
||||||
|
- [ ] 버튼에 scheduleConfig 설정
|
||||||
|
- [ ] 분할 패널 linkedFilters 설정
|
||||||
|
- [ ] 타임라인 fieldMapping 설정
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 구현 순서
|
||||||
|
|
||||||
|
| 단계 | 작업 | 상태 |
|
||||||
|
|------|------|------|
|
||||||
|
| 1 | 테이블 마이그레이션 (schedule_mng, schedule_source_mapping) | 대기 |
|
||||||
|
| 2 | 백엔드 API (scheduleController, scheduleService) | 대기 |
|
||||||
|
| 3 | 버튼 액션 핸들러 (autoGenerateSchedule) | 대기 |
|
||||||
|
| 4 | 확인 다이얼로그 (기존 AlertDialog 활용) | 대기 |
|
||||||
|
| 5 | 타임라인 linkedFilter 연동 | 대기 |
|
||||||
|
| 6 | 테스트 및 검증 | 대기 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 참고 사항
|
||||||
|
|
||||||
|
### 관련 컴포넌트
|
||||||
|
|
||||||
|
- `v2-table-grouped`: 그룹화된 테이블 (소스 데이터, TABLE_SELECTION_CHANGE 발송)
|
||||||
|
- `v2-timeline-scheduler`: 타임라인 스케줄러 (TABLE_REFRESH 수신)
|
||||||
|
- `v2-button-primary`: 액션 버튼 (SCHEDULE_GENERATE_REQUEST 발송)
|
||||||
|
- `v2-split-panel-layout`: 분할 패널
|
||||||
|
|
||||||
|
### 관련 파일
|
||||||
|
|
||||||
|
- `frontend/lib/v2-core/events/types.ts`: 이벤트 타입 정의
|
||||||
|
- `frontend/lib/v2-core/events/EventBus.ts`: 이벤트 버스
|
||||||
|
- `frontend/lib/v2-core/services/ScheduleGeneratorService.ts`: 스케줄 생성 서비스 (이벤트 리스너)
|
||||||
|
- `frontend/lib/utils/buttonActions.ts`: 버튼 액션 핸들러 (이벤트 발송)
|
||||||
|
- `backend-node/src/services/scheduleService.ts`: 스케줄 서비스
|
||||||
|
- `backend-node/src/controllers/scheduleController.ts`: 스케줄 컨트롤러
|
||||||
|
|
||||||
|
### 특이 사항
|
||||||
|
|
||||||
|
- v2-table-grouped의 `selectedItems`는 그룹 선택 시 자식 행까지 포함됨
|
||||||
|
- 스케줄 생성 시 기존 스케줄과 비교하여 변경사항만 적용 (미리보기 제공)
|
||||||
|
- source_table, source_id로 소스 추적 가능
|
||||||
|
- **컴포넌트 ID 직접 참조 없음** - 이벤트 버스로 느슨한 결합
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 이벤트 버스 패턴의 장점
|
||||||
|
|
||||||
|
### 11.1 기존 방식 vs 이벤트 버스 방식
|
||||||
|
|
||||||
|
| 항목 | 기존 (직접 참조) | 이벤트 버스 |
|
||||||
|
|------|------------------|-------------|
|
||||||
|
| 결합도 | 강 (componentId 필요) | 약 (이벤트명만 필요) |
|
||||||
|
| 버튼 설정 | `source.componentId: "order_table"` | `eventPayload.scheduleType: "PRODUCTION"` |
|
||||||
|
| 컴포넌트 교체 | 설정 수정 필요 | 이벤트만 발송/수신하면 됨 |
|
||||||
|
| 테스트 | 컴포넌트 모킹 필요 | 이벤트 발송으로 테스트 가능 |
|
||||||
|
| 디버깅 | 쉬움 | 이벤트 로깅 필요 |
|
||||||
|
|
||||||
|
### 11.2 확장성
|
||||||
|
|
||||||
|
새로운 컴포넌트 추가 시:
|
||||||
|
1. 기존 컴포넌트 수정 불필요
|
||||||
|
2. 새 컴포넌트에서 이벤트 구독만 추가
|
||||||
|
3. 이벤트 페이로드 구조만 유지하면 됨
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 새로운 컴포넌트에서 스케줄 생성 완료 이벤트 구독
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribe = v2EventBus.on(
|
||||||
|
V2_EVENTS.SCHEDULE_GENERATE_COMPLETE,
|
||||||
|
(payload) => {
|
||||||
|
// 새로운 로직 추가
|
||||||
|
console.log("스케줄 생성 완료:", payload);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return unsubscribe;
|
||||||
|
}, []);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 11.3 디버깅 팁
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 이벤트 디버깅용 전역 리스너 (개발 환경에서만)
|
||||||
|
if (process.env.NODE_ENV === "development") {
|
||||||
|
v2EventBus.on("*", (event, payload) => {
|
||||||
|
console.log(`[EventBus] ${event}:`, payload);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -1209,17 +1209,117 @@ v2-table-list (생산계획 목록)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 16. 관련 문서
|
## 16. 자동 스케줄 생성 기능
|
||||||
|
|
||||||
|
> 상세 가이드: [스케줄 자동 생성 기능 구현 가이드](../00_analysis/schedule-auto-generation-guide.md)
|
||||||
|
|
||||||
|
### 16.1 개요
|
||||||
|
|
||||||
|
좌측 수주 테이블에서 품목을 선택하고 "자동 스케줄 생성" 버튼을 클릭하면, 선택된 품목들에 대한 생산 스케줄이 자동으로 생성되어 우측 타임라인에 표시됩니다.
|
||||||
|
|
||||||
|
### 16.2 데이터 흐름
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 좌측 v2-table-grouped에서 품목 선택 (그룹 선택 시 자식 포함)
|
||||||
|
2. "자동 스케줄 생성" 버튼 클릭
|
||||||
|
3. 백엔드 API에서 미리보기 생성 (생성/삭제/수정될 스케줄)
|
||||||
|
4. 변경사항 확인 다이얼로그 표시
|
||||||
|
5. 확인 시 스케줄 적용 및 타임라인 새로고침
|
||||||
|
6. 다음 방문 시: 좌측 선택 → linkedFilter로 우측 자동 필터링
|
||||||
|
```
|
||||||
|
|
||||||
|
### 16.3 스케줄 생성 설정
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scheduleType": "PRODUCTION",
|
||||||
|
"source": {
|
||||||
|
"componentId": "order_table",
|
||||||
|
"tableName": "sales_order_mng",
|
||||||
|
"groupByField": "part_code",
|
||||||
|
"quantityField": "balance_qty",
|
||||||
|
"dueDateField": "due_date"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"type": "ITEM",
|
||||||
|
"idField": "part_code",
|
||||||
|
"nameField": "part_name"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"leadTimeDays": 3,
|
||||||
|
"dailyCapacity": 100,
|
||||||
|
"workingDays": [1, 2, 3, 4, 5],
|
||||||
|
"considerStock": true,
|
||||||
|
"stockTableName": "inventory_mng",
|
||||||
|
"stockQtyField": "current_qty"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"tableName": "production_plan_mng",
|
||||||
|
"timelineComponentId": "production_timeline"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 16.4 버튼 설정
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"componentType": "v2-button-primary",
|
||||||
|
"componentId": "btn_auto_schedule",
|
||||||
|
"componentConfig": {
|
||||||
|
"label": "자동 스케줄 생성",
|
||||||
|
"variant": "default",
|
||||||
|
"icon": "Calendar",
|
||||||
|
"action": {
|
||||||
|
"type": "custom",
|
||||||
|
"customAction": "autoGenerateSchedule",
|
||||||
|
"scheduleConfig": { /* 위 설정 */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 16.5 연결 필터 설정 (linkedFilters)
|
||||||
|
|
||||||
|
좌측 테이블 선택 시 우측 타임라인 자동 필터링:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"linkedFilters": [
|
||||||
|
{
|
||||||
|
"sourceComponentId": "order_table",
|
||||||
|
"sourceField": "part_code",
|
||||||
|
"targetColumn": "resource_id"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 16.6 구현 상태
|
||||||
|
|
||||||
|
| 항목 | 상태 | 비고 |
|
||||||
|
|------|:----:|------|
|
||||||
|
| schedule_mng 테이블 | ⏳ 대기 | 범용 스케줄 테이블 |
|
||||||
|
| /api/schedule/preview API | ⏳ 대기 | 미리보기 |
|
||||||
|
| /api/schedule/apply API | ⏳ 대기 | 적용 |
|
||||||
|
| autoGenerateSchedule 버튼 액션 | ⏳ 대기 | buttonActions.ts |
|
||||||
|
| 확인 다이얼로그 | ⏳ 대기 | 기존 AlertDialog 활용 |
|
||||||
|
| linkedFilter 연동 | ⏳ 대기 | 타임라인 필터링 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 17. 관련 문서
|
||||||
|
|
||||||
- [수주관리](../02_sales/order.md)
|
- [수주관리](../02_sales/order.md)
|
||||||
- [품목정보](../01_master-data/item-info.md)
|
- [품목정보](../01_master-data/item-info.md)
|
||||||
- [설비관리](../05_equipment/equipment-info.md)
|
- [설비관리](../05_equipment/equipment-info.md)
|
||||||
- [BOM관리](../01_master-data/bom.md)
|
- [BOM관리](../01_master-data/bom.md)
|
||||||
- [작업지시](./work-order.md)
|
- [작업지시](./work-order.md)
|
||||||
|
- **[스케줄 자동 생성 기능 가이드](../00_analysis/schedule-auto-generation-guide.md)**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 17. 참고: 표준 가이드
|
## 18. 참고: 표준 가이드
|
||||||
|
|
||||||
- [화면개발 표준 가이드](../화면개발_표준_가이드.md)
|
- [화면개발 표준 가이드](../화면개발_표준_가이드.md)
|
||||||
- [V2 컴포넌트 사용 가이드](../00_analysis/v2-component-usage-guide.md)
|
- [V2 컴포넌트 사용 가이드](../00_analysis/v2-component-usage-guide.md)
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,13 @@ export const V2_EVENTS = {
|
||||||
RELATED_BUTTON_REGISTER: "v2:related-button:register",
|
RELATED_BUTTON_REGISTER: "v2:related-button:register",
|
||||||
RELATED_BUTTON_UNREGISTER: "v2:related-button:unregister",
|
RELATED_BUTTON_UNREGISTER: "v2:related-button:unregister",
|
||||||
RELATED_BUTTON_SELECT: "v2:related-button:select",
|
RELATED_BUTTON_SELECT: "v2:related-button:select",
|
||||||
|
|
||||||
|
// 스케줄 자동 생성
|
||||||
|
SCHEDULE_GENERATE_REQUEST: "v2:schedule:generate:request",
|
||||||
|
SCHEDULE_GENERATE_PREVIEW: "v2:schedule:generate:preview",
|
||||||
|
SCHEDULE_GENERATE_APPLY: "v2:schedule:generate:apply",
|
||||||
|
SCHEDULE_GENERATE_COMPLETE: "v2:schedule:generate:complete",
|
||||||
|
SCHEDULE_GENERATE_ERROR: "v2:schedule:generate:error",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type V2EventName = (typeof V2_EVENTS)[keyof typeof V2_EVENTS];
|
export type V2EventName = (typeof V2_EVENTS)[keyof typeof V2_EVENTS];
|
||||||
|
|
@ -230,6 +237,64 @@ export interface V2RelatedButtonSelectEvent {
|
||||||
selectedData: any[];
|
selectedData: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 스케줄 자동 생성 이벤트
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/** 스케줄 타입 */
|
||||||
|
export type ScheduleType = "PRODUCTION" | "MAINTENANCE" | "SHIPPING" | "WORK_ASSIGN";
|
||||||
|
|
||||||
|
/** 스케줄 생성 요청 이벤트 */
|
||||||
|
export interface V2ScheduleGenerateRequestEvent {
|
||||||
|
requestId: string;
|
||||||
|
scheduleType: ScheduleType;
|
||||||
|
sourceData?: any[]; // 선택 데이터 (없으면 TABLE_SELECTION_CHANGE로 받은 데이터 사용)
|
||||||
|
period?: { start: string; end: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 스케줄 미리보기 결과 이벤트 */
|
||||||
|
export interface V2ScheduleGeneratePreviewEvent {
|
||||||
|
requestId: string;
|
||||||
|
scheduleType: ScheduleType;
|
||||||
|
preview: {
|
||||||
|
toCreate: any[];
|
||||||
|
toDelete: any[];
|
||||||
|
toUpdate: any[];
|
||||||
|
summary: {
|
||||||
|
createCount: number;
|
||||||
|
deleteCount: number;
|
||||||
|
updateCount: number;
|
||||||
|
totalQty: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 스케줄 적용 이벤트 */
|
||||||
|
export interface V2ScheduleGenerateApplyEvent {
|
||||||
|
requestId: string;
|
||||||
|
confirmed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 스케줄 생성 완료 이벤트 */
|
||||||
|
export interface V2ScheduleGenerateCompleteEvent {
|
||||||
|
requestId: string;
|
||||||
|
success: boolean;
|
||||||
|
applied: {
|
||||||
|
created: number;
|
||||||
|
deleted: number;
|
||||||
|
updated: number;
|
||||||
|
};
|
||||||
|
scheduleType: ScheduleType;
|
||||||
|
targetTableName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 스케줄 생성 에러 이벤트 */
|
||||||
|
export interface V2ScheduleGenerateErrorEvent {
|
||||||
|
requestId: string;
|
||||||
|
error: string;
|
||||||
|
scheduleType?: ScheduleType;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 이벤트 타입 맵핑 (타입 안전성을 위한)
|
// 이벤트 타입 맵핑 (타입 안전성을 위한)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
@ -268,6 +333,12 @@ export interface V2EventPayloadMap {
|
||||||
[V2_EVENTS.RELATED_BUTTON_REGISTER]: V2RelatedButtonRegisterEvent;
|
[V2_EVENTS.RELATED_BUTTON_REGISTER]: V2RelatedButtonRegisterEvent;
|
||||||
[V2_EVENTS.RELATED_BUTTON_UNREGISTER]: V2RelatedButtonUnregisterEvent;
|
[V2_EVENTS.RELATED_BUTTON_UNREGISTER]: V2RelatedButtonUnregisterEvent;
|
||||||
[V2_EVENTS.RELATED_BUTTON_SELECT]: V2RelatedButtonSelectEvent;
|
[V2_EVENTS.RELATED_BUTTON_SELECT]: V2RelatedButtonSelectEvent;
|
||||||
|
|
||||||
|
[V2_EVENTS.SCHEDULE_GENERATE_REQUEST]: V2ScheduleGenerateRequestEvent;
|
||||||
|
[V2_EVENTS.SCHEDULE_GENERATE_PREVIEW]: V2ScheduleGeneratePreviewEvent;
|
||||||
|
[V2_EVENTS.SCHEDULE_GENERATE_APPLY]: V2ScheduleGenerateApplyEvent;
|
||||||
|
[V2_EVENTS.SCHEDULE_GENERATE_COMPLETE]: V2ScheduleGenerateCompleteEvent;
|
||||||
|
[V2_EVENTS.SCHEDULE_GENERATE_ERROR]: V2ScheduleGenerateErrorEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue