Skip to content
Open
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
Expand Up @@ -10,12 +10,8 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

@Tag(name = "결재 - Command", description = "결재 관련 API")
@RequestMapping("/approvals")
Expand All @@ -25,11 +21,10 @@ public class ApprovalCommandController {
private final ApprovalCommandService approvalCommandService;

@Operation(summary = "결재 상신")
@PostMapping(consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
@PostMapping
public ResponseEntity<ApprovalResponseDTO> submitForApproval(@CurrentUser final Employee employee,
@Valid @RequestPart(name = "requestDTO") final ApprovalCreateRequestDTO requestDTO,
@RequestPart(name = "files", required = false) final List<MultipartFile> files) {
return ResponseEntity.ok(approvalCommandService.submitForApproval(employee, requestDTO, files));
@Valid @RequestBody final ApprovalCreateRequestDTO requestDTO) {
return ResponseEntity.ok(approvalCommandService.submitForApproval(employee, requestDTO));
}

@Operation(summary = "결재 승인")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.werp.sero.approval.command.application.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class ApprovalAttachmentRequestDTO {
@Schema(description = "파일명")
private String originalFileName;

@Schema(description = "s3 url")
private String s3Url;
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ public class ApprovalCreateRequestDTO {
@NotNull(message = "1개 이상의 결재선이 필요합니다.")
@Valid
List<ApprovalLineRequestDTO> approvalLines;

List<ApprovalAttachmentRequestDTO> approvalAttachments;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;

@Builder
@Getter
@AllArgsConstructor
Expand All @@ -20,75 +18,10 @@ public class ApprovalResponseDTO {
@Schema(description = "결재 코드")
private String approvalCode;

@Schema(description = "제목")
private String title;

@Schema(description = "내용")
private String content;

@Schema(description = "문서 번호")
private String refCode;

@Schema(description = "기안일시")
private String draftedAt;

@Schema(description = "결재 상태")
private String status;

@Schema(description = "결재 완료일시")
private String completedAt;

@Schema(description = "기안자 ID(PK)")
private int drafterId;

@Schema(description = "기안자 이름")
private String drafterName;

@Schema(description = "기안자 부서")
private String drafterDepartment;

@Schema(description = "기안자 직책")
private String drafterPosition;

@Schema(description = "기안자 직급")
private String drafterRank;

@Schema(description = "결재 첨부파일 목록")
private List<ApprovalAttachmentResponseDTO> approvalAttachments;

@Schema(description = "결재선 목록 (결재/협조)")
private List<ApprovalLineResponseDTO> approvalLines;

@Schema(description = "참조자 목록")
private List<ApprovalLineResponseDTO> referenceLines;

@Schema(description = "수신자 목록")
private List<ApprovalLineResponseDTO> recipientLines;

public static ApprovalResponseDTO of(final Approval approval,
final List<ApprovalAttachmentResponseDTO> approvalAttachments,
final List<ApprovalLineResponseDTO> approvalLines,
final List<ApprovalLineResponseDTO> referenceLines,
final List<ApprovalLineResponseDTO> recipientLines) {
public static ApprovalResponseDTO of(final Approval approval) {
return ApprovalResponseDTO.builder()
.approvalId(approval.getId())
.approvalCode(approval.getApprovalCode())
.title(approval.getTitle())
.content(approval.getContent())
.refCode(approval.getRefCode())
.draftedAt(approval.getDraftedAt())
.status(approval.getStatus())
.completedAt(approval.getCompletedAt())
.drafterId(approval.getEmployee().getId())
.drafterName(approval.getEmployee().getName())
.drafterDepartment((approval.getEmployee().getDepartment() != null) ?
approval.getEmployee().getDepartment().getDeptName() : null)
.drafterPosition(approval.getEmployee().getPositionCode())
.drafterRank(approval.getEmployee().getRankCode())
.approvalAttachments(approvalAttachments)
.approvalLines(approvalLines)
.referenceLines(referenceLines)
.recipientLines(recipientLines)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,9 @@
import com.werp.sero.approval.command.application.dto.ApprovalDecisionRequestDTO;
import com.werp.sero.approval.command.application.dto.ApprovalResponseDTO;
import com.werp.sero.employee.command.domain.aggregate.Employee;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

public interface ApprovalCommandService {
ApprovalResponseDTO submitForApproval(final Employee employee, final ApprovalCreateRequestDTO requestDTO,
final List<MultipartFile> files);
ApprovalResponseDTO submitForApproval(final Employee employee, final ApprovalCreateRequestDTO requestDTO);

void approve(final Employee employee, final int approvalId, final ApprovalDecisionRequestDTO requestDTO);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.*;
import java.util.stream.Collectors;
Expand All @@ -49,12 +48,10 @@ public class ApprovalCommandServiceImpl implements ApprovalCommandService {
private final S3Uploader s3Uploader;
private final ApplicationEventPublisher applicationEventPublisher;
private final DocumentSequenceCommandService documentSequenceCommandService;
private final ApplicationEventPublisher eventPublisher;

@Transactional
@Override
public ApprovalResponseDTO submitForApproval(final Employee employee, final ApprovalCreateRequestDTO requestDTO,
final List<MultipartFile> files) {
public ApprovalResponseDTO submitForApproval(final Employee employee, final ApprovalCreateRequestDTO requestDTO) {
validateDuplicateApproval(requestDTO.getRefCode());

validateApprovalLines(requestDTO.getApprovalLines());
Expand All @@ -65,39 +62,20 @@ public ApprovalResponseDTO submitForApproval(final Employee employee, final Appr

final Approval approval = saveApproval(employee, approvalCode, requestDTO);

List<ApprovalAttachmentResponseDTO> approvalAttachmentResponseDTOs = new ArrayList<>();

if (files != null && !files.isEmpty()) {
approvalAttachmentResponseDTOs = saveApprovalAttachments(approval, files).stream()
.map(ApprovalAttachmentResponseDTO::of)
.collect(Collectors.toList());
if (requestDTO.getApprovalAttachments() != null && !requestDTO.getApprovalAttachments().isEmpty()) {
saveApprovalAttachments(approval, requestDTO.getApprovalAttachments());
}

final List<ApprovalLine> approvalLines = saveApprovalLines(approval, requestDTO.getApprovalLines());

final List<ApprovalLineResponseDTO> approvalLineResponseDTOs = approvalLines.stream()
.filter(approvalLine ->
approvalLine.getLineType().equals(APPROVAL_TYPE_APPROVAL) ||
approvalLine.getLineType().equals(APPROVAL_TYPE_REVIEWER))
.sorted(Comparator.comparingInt(ApprovalLine::getSequence))
.map(ApprovalLineResponseDTO::of)
.collect(Collectors.toList());

final List<ApprovalLineResponseDTO> refLines = approvalLines.stream()
.filter(approvalLine -> approvalLine.getLineType().equals(APPROVAL_TYPE_REFERENCE))
.map(ApprovalLineResponseDTO::of)
.collect(Collectors.toList());

final List<ApprovalLineResponseDTO> rcptLines = approvalLines.stream()
.filter(approvalLine -> approvalLine.getLineType().equals(APPROVAL_TYPE_RECIPIENT))
.map(ApprovalLineResponseDTO::of)
.collect(Collectors.toList());
final ApprovalLine firstApprovalLine =
saveApprovalLinesAndGetFirstApprover(approval, requestDTO.getApprovalLines()).stream()
.filter(approvalLine -> "ALS_RVW".equals(approvalLine.getStatus()))
.findFirst().get();

updateRefCode(requestDTO.getApprovalTargetType(), approvalCode, ref);
sendApprovalNotification(approval, ApprovalNotificationType.REQUEST,
approvalLineResponseDTOs.get(0).getApproverId());

return ApprovalResponseDTO.of(approval, approvalAttachmentResponseDTOs, approvalLineResponseDTOs, refLines, rcptLines);
sendApprovalNotification(approval, ApprovalNotificationType.REQUEST, firstApprovalLine.getEmployee().getId());

return ApprovalResponseDTO.of(approval);
}

@Transactional
Expand Down Expand Up @@ -195,8 +173,8 @@ private void updateRefDocumentStatus(final String approvalStatus, String documen

so.updateApprovalInfo(so.getApprovalCode(), (isRejected ? "ORD_APPR_RJCT" : "ORD_APPR_DONE"));

if(!isRejected){
eventPublisher.publishEvent(NotificationEvent.forClient(
if (!isRejected) {
applicationEventPublisher.publishEvent(NotificationEvent.forClient(
NotificationType.ORDER,
"주문 상태 변경",
"주문번호 " + so.getSoCode() + "의 상태가 진행중으로 변경되었습니다.",
Expand Down Expand Up @@ -313,19 +291,20 @@ private int calculateTotalApprovalLineCount(final List<ApprovalLineRequestDTO> r
return totalLine;
}

private List<ApprovalAttachment> saveApprovalAttachments(final Approval approval, final List<MultipartFile> files) {
final List<ApprovalAttachment> approvalAttachments = files.stream()
.map(file -> {
final String s3Url = s3Uploader.upload("sero/documents/", file);
private List<ApprovalAttachment> saveApprovalAttachments(final Approval approval,
final List<ApprovalAttachmentRequestDTO> requestDTOs) {
final List<ApprovalAttachment> approvalAttachments = requestDTOs.stream()
.map(requestDTO -> {
final String s3Url = s3Uploader.copy(requestDTO.getS3Url(), "sero/documents/");

return new ApprovalAttachment(file.getOriginalFilename(), s3Url, approval);
return new ApprovalAttachment(requestDTO.getOriginalFileName(), s3Url, approval);
})
.collect(Collectors.toList());

return approvalAttachmentRepository.saveAll(approvalAttachments);
}

private List<ApprovalLine> saveApprovalLines(final Approval approval, final List<ApprovalLineRequestDTO> requestDTOs) {
private List<ApprovalLine> saveApprovalLinesAndGetFirstApprover(final Approval approval, final List<ApprovalLineRequestDTO> requestDTOs) {
final List<Employee> employees = employeeRepository.findByIdIn(requestDTOs.stream()
.map(ApprovalLineRequestDTO::getApproverId)
.collect(Collectors.toList()));
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/werp/sero/common/error/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ public enum ErrorCode {
PDF_GENERATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "FILE004", "PDF 생성에 실패했습니다."),
S3_URL_INVALID(HttpStatus.BAD_REQUEST, "FILE005", "유효하지 않은 S3 URL입니다."),
S3_DELETE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "FILE006", "S3 파일 삭제에 실패했습니다."),
S3_COPY_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "FILE007", "S3 파일 복사에 실패했습니다."),

/* PRODUCTION PLAN */
PP_ALREADY_EXISTS(HttpStatus.CONFLICT, "PRODUCTION101", "이미 해당 생산요청 품목에 대한 생산계획이 존재합니다."),
Expand Down
57 changes: 53 additions & 4 deletions src/main/java/com/werp/sero/common/file/S3Uploader.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.werp.sero.common.error.ErrorCode;
import com.werp.sero.common.error.exception.BusinessException;
import com.werp.sero.common.error.exception.SystemException;
import com.werp.sero.file.dto.PresignedResponseDTO;
import io.awspring.cloud.s3.S3Exception;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -11,18 +12,24 @@
import org.springframework.web.multipart.MultipartFile;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.CopyObjectRequest;
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.PresignedPutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.UUID;

@RequiredArgsConstructor
@Component
public class S3Uploader {
private final S3Client s3Client;
private final S3Presigner s3Presigner;

@Value("${spring.cloud.aws.s3.bucket}")
private String bucket;
Expand All @@ -32,7 +39,7 @@ public class S3Uploader {

public String upload(final String objectPath, final MultipartFile file) {
try {
final String objectKey = generateKey(objectPath, file);
final String objectKey = generateKey(objectPath, file.getOriginalFilename());

final PutObjectRequest putRequest = PutObjectRequest.builder()
.bucket(bucket)
Expand All @@ -48,7 +55,7 @@ public String upload(final String objectPath, final MultipartFile file) {
}
}

public void delete( final String s3Url) {
public void delete(final String s3Url) {
try {
final String objectKey = extractKey(s3Url);

Expand All @@ -63,6 +70,48 @@ public void delete( final String s3Url) {
}
}

public String copy(final String s3Url, final String targetPath) {
final String objectKey = extractKey(s3Url);

final String destinationKey = targetPath + objectKey.substring(objectKey.lastIndexOf("/") + 1);

final CopyObjectRequest copyRequest = CopyObjectRequest.builder()
.sourceBucket(bucket)
.sourceKey(objectKey)
.destinationBucket(bucket)
.destinationKey(destinationKey)
.build();
try {
s3Client.copyObject(copyRequest);

return generateS3Url(destinationKey);
} catch (S3Exception e) {
throw new SystemException(ErrorCode.S3_COPY_FAILED);
}
}

public PresignedResponseDTO createPresignedPutUrl(final String objectPath, final String originalFileName,
final String contentType) {
final String key = generateKey(objectPath, originalFileName);

final PutObjectRequest objectRequest = PutObjectRequest.builder()
.bucket(bucket)
.contentType(contentType)
.key(key)
.build();

final PutObjectPresignRequest presignRequest = PutObjectPresignRequest.builder()
.signatureDuration(Duration.ofMinutes(10)) // The URL will expire in 10 minutes.
.putObjectRequest(objectRequest)
.build();

final PresignedPutObjectRequest presignedRequest = s3Presigner.presignPutObject(presignRequest);

final String s3Url = String.format("https://%s.s3.%s.amazonaws.com/%s", bucket, region, key);

return new PresignedResponseDTO(presignedRequest.url().toExternalForm(), s3Url);
}

public String uploadBytes(
final String objectPath,
final byte[] bytes,
Expand Down Expand Up @@ -103,8 +152,8 @@ private String extractKey(final String s3Url) {
}
}

private String generateKey(final String path, final MultipartFile file) {
final String extension = StringUtils.getFilenameExtension(file.getOriginalFilename());
private String generateKey(final String path, final String originalFileName) {
final String extension = StringUtils.getFilenameExtension(originalFileName);
final String fileName = UUID.randomUUID() + "." + extension;

return path + fileName;
Expand Down
Loading