Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
) {
}
Original file line number Diff line number Diff line change
@@ -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
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;

Expand All @@ -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) {
Expand Down Expand Up @@ -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();
Expand All @@ -86,6 +96,12 @@ public void createRevision(Long instructorId, Long commissionId, RevisionCreateR

// 수정 요청 저장
instructorRevisionService.createRevisionRequest(commission, latestDraft, request);

// 생성된 수정 요청을 반영한 수정 횟수
int currentRevisionCount = current + 1;

// 디자이너에게 수정 요청 도착 메일 발송
publishRevisionRequestedEvent(commission, currentRevisionCount);
}

// 카테고리 중복 검증
Expand All @@ -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)
));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
</head>
<body>
<div style="font-family: 'Apple SD Gothic Neo', sans-serif; max-width: 480px; margin: 0 auto; padding: 24px;">

<div style="text-align: center; margin-bottom: 24px;">
<img alt="ditda" src="cid:logoImage" style="height: 48px;">
</div>

<div style="text-align: center;">
<h2 style="color: #874fff;">시안 수정 요청 안내</h2>
<p style="color: #222; font-weight: 600; font-size: 16px;" th:text="${designerName} + ' 디자이너님'"></p>
<p style="color: #666; line-height: 1.6;">
강사님으로부터 <strong style="color: #874fff;">시안 수정 요청</strong>이 도착했습니다.<br>
요청 내용을 확인하시고 가능한 빠르게 수정본을 제출해 주세요.
</p>
</div>

<div style="margin-top: 24px; padding: 16px 20px; background: #f5f5f7; border-radius: 8px;">
<div style="margin: 8px 0;">
<span style="display: inline-block; width: 130px; color: #888; font-size: 14px;">외주명</span>
<span style="font-weight: 600;" th:text="${commissionTitle}"></span>
</div>
<div style="margin: 8px 0;">
<span style="display: inline-block; width: 130px; color: #888; font-size: 14px;">수정 차수</span>
<span style="font-weight: 600; color: #874fff;" th:text="${currentRevisionCount} + '차'"></span>
</div>
</div>

<div style="margin-top: 24px; text-align: center;">
<a href="https://ditda.kr/designer"
style="display: inline-block; padding: 12px 24px; background: #874fff; color: #fff; text-decoration: none; border-radius: 8px; font-weight: bold; font-size: 14px;">
수정 요청 확인하기
</a>
</div>

<p style="margin-top: 24px; color: #666; font-size: 14px; line-height: 1.6; text-align: center;">
제출이 지연되면 <strong style="color: #874fff;">남은 수정 기회</strong>에 영향을 줄 수 있으니 신속히 진행해 주세요.
</p>

<p style="color: #999; margin-top: 32px; font-size: 12px; text-align: center; border-top: 1px solid #eee; padding-top: 16px;">
본 메일은 발신 전용입니다.
</p>
</div>
</body>
</html>