diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/order/controller/OrderAdminController.java b/DuDoong-Api/src/main/java/band/gosrock/api/order/controller/OrderAdminController.java index 5aa59054..d083df50 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/order/controller/OrderAdminController.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/order/controller/OrderAdminController.java @@ -10,6 +10,7 @@ import band.gosrock.api.order.service.ApproveOrderUseCase; import band.gosrock.api.order.service.CancelOrderUseCase; import band.gosrock.api.order.service.ReadOrderUseCase; +import band.gosrock.api.order.service.RefuseOrderUseCase; import band.gosrock.common.annotation.ApiErrorExceptionsExample; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; @@ -35,6 +36,8 @@ public class OrderAdminController { private final ReadOrderUseCase readOrderUseCase; private final CancelOrderUseCase cancelOrderUseCase; + private final RefuseOrderUseCase refuseOrderUseCase; + @Operation(summary = "어드민 목록 내 테이블 조회 OrderStage 는 꼭 보내주삼!") @GetMapping public PageResponse getEventOrders( @@ -52,7 +55,7 @@ public OrderResponse cancelOrder( return cancelOrderUseCase.execute(eventId, orderUuid); } - @Operation(summary = "주문 승인하기 . 호스트 관리자가 티켓 주문을 승인합니다. ( 어드민 이벤트쪽으로 이동예정 )") + @Operation(summary = "주문 승인하기 . 호스트 관리자가 티켓 주문을 승인합니다.") @ApiErrorExceptionsExample(ApproveOrderExceptionDocs.class) @PostMapping("/{order_uuid}/approve") public OrderResponse confirmOrder( @@ -60,6 +63,13 @@ public OrderResponse confirmOrder( return approveOrderUseCase.execute(eventId, orderUuid); } + @Operation(summary = "승인 주문 거절하기 . 호스트 관리자가 승인 대기중인 주문을 거절합니다.") + @PostMapping("/{order_uuid}/refuse") + public OrderResponse refuseOrder( + @PathVariable Long eventId, @PathVariable("order_uuid") String orderUuid) { + return refuseOrderUseCase.execute(eventId, orderUuid); + } + @Operation(summary = "주문관리 리스트 페이지에서 주문 상세정보 조회할때") @GetMapping("/{order_uuid}") public OrderResponse getEventOrderDetail( diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/order/service/RefuseOrderUseCase.java b/DuDoong-Api/src/main/java/band/gosrock/api/order/service/RefuseOrderUseCase.java new file mode 100644 index 00000000..ffede5e4 --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/order/service/RefuseOrderUseCase.java @@ -0,0 +1,26 @@ +package band.gosrock.api.order.service; + +import static band.gosrock.api.common.aop.hostRole.FindHostFrom.EVENT_ID; +import static band.gosrock.api.common.aop.hostRole.HostQualification.MANAGER; + +import band.gosrock.api.common.aop.hostRole.HostRolesAllowed; +import band.gosrock.api.order.model.dto.response.OrderResponse; +import band.gosrock.api.order.model.mapper.OrderMapper; +import band.gosrock.common.annotation.UseCase; +import band.gosrock.domain.domains.order.service.WithdrawOrderService; +import lombok.RequiredArgsConstructor; + +@UseCase +@RequiredArgsConstructor +public class RefuseOrderUseCase { + + private final WithdrawOrderService withdrawOrderService; + + private final OrderMapper orderMapper; + + @HostRolesAllowed(role = MANAGER, findHostFrom = EVENT_ID, applyTransaction = false) + public OrderResponse execute(Long eventId, String orderUuid) { + withdrawOrderService.refuseOrder(orderUuid); + return orderMapper.toOrderResponse(orderUuid); + } +} diff --git a/DuDoong-Batch/Dockerfile b/DuDoong-Batch/Dockerfile index 78f7ec77..46caf865 100644 --- a/DuDoong-Batch/Dockerfile +++ b/DuDoong-Batch/Dockerfile @@ -6,4 +6,4 @@ COPY ./build/libs/*.jar app.jar ARG PROFILE=dev ENV PROFILE=${PROFILE} -ENTRYPOINT ["java","-Dspring.profiles.active=${PROFILE}", "-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] \ No newline at end of file +ENTRYPOINT ["java","-Dspring.profiles.active=${PROFILE}", "-Djava.security.egd=file:/dev/./urandom","-jar","-Duser.timezone=Asia/Seoul","/app.jar"] \ No newline at end of file diff --git a/DuDoong-Batch/src/main/java/band/gosrock/excel/ExcelOrderHelper.java b/DuDoong-Batch/src/main/java/band/gosrock/excel/ExcelOrderHelper.java deleted file mode 100644 index 9bb9583d..00000000 --- a/DuDoong-Batch/src/main/java/band/gosrock/excel/ExcelOrderHelper.java +++ /dev/null @@ -1,120 +0,0 @@ -package band.gosrock.excel; - - -import java.lang.reflect.Field; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Arrays; -import java.util.List; -import java.util.stream.IntStream; -import org.apache.poi.ss.usermodel.Cell; -import org.apache.poi.ss.usermodel.CellStyle; -import org.apache.poi.ss.usermodel.FillPatternType; -import org.apache.poi.ss.usermodel.IndexedColors; -import org.apache.poi.ss.usermodel.Row; -import org.apache.poi.ss.usermodel.Sheet; -import org.apache.poi.ss.usermodel.Workbook; -import org.apache.poi.xssf.usermodel.XSSFFont; -import org.apache.poi.xssf.usermodel.XSSFWorkbook; -import org.springframework.stereotype.Component; - -@Component -public class ExcelOrderHelper { - - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); - - public Workbook execute(List excelOrders) { - Workbook workbook = new XSSFWorkbook(); - - Field[] declaredFields = ExcelOrderDto.class.getDeclaredFields(); - - List fieldNames = Arrays.stream(declaredFields).map(Field::getName).toList(); - Sheet sheet = workbook.createSheet("orderList"); - sheet.setColumnWidth(0, 6000); - sheet.setColumnWidth(1, 6000); - sheet.setColumnWidth(2, 6000); - sheet.setColumnWidth(3, 6000); - sheet.setColumnWidth(4, 6000); - sheet.setColumnWidth(5, 6000); - sheet.setColumnWidth(6, 6000); - sheet.setColumnWidth(7, 6000); - sheet.setColumnWidth(8, 6000); - // create header - Row header = sheet.createRow(0); - CellStyle headerStyle = workbook.createCellStyle(); - headerStyle.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex()); - headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); - XSSFFont font = ((XSSFWorkbook) workbook).createFont(); - font.setFontName("Arial"); - font.setFontHeightInPoints((short) 16); - font.setBold(true); - headerStyle.setFont(font); - - Cell headerCell0 = header.createCell(0); - headerCell0.setCellValue("주문번호"); - headerCell0.setCellStyle(headerStyle); - - Cell headerCell1 = header.createCell(1); - headerCell1.setCellValue("주문 방식"); - headerCell1.setCellStyle(headerStyle); - - Cell headerCell2 = header.createCell(2); - headerCell2.setCellValue("주문 상태"); - headerCell2.setCellStyle(headerStyle); - - Cell headerCell3 = header.createCell(3); - headerCell3.setCellValue("주문 이름"); - headerCell3.setCellStyle(headerStyle); - - Cell headerCell4 = header.createCell(4); - headerCell4.setCellValue("주문자 아이디"); - headerCell4.setCellStyle(headerStyle); - - Cell headerCell5 = header.createCell(5); - headerCell5.setCellValue("총액"); - headerCell5.setCellStyle(headerStyle); - - Cell headerCell6 = header.createCell(6); - headerCell6.setCellValue("티켓 수량"); - headerCell6.setCellStyle(headerStyle); - - Cell headerCell7 = header.createCell(7); - headerCell7.setCellValue("생성 일시"); - headerCell7.setCellStyle(headerStyle); - - Cell headerCell8 = header.createCell(8); - headerCell8.setCellValue("환불 일시"); - headerCell8.setCellStyle(headerStyle); - - CellStyle style = workbook.createCellStyle(); - style.setWrapText(true); - IntStream.range(0, excelOrders.size()) - .forEach( - idx -> { - Row row = sheet.createRow(idx + 1); - ExcelOrderDto excelOrderDto = excelOrders.get(idx); - row.createCell(0).setCellValue(excelOrderDto.getOrderNo()); - - row.createCell(1).setCellValue(excelOrderDto.getOrderMethod().getKr()); - - row.createCell(2).setCellValue(excelOrderDto.getOrderStatus().getKr()); - - row.createCell(3).setCellValue(excelOrderDto.getOrderName()); - - row.createCell(4).setCellValue(excelOrderDto.getUserId()); - - row.createCell(5).setCellValue(excelOrderDto.getAmount().toString()); - - row.createCell(6).setCellValue(excelOrderDto.getQuantity()); - - row.createCell(7) - .setCellValue(excelOrderDto.getCreatedAt().format(formatter)); - - LocalDateTime refundAt = excelOrderDto.getRefundAt(); - row.createCell(8) - .setCellValue( - refundAt != null ? refundAt.format(formatter) : null); - }); - return workbook; - } -} diff --git a/DuDoong-Batch/src/main/java/band/gosrock/helper/SettlementEmailHelper.java b/DuDoong-Batch/src/main/java/band/gosrock/helper/SettlementEmailHelper.java new file mode 100644 index 00000000..1a7ec4ca --- /dev/null +++ b/DuDoong-Batch/src/main/java/band/gosrock/helper/SettlementEmailHelper.java @@ -0,0 +1,64 @@ +package band.gosrock.helper; + + +import band.gosrock.common.annotation.Helper; +import band.gosrock.domain.domains.event.domain.Event; +import band.gosrock.infrastructure.config.s3.S3PrivateFileService; +import band.gosrock.infrastructure.config.ses.AwsSesUtils; +import band.gosrock.infrastructure.config.ses.RawEmailAttachmentDto; +import band.gosrock.infrastructure.config.ses.SendRawEmailDto; +import javax.mail.MessagingException; +import lombok.RequiredArgsConstructor; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring5.SpringTemplateEngine; + +@Helper +@RequiredArgsConstructor +public class SettlementEmailHelper { + private final SpringTemplateEngine templateEngine; + + private final S3PrivateFileService s3PrivateFileService; + private final AwsSesUtils awsSesUtils; + + private RawEmailAttachmentDto getSettlementPdfAttachment(Event event) { + return RawEmailAttachmentDto.builder() + .fileName(event.getEventName() + "_정산서.pdf") + .fileBytes(s3PrivateFileService.downloadEventSettlementPdf(event.getId())) + .type("application/pdf") + .build(); + } + + private RawEmailAttachmentDto getOrderListExcelAttachment(Event event) { + return RawEmailAttachmentDto.builder() + .fileName(event.getEventName() + "_주문목록.xlsx") + .fileBytes(s3PrivateFileService.downloadEventOrdersExcel(event.getId())) + .type("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + .build(); + } + + public void sendToAdmin(Event event) throws MessagingException { + SendRawEmailDto sendRawEmailDto = + SendRawEmailDto.builder() + .bodyHtml(templateEngine.process("eventSettlement", new Context())) + .recipient("support@dudoong.com") + .subject(event.getEventName() + "공연 정산서 어드민 발송 ( 관리자용 )") + .build(); + + sendRawEmailDto.addEmailAttachments(getSettlementPdfAttachment(event)); + sendRawEmailDto.addEmailAttachments(getOrderListExcelAttachment(event)); + awsSesUtils.sendRawEmails(sendRawEmailDto); + } + + public void sendToHost(Event event, String hostUserEmail) throws MessagingException { + SendRawEmailDto sendRawEmailDto = + SendRawEmailDto.builder() + .bodyHtml(templateEngine.process("eventSettlement", new Context())) + .recipient(hostUserEmail) + .subject(event.getEventName() + "공연 정산관련 안내") + .build(); + + sendRawEmailDto.addEmailAttachments(getSettlementPdfAttachment(event)); + sendRawEmailDto.addEmailAttachments(getOrderListExcelAttachment(event)); + awsSesUtils.sendRawEmails(sendRawEmailDto); + } +} diff --git a/DuDoong-Batch/src/main/java/band/gosrock/helper/SettlementPdfHelper.java b/DuDoong-Batch/src/main/java/band/gosrock/helper/SettlementPdfHelper.java new file mode 100644 index 00000000..53792d6f --- /dev/null +++ b/DuDoong-Batch/src/main/java/band/gosrock/helper/SettlementPdfHelper.java @@ -0,0 +1,69 @@ +package band.gosrock.helper; + + +import band.gosrock.common.annotation.Helper; +import band.gosrock.domain.common.vo.Money; +import band.gosrock.domain.domains.event.domain.Event; +import band.gosrock.domain.domains.settlement.domain.EventSettlement; +import band.gosrock.domain.domains.user.domain.User; +import band.gosrock.dto.SettlementPDFDto; +import band.gosrock.infrastructure.config.pdf.PdfRender; +import band.gosrock.infrastructure.config.s3.S3PrivateFileService; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.lowagie.text.DocumentException; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring5.SpringTemplateEngine; + +@Helper +@RequiredArgsConstructor +public class SettlementPdfHelper { + private final PdfRender pdfRender; + + private final ObjectMapper objectMapper; + + private final SpringTemplateEngine templateEngine; + + private final S3PrivateFileService s3PrivateFileUploadService; + + public void uploadPdfToS3(Event event, EventSettlement eventSettlement, User masterUser) + throws DocumentException, IOException { + SettlementPDFDto settlementPDFDto = getSettlementPDFDto(event, masterUser, eventSettlement); + // 정산 관련 타임리프 파일. + String html = templateEngine.process("settlement", getPdfHtmlContext(settlementPDFDto)); + ByteArrayOutputStream outputStream = pdfRender.generatePdfFromHtml(html); + s3PrivateFileUploadService.eventSettlementPdfUpload(event.getId(), outputStream); + } + + private Context getPdfHtmlContext(SettlementPDFDto settlementPDFDto) { + Map result = objectMapper.convertValue(settlementPDFDto, Map.class); + + Context context = new Context(null, result); + context.setVariable("settlementAt", settlementPDFDto.getSettlementAt()); + context.setVariable("now", settlementPDFDto.getNow()); + return context; + } + + private SettlementPDFDto getSettlementPDFDto( + Event event, User masterUser, EventSettlement eventSettlement) { + return SettlementPDFDto.builder() + .eventTitle(event.getEventBasic().getName()) + .hostName(masterUser.getProfile().getName()) + .settlementAt(event.getEndAt().plusDays(6L)) + .dudoongTicketAmount(eventSettlement.getDudoongAmount().toString()) + .pgTicketAmount(eventSettlement.getPaymentAmount().toString()) + .totalAmount(eventSettlement.getTotalSalesAmount().toString()) + // 초기 두둥 자체 수수료 없음. + .dudoongFee(Money.ZERO.toString()) + .pgFee(eventSettlement.getPgFee().toString()) + .totalFee(eventSettlement.getPgFee().toString()) + .totalFeeVat(eventSettlement.getPgFeeVat().toString()) + .totalSettlement(eventSettlement.getTotalAmount().toString()) + .now(LocalDateTime.now()) + .build(); + } +} diff --git a/DuDoong-Batch/src/main/java/band/gosrock/excel/ExcelOrderDto.java b/DuDoong-Batch/src/main/java/band/gosrock/helper/excel/ExcelOrderDto.java similarity index 97% rename from DuDoong-Batch/src/main/java/band/gosrock/excel/ExcelOrderDto.java rename to DuDoong-Batch/src/main/java/band/gosrock/helper/excel/ExcelOrderDto.java index 3ca69177..0826b7f7 100644 --- a/DuDoong-Batch/src/main/java/band/gosrock/excel/ExcelOrderDto.java +++ b/DuDoong-Batch/src/main/java/band/gosrock/helper/excel/ExcelOrderDto.java @@ -1,4 +1,4 @@ -package band.gosrock.excel; +package band.gosrock.helper.excel; import band.gosrock.domain.common.vo.Money; diff --git a/DuDoong-Batch/src/main/java/band/gosrock/helper/excel/ExcelOrderHelper.java b/DuDoong-Batch/src/main/java/band/gosrock/helper/excel/ExcelOrderHelper.java new file mode 100644 index 00000000..238c495b --- /dev/null +++ b/DuDoong-Batch/src/main/java/band/gosrock/helper/excel/ExcelOrderHelper.java @@ -0,0 +1,133 @@ +package band.gosrock.helper.excel; + + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.List; +import java.util.stream.IntStream; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFFont; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.stereotype.Component; + +@Component +public class ExcelOrderHelper { + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + public ByteArrayOutputStream execute(List excelOrders) { + try (Workbook workbook = new XSSFWorkbook()) { + Field[] declaredFields = ExcelOrderDto.class.getDeclaredFields(); + + List fieldNames = Arrays.stream(declaredFields).map(Field::getName).toList(); + Sheet sheet = workbook.createSheet("orderList"); + sheet.setColumnWidth(0, 6000); + sheet.setColumnWidth(1, 6000); + sheet.setColumnWidth(2, 6000); + sheet.setColumnWidth(3, 6000); + sheet.setColumnWidth(4, 6000); + sheet.setColumnWidth(5, 6000); + sheet.setColumnWidth(6, 6000); + sheet.setColumnWidth(7, 6000); + sheet.setColumnWidth(8, 6000); + // create header + Row header = sheet.createRow(0); + CellStyle headerStyle = workbook.createCellStyle(); + headerStyle.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex()); + headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); + XSSFFont font = ((XSSFWorkbook) workbook).createFont(); + font.setFontName("Arial"); + font.setFontHeightInPoints((short) 16); + font.setBold(true); + headerStyle.setFont(font); + + Cell headerCell0 = header.createCell(0); + headerCell0.setCellValue("주문번호"); + headerCell0.setCellStyle(headerStyle); + + Cell headerCell1 = header.createCell(1); + headerCell1.setCellValue("주문 방식"); + headerCell1.setCellStyle(headerStyle); + + Cell headerCell2 = header.createCell(2); + headerCell2.setCellValue("주문 상태"); + headerCell2.setCellStyle(headerStyle); + + Cell headerCell3 = header.createCell(3); + headerCell3.setCellValue("주문 이름"); + headerCell3.setCellStyle(headerStyle); + + Cell headerCell4 = header.createCell(4); + headerCell4.setCellValue("주문자 아이디"); + headerCell4.setCellStyle(headerStyle); + + Cell headerCell5 = header.createCell(5); + headerCell5.setCellValue("총액"); + headerCell5.setCellStyle(headerStyle); + + Cell headerCell6 = header.createCell(6); + headerCell6.setCellValue("티켓 수량"); + headerCell6.setCellStyle(headerStyle); + + Cell headerCell7 = header.createCell(7); + headerCell7.setCellValue("생성 일시"); + headerCell7.setCellStyle(headerStyle); + + Cell headerCell8 = header.createCell(8); + headerCell8.setCellValue("환불 일시"); + headerCell8.setCellStyle(headerStyle); + + CellStyle style = workbook.createCellStyle(); + style.setWrapText(true); + IntStream.range(0, excelOrders.size()) + .forEach( + idx -> { + Row row = sheet.createRow(idx + 1); + ExcelOrderDto excelOrderDto = excelOrders.get(idx); + row.createCell(0).setCellValue(excelOrderDto.getOrderNo()); + + row.createCell(1) + .setCellValue(excelOrderDto.getOrderMethod().getKr()); + + row.createCell(2) + .setCellValue(excelOrderDto.getOrderStatus().getKr()); + + row.createCell(3).setCellValue(excelOrderDto.getOrderName()); + + row.createCell(4).setCellValue(excelOrderDto.getUserId()); + + row.createCell(5) + .setCellValue(excelOrderDto.getAmount().toString()); + + row.createCell(6).setCellValue(excelOrderDto.getQuantity()); + + row.createCell(7) + .setCellValue( + excelOrderDto.getCreatedAt().format(formatter)); + + LocalDateTime refundAt = excelOrderDto.getRefundAt(); + row.createCell(8) + .setCellValue( + refundAt != null + ? refundAt.format(formatter) + : null); + }); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + workbook.write(outputStream); + workbook.close(); + return outputStream; + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/DuDoong-Batch/src/main/java/band/gosrock/slack/SlackEventExpirationSender.java b/DuDoong-Batch/src/main/java/band/gosrock/helper/slack/SlackEventExpirationSender.java similarity index 98% rename from DuDoong-Batch/src/main/java/band/gosrock/slack/SlackEventExpirationSender.java rename to DuDoong-Batch/src/main/java/band/gosrock/helper/slack/SlackEventExpirationSender.java index 67f599c4..7c88d8bf 100644 --- a/DuDoong-Batch/src/main/java/band/gosrock/slack/SlackEventExpirationSender.java +++ b/DuDoong-Batch/src/main/java/band/gosrock/helper/slack/SlackEventExpirationSender.java @@ -1,4 +1,4 @@ -package band.gosrock.slack; +package band.gosrock.helper.slack; import static com.slack.api.model.block.Blocks.divider; import static com.slack.api.model.block.Blocks.section; diff --git a/DuDoong-Batch/src/main/java/band/gosrock/slack/SlackUserNotificationSender.java b/DuDoong-Batch/src/main/java/band/gosrock/helper/slack/SlackUserNotificationSender.java similarity index 98% rename from DuDoong-Batch/src/main/java/band/gosrock/slack/SlackUserNotificationSender.java rename to DuDoong-Batch/src/main/java/band/gosrock/helper/slack/SlackUserNotificationSender.java index 7183d8f1..57fcac41 100644 --- a/DuDoong-Batch/src/main/java/band/gosrock/slack/SlackUserNotificationSender.java +++ b/DuDoong-Batch/src/main/java/band/gosrock/helper/slack/SlackUserNotificationSender.java @@ -1,4 +1,4 @@ -package band.gosrock.slack; +package band.gosrock.helper.slack; import static com.slack.api.model.block.Blocks.divider; import static com.slack.api.model.block.Blocks.section; diff --git a/DuDoong-Batch/src/main/java/band/gosrock/job/EventExpiration.java b/DuDoong-Batch/src/main/java/band/gosrock/job/EventExpiration.java index 592ab92e..0d28d49d 100644 --- a/DuDoong-Batch/src/main/java/band/gosrock/job/EventExpiration.java +++ b/DuDoong-Batch/src/main/java/band/gosrock/job/EventExpiration.java @@ -3,8 +3,8 @@ import band.gosrock.domain.domains.event.domain.Event; import band.gosrock.domain.domains.event.service.EventService; +import band.gosrock.helper.slack.SlackEventExpirationSender; import band.gosrock.parameter.DateTimeJobParameter; -import band.gosrock.slack.SlackEventExpirationSender; import java.time.LocalDateTime; import java.util.List; import lombok.RequiredArgsConstructor; diff --git a/DuDoong-Batch/src/main/java/band/gosrock/job/EventOrdersToExcel.java b/DuDoong-Batch/src/main/java/band/gosrock/job/EventOrdersToExcel.java index 1c145b2b..6a219447 100644 --- a/DuDoong-Batch/src/main/java/band/gosrock/job/EventOrdersToExcel.java +++ b/DuDoong-Batch/src/main/java/band/gosrock/job/EventOrdersToExcel.java @@ -5,15 +5,13 @@ import band.gosrock.domain.domains.event.domain.Event; import band.gosrock.domain.domains.order.adaptor.OrderAdaptor; import band.gosrock.domain.domains.order.domain.Order; -import band.gosrock.excel.ExcelOrderDto; -import band.gosrock.excel.ExcelOrderHelper; +import band.gosrock.helper.excel.ExcelOrderDto; +import band.gosrock.helper.excel.ExcelOrderHelper; import band.gosrock.infrastructure.config.s3.S3PrivateFileService; import band.gosrock.parameter.EventJobParameter; -import java.io.ByteArrayOutputStream; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.poi.ss.usermodel.Workbook; import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; @@ -24,6 +22,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +/** 공연 관련 주문 목록을 엑셀화 하여 프라이빗한 S3에 저장합니다. */ @Slf4j @RequiredArgsConstructor @Configuration @@ -65,34 +64,20 @@ public Step userStatisticStep() { Event event = eventJobParameter.getEvent(); List eventOrders = orderAdaptor.findByEventId(event.getId()); - // 주문 상태가 결제 완료, 승인 완료 , 환불 , 취소 인것만 가져오도록 필터링. - List excelOrders = - eventOrders.stream() - .filter( - order -> - order.getOrderStatus() - .isInEventOrderExcelStatus()) - .map(ExcelOrderDto::from) - .toList(); - - // 엑셀에 만들기 - // 주문 번호 , 주문 방식 , 유저아이디 , 주문 이름 , 매수 , 총 결제금액 , 일시 , 환불일시. - Workbook workbook = excelOrderHelper.execute(excelOrders); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - workbook.write(outputStream); - workbook.close(); - - String fileKey = - s3PrivateFileUploadService.eventOrdersExcelUpload( - event.getId(), outputStream); - // 이벤트 정산용 엑셀 파일 업로드 정보 저장 - // 파일 키 형식 맞출꺼라서 필요없을듯. - // - // eventSettlementDomainService.updateEventOrderListExcelFileKey( - // event.getId(), fileKey); + List excelOrders = getExcelOrders(eventOrders); + s3PrivateFileUploadService.eventOrdersExcelUpload( + event.getId(), excelOrderHelper.execute(excelOrders)); return RepeatStatus.FINISHED; }) .build(); } + + // 주문 상태가 결제 완료, 승인 완료 , 환불 , 취소 인것만 가져오도록 필터링. + private List getExcelOrders(List eventOrders) { + return eventOrders.stream() + .filter(order -> order.getOrderStatus().isInEventOrderExcelStatus()) + .map(ExcelOrderDto::from) + .toList(); + } } diff --git a/DuDoong-Batch/src/main/java/band/gosrock/job/EventSettlementEmailToAdmin.java b/DuDoong-Batch/src/main/java/band/gosrock/job/EventSettlementEmailToAdmin.java index 957f56cb..c2ecf5f4 100644 --- a/DuDoong-Batch/src/main/java/band/gosrock/job/EventSettlementEmailToAdmin.java +++ b/DuDoong-Batch/src/main/java/band/gosrock/job/EventSettlementEmailToAdmin.java @@ -3,14 +3,7 @@ import band.gosrock.domain.domains.event.adaptor.EventAdaptor; import band.gosrock.domain.domains.event.domain.Event; -import band.gosrock.domain.domains.host.adaptor.HostAdaptor; -import band.gosrock.domain.domains.host.domain.Host; -import band.gosrock.domain.domains.user.adaptor.UserAdaptor; -import band.gosrock.domain.domains.user.domain.User; -import band.gosrock.infrastructure.config.s3.S3PrivateFileService; -import band.gosrock.infrastructure.config.ses.AwsSesUtils; -import band.gosrock.infrastructure.config.ses.RawEmailAttachmentDto; -import band.gosrock.infrastructure.config.ses.SendRawEmailDto; +import band.gosrock.helper.SettlementEmailHelper; import band.gosrock.parameter.EventJobParameter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -23,8 +16,6 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.thymeleaf.context.Context; -import org.thymeleaf.spring5.SpringTemplateEngine; @Slf4j @RequiredArgsConstructor @@ -38,14 +29,7 @@ public class EventSettlementEmailToAdmin { private final StepBuilderFactory stepBuilderFactory; private final EventAdaptor eventAdaptor; - private final HostAdaptor hostAdaptor; - private final UserAdaptor userAdaptor; - - private final SpringTemplateEngine templateEngine; - - private final S3PrivateFileService s3PrivateFileUploadService; - - private final AwsSesUtils awsSesUtils; + private final SettlementEmailHelper settlementEmailHelper; @Bean(BEAN_PREFIX + "eventJobParameter") @JobScope @@ -69,42 +53,7 @@ public Step userStatisticStep() { .tasklet( (contribution, chunkContext) -> { Event event = eventJobParameter.getEvent(); - String eventName = event.getEventBasic().getName(); - Long eventId = event.getId(); - Host host = hostAdaptor.findById(event.getHostId()); - User masterUser = userAdaptor.queryUser(host.getMasterUserId()); - byte[] eventSettlementPdf = - s3PrivateFileUploadService.downloadEventSettlementPdf(eventId); - RawEmailAttachmentDto eventSettlementPdfAttachment = - RawEmailAttachmentDto.builder() - .fileName(eventName + "_정산서.pdf") - .fileBytes(eventSettlementPdf) - .type("application/pdf") - .build(); - - byte[] eventOrderListExcel = - s3PrivateFileUploadService.downloadEventOrdersExcel(eventId); - RawEmailAttachmentDto eventOrderListExcelAttachment = - RawEmailAttachmentDto.builder() - .fileName(eventName + "_주문목록.xlsx") - .fileBytes(eventOrderListExcel) - .type( - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") - .build(); - - SendRawEmailDto sendRawEmailDto = - SendRawEmailDto.builder() - .bodyHtml( - templateEngine.process( - "eventSettlement", new Context())) - .recipient("support@dudoong.com") - .subject(eventName + "공연 정산서 어드민 발송 ( 관리자용 )") - .build(); - - sendRawEmailDto.addEmailAttachments(eventSettlementPdfAttachment); - sendRawEmailDto.addEmailAttachments(eventOrderListExcelAttachment); - - awsSesUtils.sendRawEmails(sendRawEmailDto); + settlementEmailHelper.sendToAdmin(event); return RepeatStatus.FINISHED; }) .build(); diff --git a/DuDoong-Batch/src/main/java/band/gosrock/job/EventSettlementEmailToHost.java b/DuDoong-Batch/src/main/java/band/gosrock/job/EventSettlementEmailToHost.java index c9620cee..63ff275b 100644 --- a/DuDoong-Batch/src/main/java/band/gosrock/job/EventSettlementEmailToHost.java +++ b/DuDoong-Batch/src/main/java/band/gosrock/job/EventSettlementEmailToHost.java @@ -7,10 +7,7 @@ import band.gosrock.domain.domains.host.domain.Host; import band.gosrock.domain.domains.user.adaptor.UserAdaptor; import band.gosrock.domain.domains.user.domain.User; -import band.gosrock.infrastructure.config.s3.S3PrivateFileService; -import band.gosrock.infrastructure.config.ses.AwsSesUtils; -import band.gosrock.infrastructure.config.ses.RawEmailAttachmentDto; -import band.gosrock.infrastructure.config.ses.SendRawEmailDto; +import band.gosrock.helper.SettlementEmailHelper; import band.gosrock.parameter.EventJobParameter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -23,8 +20,6 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.thymeleaf.context.Context; -import org.thymeleaf.spring5.SpringTemplateEngine; @Slf4j @RequiredArgsConstructor @@ -37,15 +32,10 @@ public class EventSettlementEmailToHost { private final JobBuilderFactory jobBuilderFactory; private final StepBuilderFactory stepBuilderFactory; private final EventAdaptor eventAdaptor; - private final HostAdaptor hostAdaptor; private final UserAdaptor userAdaptor; - private final SpringTemplateEngine templateEngine; - - private final S3PrivateFileService s3PrivateFileUploadService; - - private final AwsSesUtils awsSesUtils; + private final SettlementEmailHelper settlementEmailHelper; @Bean(BEAN_PREFIX + "eventJobParameter") @JobScope @@ -69,42 +59,11 @@ public Step userStatisticStep() { .tasklet( (contribution, chunkContext) -> { Event event = eventJobParameter.getEvent(); - String eventName = event.getEventBasic().getName(); - Long eventId = event.getId(); Host host = hostAdaptor.findById(event.getHostId()); User masterUser = userAdaptor.queryUser(host.getMasterUserId()); - byte[] eventSettlementPdf = - s3PrivateFileUploadService.downloadEventSettlementPdf(eventId); - RawEmailAttachmentDto eventSettlementPdfAttachment = - RawEmailAttachmentDto.builder() - .fileName(eventName + "_정산서.pdf") - .fileBytes(eventSettlementPdf) - .type("application/pdf") - .build(); - - byte[] eventOrderListExcel = - s3PrivateFileUploadService.downloadEventOrdersExcel(eventId); - RawEmailAttachmentDto eventOrderListExcelAttachment = - RawEmailAttachmentDto.builder() - .fileName(eventName + "_주문목록.xlsx") - .fileBytes(eventOrderListExcel) - .type( - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") - .build(); - - SendRawEmailDto sendRawEmailDto = - SendRawEmailDto.builder() - .bodyHtml( - templateEngine.process( - "eventSettlement", new Context())) - .recipient(masterUser.getProfile().getEmail()) - .subject(eventName + "공연 정산관련 안내") - .build(); - - sendRawEmailDto.addEmailAttachments(eventSettlementPdfAttachment); - sendRawEmailDto.addEmailAttachments(eventOrderListExcelAttachment); + settlementEmailHelper.sendToHost( + event, masterUser.getProfile().getEmail()); - awsSesUtils.sendRawEmails(sendRawEmailDto); return RepeatStatus.FINISHED; }) .build(); diff --git a/DuDoong-Batch/src/main/java/band/gosrock/job/EventSettlementPDF.java b/DuDoong-Batch/src/main/java/band/gosrock/job/EventSettlementPDF.java index 01c9c18c..7798ec51 100644 --- a/DuDoong-Batch/src/main/java/band/gosrock/job/EventSettlementPDF.java +++ b/DuDoong-Batch/src/main/java/band/gosrock/job/EventSettlementPDF.java @@ -1,7 +1,6 @@ package band.gosrock.job; -import band.gosrock.domain.common.vo.Money; import band.gosrock.domain.domains.event.adaptor.EventAdaptor; import band.gosrock.domain.domains.event.domain.Event; import band.gosrock.domain.domains.host.adaptor.HostAdaptor; @@ -10,14 +9,8 @@ import band.gosrock.domain.domains.settlement.domain.EventSettlement; import band.gosrock.domain.domains.user.adaptor.UserAdaptor; import band.gosrock.domain.domains.user.domain.User; -import band.gosrock.dto.SettlementPDFDto; -import band.gosrock.infrastructure.config.pdf.PdfRender; -import band.gosrock.infrastructure.config.s3.S3PrivateFileService; +import band.gosrock.helper.SettlementPdfHelper; import band.gosrock.parameter.EventJobParameter; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.ByteArrayOutputStream; -import java.time.LocalDateTime; -import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.batch.core.Job; @@ -29,8 +22,6 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.thymeleaf.context.Context; -import org.thymeleaf.spring5.SpringTemplateEngine; @Slf4j @RequiredArgsConstructor @@ -49,13 +40,7 @@ public class EventSettlementPDF { private final EventSettlementAdaptor eventSettlementAdaptor; - private final PdfRender pdfRender; - - private final ObjectMapper objectMapper; - - private final SpringTemplateEngine templateEngine; - - private final S3PrivateFileService s3PrivateFileUploadService; + private final SettlementPdfHelper settlementPdfHelper; @Bean(BEAN_PREFIX + "eventJobParameter") @JobScope @@ -84,47 +69,7 @@ public Step userStatisticStep() { User masterUser = userAdaptor.queryUser(host.getMasterUserId()); EventSettlement eventSettlement = eventSettlementAdaptor.findByEventId(eventId); - // 결제 대행사 수수료 - - Money pgFee = eventSettlement.getPgFee(); - Money pgFeeVat = eventSettlement.getPgFeeVat(); - SettlementPDFDto settlementPDFDto = - SettlementPDFDto.builder() - .eventTitle(event.getEventBasic().getName()) - .hostName(masterUser.getProfile().getName()) - .settlementAt(event.getEndAt().plusDays(6L)) - .dudoongTicketAmount( - eventSettlement.getDudoongAmount().toString()) - .pgTicketAmount( - eventSettlement.getPaymentAmount().toString()) - .totalAmount( - eventSettlement - .getTotalSalesAmount() - .toString()) - // 초기 두둥 자체 수수료 없음. - .dudoongFee(Money.ZERO.toString()) - .pgFee(pgFee.toString()) - .totalFee(pgFee.toString()) - .totalFeeVat(pgFeeVat.toString()) - .totalSettlement( - eventSettlement.getTotalAmount().toString()) - .now(LocalDateTime.now()) - .build(); - Map result = objectMapper.convertValue(settlementPDFDto, Map.class); - - Context context = new Context(null, result); - context.setVariable("settlementAt", settlementPDFDto.getSettlementAt()); - context.setVariable("now", settlementPDFDto.getNow()); - // 정산 관련 타임리프 파일. - String html = templateEngine.process("settlement", context); - // html - ByteArrayOutputStream outputStream = - pdfRender.generatePdfFromHtml(html); - - String fileKey = - s3PrivateFileUploadService.eventSettlementPdfUpload( - event.getId(), outputStream); - log.info(fileKey); + settlementPdfHelper.uploadPdfToS3(event, eventSettlement, masterUser); return RepeatStatus.FINISHED; }) .build(); diff --git a/DuDoong-Batch/src/main/java/band/gosrock/job/EventSummarySettlement.java b/DuDoong-Batch/src/main/java/band/gosrock/job/EventSummarySettlement.java index 0b2e5b6c..b73aac1f 100644 --- a/DuDoong-Batch/src/main/java/band/gosrock/job/EventSummarySettlement.java +++ b/DuDoong-Batch/src/main/java/band/gosrock/job/EventSummarySettlement.java @@ -1,17 +1,12 @@ package band.gosrock.job; -import band.gosrock.domain.common.vo.Money; import band.gosrock.domain.domains.event.adaptor.EventAdaptor; import band.gosrock.domain.domains.event.domain.Event; import band.gosrock.domain.domains.order.adaptor.OrderAdaptor; import band.gosrock.domain.domains.order.domain.Order; -import band.gosrock.domain.domains.order.domain.OrderStatus; import band.gosrock.domain.domains.settlement.adaptor.EventSettlementAdaptor; -import band.gosrock.domain.domains.settlement.adaptor.TransactionSettlementAdaptor; -import band.gosrock.domain.domains.settlement.domain.EventSettlement; -import band.gosrock.domain.domains.settlement.domain.EventSettlementStatus; -import band.gosrock.domain.domains.settlement.domain.TransactionSettlement; +import band.gosrock.domain.domains.settlement.service.EventSettlementDomainService; import band.gosrock.parameter.EventJobParameter; import java.util.List; import lombok.RequiredArgsConstructor; @@ -26,6 +21,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +/** 이벤트 정산 수수료 , 내역등을 요약해서 저장합니다. */ @Slf4j @RequiredArgsConstructor @Configuration @@ -44,7 +40,7 @@ public class EventSummarySettlement { private final EventSettlementAdaptor eventSettlementAdaptor; - private final TransactionSettlementAdaptor transactionSettlementAdaptor; + private final EventSettlementDomainService eventSettlementDomainService; @Bean(BEAN_PREFIX + "eventJobParameter") @JobScope @@ -67,84 +63,8 @@ public Step eventSettlement() { Event event = eventJobParameter.getEvent(); Long eventId = event.getId(); eventSettlementAdaptor.deleteByEventId(eventId); - List orders = orderAdaptor.findByEventId(eventId); - Money totalSalesAmount = - orders.stream() - .filter(order -> order.getOrderStatus().isCanWithDraw()) - .map(Order::getTotalPaymentPrice) - .reduce(Money.ZERO, Money::plus); - - // 오더중에 승인 주문중 두둥 총 - Money dudoongTicketSalesAmount = - orders.stream() - .filter( - order -> - order.getOrderStatus() - == OrderStatus.APPROVED) - .map(Order::getTotalPaymentPrice) - .reduce(Money.ZERO, Money::plus); - // 오더중에 토스 페이먼츠 로결제를 진행한 목록을 추출. - Money paymentOrderTotalSales = - orders.stream() - .filter( - order -> - order.getOrderStatus() - == OrderStatus.CONFIRM) - .map(Order::getTotalPaymentPrice) - .reduce(Money.ZERO, Money::plus); - log.info("주문 결제 금액" + paymentOrderTotalSales); - // 오더중에 토스 페이먼츠로 결제를 진행한 목록중 쿠폰 할인 금액. - Money paymentOrderDiscountAmount = - orders.stream() - .filter( - order -> - order.getOrderStatus() - == OrderStatus.CONFIRM) - .map(Order::getTotalDiscountPrice) - .reduce(Money.ZERO, Money::plus); - - // 이벤트 트랜잭션 저장 목록 조회 - List transactionSettlements = - transactionSettlementAdaptor.findByEventId(eventId); - // 결제 된 금액 - - Money paymentAmount = - transactionSettlements.stream() - .map(TransactionSettlement::getPaymentAmount) - .reduce(Money.ZERO, Money::plus); - // TODO : 정산금액 밸리데이션 - // paymentOrderTotalSales == paymentAmount 이어야함! - - // 결제 정산 받을금액 - Money settlementAmount = - transactionSettlements.stream() - .map(TransactionSettlement::getSettlementAmount) - .reduce(Money.ZERO, Money::plus); - // PG 수수료 - Money pgFee = paymentAmount.minus(settlementAmount); - - // 중개 수수료 계산 공식? 그냥 정액으로.. - // 소숫점 없애기 - Money pgFeeVat = Money.wons(pgFee.times(0.1).longValue()); - Money totalAmount = paymentAmount.minus(pgFee).minus(pgFeeVat); - EventSettlement eventSettlement = - EventSettlement.builder() - .eventId(eventId) - .totalSalesAmount(totalSalesAmount) - .dudoongAmount(dudoongTicketSalesAmount) - .paymentAmount(paymentAmount) - .couponAmount(paymentOrderDiscountAmount) - .dudoongFee(Money.ZERO) - .pgFee(pgFee) - .pgFeeVat(pgFeeVat) - // 최종 정산금액 - .totalAmount(totalAmount) - .eventSettlementStatus(EventSettlementStatus.CALCULATED) - .build(); - - // 최종 정산 금액 계산. - eventSettlementAdaptor.save(eventSettlement); + eventSettlementDomainService.generateEventSettlement(eventId, orders); return RepeatStatus.FINISHED; }) .build(); diff --git a/DuDoong-Batch/src/main/java/band/gosrock/job/EventTransactionSettlement.java b/DuDoong-Batch/src/main/java/band/gosrock/job/EventTransactionSettlement.java index c845b68f..67dad868 100644 --- a/DuDoong-Batch/src/main/java/band/gosrock/job/EventTransactionSettlement.java +++ b/DuDoong-Batch/src/main/java/band/gosrock/job/EventTransactionSettlement.java @@ -24,6 +24,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +/** 토스페이먼츠 거래 내역들을 이벤트별로 취합 후 저장합니다. */ @Slf4j @RequiredArgsConstructor @Configuration @@ -64,46 +65,42 @@ public Step eventSettlement() { (contribution, chunkContext) -> { Event event = eventJobParameter.getEvent(); Long eventId = event.getId(); - // 멱등성 유지하기위해 - // 저장된 정산 목록에서 지움 + // 멱등성 유지하기위해 저장된 정산 목록에서 지움 ( 실제로 다시만들일은 없을듯...? ) transactionSettlementAdaptor.deleteByEventId(eventId); - List orders = orderAdaptor.findByEventId(eventId); - // 오더중에 토스 페이먼츠 로결제를 진행한 목록을 추출. - // 해당이벤트의 모든 주문 내역을 불러와야함. - // 토스에 환불진행되는 입금 건도 있음 - List paymentOrderUuids = - orders.stream() - .filter(order -> order.isPaid()) - .map(order -> order.getPgPaymentInfo().getPaymentKey()) - .toList(); - - // 시작 날짜. ( 이벤트 생성 시간 ) - LocalDate startAt = event.getCreatedAt().toLocalDate(); - // 끝나는 날짜. - LocalDate endAt = event.getEndAt().toLocalDate(); - // 데이터가 실제로 들어가야만 있음.. 테스트 코드로 돌려야함. - List settlements = - settlementClient.execute(startAt, endAt, "soldDate", 1, 10000); - - // 이벤트와 관련된 정산 객체 집합. - List transactionSettlements = - settlements.stream() - .filter( - settlementResponse -> - paymentOrderUuids.contains( - settlementResponse - .getPaymentKey())) - .map( - settlementResponse -> - TransactionSettlement.of( - eventId, settlementResponse)) - .toList(); - // 정산 정보 저장. - transactionSettlementAdaptor.saveAll(transactionSettlements); + // 정산 정보 저장 + transactionSettlementAdaptor.saveAll(getTransactionSettlements(event)); return RepeatStatus.FINISHED; }) .build(); } + + private List getPaymentOrderUUIDs(Long eventId) { + List orders = orderAdaptor.findByEventId(eventId); + return orders.stream() + .filter(Order::isPaid) + .map(order -> order.getPgPaymentInfo().getPaymentKey()) + .toList(); + } + + private List getTransactionSettlements(Event event) { + Long eventId = event.getId(); + List paymentOrderUuids = getPaymentOrderUUIDs(eventId); + List settlements = getTossPaymentsSettlementData(event); + + return settlements.stream() + .filter( + settlementResponse -> + paymentOrderUuids.contains(settlementResponse.getPaymentKey())) + .map(settlementResponse -> TransactionSettlement.of(eventId, settlementResponse)) + .toList(); + } + + // 토스페이먼츠에서 시작일 종료일 매출일 기준 정산액을 조회합니다. + private List getTossPaymentsSettlementData(Event event) { + LocalDate startAt = event.getCreatedAt().toLocalDate(); + LocalDate endAt = event.getEndAt().toLocalDate(); + return settlementClient.execute(startAt, endAt, "soldDate", 1, 10000); + } } diff --git a/DuDoong-Batch/src/main/java/band/gosrock/job/SlackUserStatistic.java b/DuDoong-Batch/src/main/java/band/gosrock/job/SlackUserStatistic.java index 2d754e20..1918f21f 100644 --- a/DuDoong-Batch/src/main/java/band/gosrock/job/SlackUserStatistic.java +++ b/DuDoong-Batch/src/main/java/band/gosrock/job/SlackUserStatistic.java @@ -2,8 +2,8 @@ import band.gosrock.domain.domains.user.adaptor.UserAdaptor; +import band.gosrock.helper.slack.SlackUserNotificationSender; import band.gosrock.parameter.DateJobParameter; -import band.gosrock.slack.SlackUserNotificationSender; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/event/domain/Event.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/event/domain/Event.java index 9935516b..1e413cb9 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/event/domain/Event.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/event/domain/Event.java @@ -178,4 +178,8 @@ public void deleteSoft() { this.status = DELETED; Events.raise(EventDeletionEvent.of(this)); } + + public String getEventName() { + return eventBasic.getName(); + } } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/domain/Order.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/domain/Order.java index 3604e3c0..dfa6e18a 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/domain/Order.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/domain/Order.java @@ -239,6 +239,14 @@ public void cancel(OrderValidator orderValidator) { Events.raise(WithDrawOrderEvent.from(this)); } + /** 관리자가 승인 대기 중인 주문을 거절합니다. */ + public void refuse(OrderValidator orderValidator) { + orderValidator.validCanRefuse(this); + this.orderStatus = OrderStatus.CANCELED; + this.withDrawAt = LocalDateTime.now(); + Events.raise(WithDrawOrderEvent.from(this)); + } + /** 사용자가 주문을 환불 시킵니다. */ public void refund(Long currentUserId, OrderValidator orderValidator) { orderValidator.validOwner(this, currentUserId); diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/domain/validator/OrderValidator.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/domain/validator/OrderValidator.java index 3bb54f8e..25b62c37 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/domain/validator/OrderValidator.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/domain/validator/OrderValidator.java @@ -121,6 +121,13 @@ public void validCanCancel(Order order) { validCanWithDraw(order); } + /** 거절할 수 있는 승인 대기중인 주문인지 검증합니다. */ + public void validCanRefuse(Order order) { + validAvailableRefundDate(order); + validStatusCanRefuse(getOrderStatus(order)); + validCanWithDraw(order); + } + /** 환불 할 수 있는 주문인지 검증합니다. */ public void validCanRefund(Order order) { validAvailableRefundDate(order); @@ -280,6 +287,11 @@ public Boolean isStatusCanWithDraw(OrderStatus orderStatus) { || Objects.equals(orderStatus, OrderStatus.APPROVED); } + /** 주문상태가 철회가능한 상태인지를 반환합니다. */ + public Boolean isStatusCanRefuse(OrderStatus orderStatus) { + return Objects.equals(orderStatus, OrderStatus.PENDING_APPROVE); + } + /** 주문 상태가 취소가능한 상태인지 검증합니다. */ public void validStatusCanCancel(OrderStatus orderStatus) { if (!isStatusCanWithDraw(orderStatus)) { @@ -293,6 +305,12 @@ public void validStatusCanRefund(OrderStatus orderStatus) { throw CanNotRefundOrderException.EXCEPTION; } } + /** 주문 상태가 관리자 취소가능한 상태인지 검증합니다. */ + public void validStatusCanRefuse(OrderStatus orderStatus) { + if (!isStatusCanRefuse(orderStatus)) { + throw CanNotCancelOrderException.EXCEPTION; + } + } /** 주문 상태가 결제방식의 승인 가능한 상태인지 검증합니다. */ public void validStatusCanPaymentConfirm(OrderStatus orderStatus) { diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/exception/CanNotRefuseOrderException.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/exception/CanNotRefuseOrderException.java new file mode 100644 index 00000000..2c1cd43e --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/exception/CanNotRefuseOrderException.java @@ -0,0 +1,13 @@ +package band.gosrock.domain.domains.order.exception; + + +import band.gosrock.common.exception.DuDoongCodeException; + +public class CanNotRefuseOrderException extends DuDoongCodeException { + + public static final DuDoongCodeException EXCEPTION = new CanNotRefuseOrderException(); + + private CanNotRefuseOrderException() { + super(OrderErrorCode.ORDER_CANNOT_REFUSE); + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/exception/OrderErrorCode.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/exception/OrderErrorCode.java index e0cff47d..6880d9cc 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/exception/OrderErrorCode.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/exception/OrderErrorCode.java @@ -39,7 +39,8 @@ public enum OrderErrorCode implements BaseErrorCode { APPROVE_WAITING_PURCHASE_LIMIT( BAD_REQUEST, "Order_400_15", - "승인 대기중인 주문으로 인해 티켓 최대 구매 가능 횟수를 넘겼습니다." + "이미 신청한 주문이 승인 될 때까지 기다려주세요."); + "승인 대기중인 주문으로 인해 티켓 최대 구매 가능 횟수를 넘겼습니다." + "이미 신청한 주문이 승인 될 때까지 기다려주세요."), + ORDER_CANNOT_REFUSE(BAD_REQUEST, "Order_400_16", "승인 대기중인 주문을 거절할 수 없는 상태입니다."); private Integer status; private String code; diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/service/WithdrawOrderService.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/service/WithdrawOrderService.java index c7aac782..44994cd8 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/service/WithdrawOrderService.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/service/WithdrawOrderService.java @@ -33,4 +33,11 @@ public String refundOrder(String orderUuid, Long userId) { order.refund(userId, orderValidator); return orderUuid; } + + @RedissonLock(LockName = "주문", identifier = "orderUuid") + public String refuseOrder(String orderUuid) { + Order order = orderAdaptor.findByOrderUuid(orderUuid); + order.refuse(orderValidator); + return orderUuid; + } } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/settlement/service/EventSettlementDomainService.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/settlement/service/EventSettlementDomainService.java index 2cec804d..eb821593 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/settlement/service/EventSettlementDomainService.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/settlement/service/EventSettlementDomainService.java @@ -2,8 +2,15 @@ import band.gosrock.common.annotation.DomainService; +import band.gosrock.domain.common.vo.Money; +import band.gosrock.domain.domains.order.domain.Order; +import band.gosrock.domain.domains.order.domain.OrderStatus; import band.gosrock.domain.domains.settlement.adaptor.EventSettlementAdaptor; +import band.gosrock.domain.domains.settlement.adaptor.TransactionSettlementAdaptor; import band.gosrock.domain.domains.settlement.domain.EventSettlement; +import band.gosrock.domain.domains.settlement.domain.EventSettlementStatus; +import band.gosrock.domain.domains.settlement.domain.TransactionSettlement; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; @@ -12,10 +19,87 @@ public class EventSettlementDomainService { private final EventSettlementAdaptor eventSettlementAdaptor; + private final TransactionSettlementAdaptor transactionSettlementAdaptor; @Transactional - public void updateEventOrderListExcelFileKey(Long eventId, String key) { - EventSettlement eventSettlement = eventSettlementAdaptor.upsertByEventId(eventId); - eventSettlement.updateEventOrderListExcelKey(key); + public void generateEventSettlement(Long eventId, List orders) { + List transactionSettlements = + transactionSettlementAdaptor.findByEventId(eventId); + Money totalPaymentAmount = getTotalPaymentAmount(transactionSettlements); + Money pgFee = getPgFee(transactionSettlements, totalPaymentAmount); + Money pgFeeVat = getPgFeeVat(pgFee); + + EventSettlement eventSettlement = + EventSettlement.builder() + .eventId(eventId) + .totalSalesAmount(getTotalSalesAmount(orders)) + .dudoongAmount(getDudoongTicketSalesAmount(orders)) + .paymentAmount(totalPaymentAmount) + .couponAmount(getPaymentOrderDiscountAmount(orders)) + .dudoongFee(Money.ZERO) + .pgFee(pgFee) + .pgFeeVat(pgFeeVat) + .totalAmount(getTotalSettlementAmount(totalPaymentAmount, pgFee, pgFeeVat)) + .eventSettlementStatus(EventSettlementStatus.CALCULATED) + .build(); + + // 최종 정산 금액 계산. + eventSettlementAdaptor.save(eventSettlement); + } + /** 토스페이먼츠 수수료 */ + private Money getPgFee( + List transactionSettlements, Money paymentAmount) { + return paymentAmount.minus(getSettlementAmount(transactionSettlements)); + } + + /** 최종 정산금액 ( 판매대금 - 토스페이먼츠 수수료 - 토스페이먼츠 수수료 vat ) */ + private static Money getTotalSettlementAmount( + Money paymentAmount, Money pgFee, Money pgFeeVat) { + return paymentAmount.minus(pgFee).minus(pgFeeVat); + } + /** 토스페이먼츠 수수료 vat */ + private Money getPgFeeVat(Money pgFee) { + return Money.wons(pgFee.times(0.1).longValue()); + } + + private Money getPaymentOrderDiscountAmount(List orders) { + return orders.stream() + .filter(order -> order.getOrderStatus() == OrderStatus.CONFIRM) + .map(Order::getTotalDiscountPrice) + .reduce(Money.ZERO, Money::plus); + } + + private Money getPaymentOrderTotalSales(List orders) { + return orders.stream() + .filter(order -> order.getOrderStatus() == OrderStatus.CONFIRM) + .map(Order::getTotalPaymentPrice) + .reduce(Money.ZERO, Money::plus); + } + /** 주문목록 두둥티켓 판매 대금 */ + private Money getDudoongTicketSalesAmount(List orders) { + return orders.stream() + .filter(order -> order.getOrderStatus() == OrderStatus.APPROVED) + .map(Order::getTotalPaymentPrice) + .reduce(Money.ZERO, Money::plus); + } + /** 주문 액 기준 총 판매 대금 ( 두둥티켓 , 결제티켓 ) */ + private Money getTotalSalesAmount(List orders) { + return orders.stream() + .filter(order -> order.getOrderStatus().isCanWithDraw()) + .map(Order::getTotalPaymentPrice) + .reduce(Money.ZERO, Money::plus); + } + + /** 토스페이먼츠에서 정산해주는 금액 */ + private Money getSettlementAmount(List transactionSettlements) { + return transactionSettlements.stream() + .map(TransactionSettlement::getSettlementAmount) + .reduce(Money.ZERO, Money::plus); + } + /** 토스페이먼츠의 총 매출액 */ + private Money getTotalPaymentAmount(List transactionSettlements) { + return transactionSettlements.stream() + .map(TransactionSettlement::getPaymentAmount) + .reduce(Money.ZERO, Money::plus); } } diff --git a/DuDoong-Infrastructure/src/main/java/band/gosrock/infrastructure/config/s3/S3PrivateFileService.java b/DuDoong-Infrastructure/src/main/java/band/gosrock/infrastructure/config/s3/S3PrivateFileService.java index 0c6ce673..e505b853 100644 --- a/DuDoong-Infrastructure/src/main/java/band/gosrock/infrastructure/config/s3/S3PrivateFileService.java +++ b/DuDoong-Infrastructure/src/main/java/band/gosrock/infrastructure/config/s3/S3PrivateFileService.java @@ -72,16 +72,24 @@ private ObjectMetadata getPdfObjectMetadata(int contentLength) { return objectMetadata; } - public byte[] downloadEventSettlementPdf(Long eventId) throws IOException { + public byte[] downloadEventSettlementPdf(Long eventId) { S3Object object = amazonS3.getObject(bucket, getEventSettlementPdfKey(eventId)); S3ObjectInputStream finalObject = object.getObjectContent(); - return finalObject.readAllBytes(); + try { + return finalObject.readAllBytes(); + } catch (IOException e) { + throw new RuntimeException(e); + } } - public byte[] downloadEventOrdersExcel(Long eventId) throws IOException { + public byte[] downloadEventOrdersExcel(Long eventId) { S3Object object = amazonS3.getObject(bucket, eventOrdersExcelGetKey(eventId)); S3ObjectInputStream finalObject = object.getObjectContent(); - return finalObject.readAllBytes(); + try { + return finalObject.readAllBytes(); + } catch (IOException e) { + throw new RuntimeException(e); + } } } diff --git a/DuDoong-Infrastructure/src/main/java/band/gosrock/infrastructure/config/ses/AwsSesUtils.java b/DuDoong-Infrastructure/src/main/java/band/gosrock/infrastructure/config/ses/AwsSesUtils.java index bf882a70..9f5c117e 100644 --- a/DuDoong-Infrastructure/src/main/java/band/gosrock/infrastructure/config/ses/AwsSesUtils.java +++ b/DuDoong-Infrastructure/src/main/java/band/gosrock/infrastructure/config/ses/AwsSesUtils.java @@ -11,7 +11,6 @@ import javax.activation.DataSource; import javax.mail.MessagingException; import javax.mail.Session; -import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; @@ -70,8 +69,7 @@ private Message newMessage(String subject, String html) { } // The HTML body of the email. - public void sendRawEmails(SendRawEmailDto sendRawEmailDto) - throws AddressException, MessagingException, IOException { + public void sendRawEmails(SendRawEmailDto sendRawEmailDto) throws MessagingException { Session session = Session.getDefaultInstance(new Properties()); // Create a new MimeMessage object. diff --git a/README.md b/README.md index 212f469e..93c875f8 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ - [찬진 : spring feign client wiremock test](https://devnm.tistory.com/34) - [찬진 : spring oauth Open ID Connect with kakao](https://devnm.tistory.com/35) - [찬진 : 멀티모듈 jacoco , sonarqube (cloud) 세팅](https://devnm.tistory.com/36) +- [찬진 : spring redisson 분산락 Aop 적용기](https://devnm.tistory.com/37) - [찬진 : 도커 로그 ec2환경에서 클라우드 와치로 전송하기](https://devnm.tistory.com/8) - [경민 : Custom Enum Validator 구현하기](https://gengminy.tistory.com/47) - [경민 : Reflection 을 이용하여 Enum Validator 개선하기](https://gengminy.tistory.com/48)