파일
wbx-spring/wbx-spring-core/HanwhaOCN/wtmgr/05-approval-handlers.md
accura0117 df723f1d59 feat: WTM 멀티프로젝트 플랫폼 구축 (BE + FE 전체 구현)
Phase 0: wbx-spring-core 라이브러리 전환
- java-library 플러그인, WbxAutoConfiguration, Admin 조건부 활성화
- 루트 settings.gradle + build.gradle (멀티모듈)

Phase 1: wtm-api 모듈 생성
- 23개 JPA Entity, 14개 Controller, 79개 API 엔드포인트
- Flyway V100~V107 MySQL 마이그레이션
- TimesheetRuleEngine, TimesheetApprovalHandler, P6WbsParser

Phase 2: wtm-frontend (Vue 3 + PrimeVue 4)
- 10개 도메인 모듈, 17개 View, 5개 서브컴포넌트
- 반응형 레이아웃 (AppLayout, AppSidebar, AppTopbar)
- BaseCrudTable, BaseFormDialog, BasePageHeader 표준 컴포넌트
- JWT 인터셉터, 역할 기반 메뉴 필터링

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 21:01:43 +09:00

98 줄
3.0 KiB
Markdown

# 05. 결재 핸들러 구현
> **전제**: wbx-spring 결재 엔진(`ApprovalHandler` interface, `ApprovalHandlerRegistry`,
> `UnifiedApprovalController`, `BaseApprovalLine`, `ApprovalCompletedEvent`)이 제공됨.
>
> WTM은 **핸들러만 구현**하면 결재 API가 자동으로 활성화됩니다.
## WTM이 구현하는 핸들러
```java
@Component
@RequiredArgsConstructor
public class TimesheetApprovalHandler implements ApprovalHandler {
@Override
public String getTypeKey() { return "timesheet"; }
@Override
public String getTypeDisplay() { return "시수 결재"; }
@Override
@Transactional
public ApprovalResult approve(Long lineId, Long approverId, String comment) {
TtApprovalLine line = lineRepository.findById(lineId).orElseThrow();
line.approve();
// 다음 결재자 확인
Optional<TtApprovalLine> next = lineRepository
.findNextPending(line.getApprovalId(), line.getApprovalOrder());
Timesheet ts = line.getApproval().getTimesheet();
if (next.isPresent()) {
ts.setStatus(TimesheetStatus.DL_APPROVED);
notificationService.sendToUser(next.get().getApproverId(),
NotificationDto.approvalRequest("시수 결재 요청", ts));
} else {
ts.setStatus(TimesheetStatus.APPROVED);
line.getApproval().complete();
eventPublisher.publishEvent(new ApprovalCompletedEvent(
"timesheet", ts.getId(), approverId, line.getApproval()));
}
return ApprovalResult.success("승인 완료");
}
@Override
@Transactional
public ApprovalResult reject(Long lineId, Long approverId, String comment) {
TtApprovalLine line = lineRepository.findById(lineId).orElseThrow();
line.reject();
line.setComment(comment);
line.getApproval().getTimesheet().setStatus(TimesheetStatus.REJECTED);
return ApprovalResult.success("반려 완료");
}
// getApprovalHistory(), getPending() 구현 ...
}
```
## 결재 흐름 (User → DL → PM)
```
User 시수 제출
TimesheetService.submit()
├── Timesheet.status = SUBMITTED
├── TtApprovalLine #1 (DL) + #2 (PM) 생성
└── DL에게 SSE 알림 (wbx-spring notificationService)
DL 승인 → POST /api/wtm/approvals/unified/action/timesheet/{lineId}/approve
├── wbx-spring UnifiedApprovalController → TimesheetApprovalHandler
└── PM에게 SSE 알림
PM 승인 → 최종
├── ApprovalCompletedEvent 발행
└── User에게 승인 완료 알림
```
## 미완료 Timesheet 리마인더 (No.70)
```java
@Component
public class TimesheetReminderScheduler {
private final SseNotificationService notificationService; // wbx-spring 제공
@Scheduled(cron = "0 0 17 * * MON-FRI")
public void dailyReminder() {
// 당일 미입력 사용자에게 알림
}
@Scheduled(cron = "0 0 10 * * FRI")
public void weeklySubmitReminder() {
// 주간 미제출 사용자에게 알림
}
}
```