diff --git a/src/main/java/ditda/backend/domain/commission/core/event/RevisionRequestedEvent.java b/src/main/java/ditda/backend/domain/commission/core/event/RevisionRequestedEvent.java new file mode 100644 index 0000000..8ed6fbd --- /dev/null +++ b/src/main/java/ditda/backend/domain/commission/core/event/RevisionRequestedEvent.java @@ -0,0 +1,13 @@ +package ditda.backend.domain.commission.core.event; + +import java.time.LocalDateTime; + +public record RevisionRequestedEvent( + Long commissionId, + String commissionTitle, + String designerEmail, + String designerName, + int currentRevisionCount, + LocalDateTime mailScheduledAt +) { +} diff --git a/src/main/java/ditda/backend/domain/commission/core/notification/RevisionRequestedNotifier.java b/src/main/java/ditda/backend/domain/commission/core/notification/RevisionRequestedNotifier.java new file mode 100644 index 0000000..a3270ec --- /dev/null +++ b/src/main/java/ditda/backend/domain/commission/core/notification/RevisionRequestedNotifier.java @@ -0,0 +1,45 @@ +package ditda.backend.domain.commission.core.notification; + +import java.time.LocalDateTime; +import java.util.Map; + +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +import ditda.backend.domain.commission.core.event.RevisionRequestedEvent; +import ditda.backend.global.email.NotificationOutbox; +import ditda.backend.global.email.NotificationOutboxRepository; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class RevisionRequestedNotifier { + + private final NotificationOutboxRepository outboxRepository; + + @EventListener + public void onRevisionRequested(RevisionRequestedEvent event) { + + // 메일 전송 시간 + LocalDateTime mailScheduledAt = event.mailScheduledAt(); + + registerDesignerRevisionRequested(event, mailScheduledAt); + } + + private void registerDesignerRevisionRequested( + RevisionRequestedEvent event, + LocalDateTime mailScheduledAt + ) { + outboxRepository.save(NotificationOutbox.create( + event.designerEmail(), + "[DITDA] 시안 수정 요청이 도착했습니다. 확인해주세요.", + "email/revision-requested-designer", + Map.of( + "designerName", event.designerName(), + "commissionTitle", event.commissionTitle(), + "currentRevisionCount", event.currentRevisionCount() + ), + mailScheduledAt + )); + } +} diff --git a/src/main/java/ditda/backend/domain/commission/core/service/InstructorCommissionService.java b/src/main/java/ditda/backend/domain/commission/core/service/InstructorCommissionService.java index 2b5db3e..4e91881 100644 --- a/src/main/java/ditda/backend/domain/commission/core/service/InstructorCommissionService.java +++ b/src/main/java/ditda/backend/domain/commission/core/service/InstructorCommissionService.java @@ -111,6 +111,19 @@ public Commission getOwnedCommission(Long commissionId, Long instructorId) { return commission; } + // 외주 조회(instructor/designer fetch) + 강사 확인 + @Transactional(readOnly = true) + public Commission getOwnedCommissionWithInstructorAndAssignedDesigner(Long commissionId, Long instructorId) { + + Commission commission = commissionRepository.findWithInstructorAndAssignedDesignerById(commissionId) + .orElseThrow(() -> new GeneralException(CommissionErrorCode.COMMISSION_NOT_FOUND)); + + if (!commission.isOwnedBy(instructorId)) { + throw new GeneralException(CommissionErrorCode.COMMISSION_ACCESS_DENIED); + } + return commission; + } + // 디자이너 선택 @Transactional public void selectDesigner(Commission commission, Designer designer, LocalDateTime now) { diff --git a/src/main/java/ditda/backend/domain/commission/revision/facade/InstructorRevisionFacade.java b/src/main/java/ditda/backend/domain/commission/revision/facade/InstructorRevisionFacade.java index ed3d045..e0b8e6b 100644 --- a/src/main/java/ditda/backend/domain/commission/revision/facade/InstructorRevisionFacade.java +++ b/src/main/java/ditda/backend/domain/commission/revision/facade/InstructorRevisionFacade.java @@ -1,9 +1,14 @@ package ditda.backend.domain.commission.revision.facade; +import java.time.LocalDateTime; +import java.time.ZoneId; + +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import ditda.backend.domain.commission.core.entity.Commission; +import ditda.backend.domain.commission.core.event.RevisionRequestedEvent; import ditda.backend.domain.commission.core.service.InstructorCommissionService; import ditda.backend.domain.commission.draft.entity.CommissionDraft; import ditda.backend.domain.commission.draft.entity.CommissionDraftFile; @@ -14,6 +19,7 @@ import ditda.backend.domain.commission.revision.mapper.RevisionMapper; import ditda.backend.domain.commission.revision.service.InstructorRevisionService; import ditda.backend.domain.commission.revision.service.RevisionQueryService; +import ditda.backend.domain.user.entity.User; import ditda.backend.global.apipayload.exception.GeneralException; import lombok.RequiredArgsConstructor; @@ -22,11 +28,14 @@ @Transactional(readOnly = true) public class InstructorRevisionFacade { + private static final ZoneId ZONE_KST = ZoneId.of("Asia/Seoul"); + private final InstructorCommissionService instructorCommissionService; private final RevisionQueryService revisionQueryService; private final InstructorRevisionService instructorRevisionService; private final DraftQueryService draftQueryService; private final RevisionMapper revisionMapper; + private final ApplicationEventPublisher eventPublisher; @Transactional public InstructorRevisionDetailResponse getRevisionDetail(Long instructorId, Long commissionId) { @@ -62,7 +71,8 @@ public InstructorRevisionDetailResponse getRevisionDetail(Long instructorId, Lon public void createRevision(Long instructorId, Long commissionId, RevisionCreateRequest request) { // 외주 조회 + 강사 확인 - Commission commission = instructorCommissionService.getOwnedCommission(commissionId, instructorId); + Commission commission = + instructorCommissionService.getOwnedCommissionWithInstructorAndAssignedDesigner(commissionId, instructorId); // 수정 단계인지 검증 commission.validateRevisable(); @@ -86,6 +96,12 @@ public void createRevision(Long instructorId, Long commissionId, RevisionCreateR // 수정 요청 저장 instructorRevisionService.createRevisionRequest(commission, latestDraft, request); + + // 생성된 수정 요청을 반영한 수정 횟수 + int currentRevisionCount = current + 1; + + // 디자이너에게 수정 요청 도착 메일 발송 + publishRevisionRequestedEvent(commission, currentRevisionCount); } // 카테고리 중복 검증 @@ -100,4 +116,19 @@ private void validateDistinctCategories(RevisionCreateRequest request) { throw new GeneralException(RevisionErrorCode.DUPLICATE_REVISION_CATEGORY); } } + + // 수정 요청 알림 이벤트 발행 + private void publishRevisionRequestedEvent(Commission commission, int currentRevisionCount) { + + User designerUser = commission.getAssignedDesigner().getUser(); + + eventPublisher.publishEvent(new RevisionRequestedEvent( + commission.getId(), + commission.getTitle(), + designerUser.getEmail(), + designerUser.getName(), + currentRevisionCount, + LocalDateTime.now(ZONE_KST) + )); + } } diff --git a/src/main/resources/templates/email/revision-requested-designer.html b/src/main/resources/templates/email/revision-requested-designer.html new file mode 100644 index 0000000..5bf9529 --- /dev/null +++ b/src/main/resources/templates/email/revision-requested-designer.html @@ -0,0 +1,49 @@ + + +
+ + + +
+ 강사님으로부터 시안 수정 요청이 도착했습니다.
+ 요청 내용을 확인하시고 가능한 빠르게 수정본을 제출해 주세요.
+
+ 제출이 지연되면 남은 수정 기회에 영향을 줄 수 있으니 신속히 진행해 주세요. +
+ ++ 본 메일은 발신 전용입니다. +
+