From d1393024cdf929379023068d78ba6859bd36d50d Mon Sep 17 00:00:00 2001 From: kimhobeen Date: Tue, 26 Sep 2023 15:35:44 +0900 Subject: [PATCH] =?UTF-8?q?:sparkles:=20feat=20:=20=EC=A0=95=EC=82=B0=20?= =?UTF-8?q?=EB=82=B4=EC=97=AD=20=EC=A1=B0=ED=9A=8C=20=EB=B0=8F=20=EA=B3=84?= =?UTF-8?q?=EC=A2=8C=20=EB=93=B1=EB=A1=9D,=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#494?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Server/src/docs/asciidoc/index.adoc | 5 +- .../asciidoc/snippets/adjustment/account.adoc | 42 ++ .../snippets/adjustment/adjustment.adoc | 51 +++ .../asciidoc/snippets/adjustment/docinfo.html | 36 ++ .../snippets/common/adjustmentstatus.adoc | 6 + .../asciidoc/snippets/order/adjustment.adoc | 25 -- .../java/com/server/ServerApplication.java | 1 - .../server/domain/account/domain/Account.java | 18 + .../account/repository/AccountRepository.java | 13 + .../controller/AdjustmentController.java | 103 +++++ .../dto/request/AccountUpdateApiRequest.java | 32 ++ .../domain/adjustment/domain/Adjustment.java | 75 ++++ .../adjustment/domain/AdjustmentStatus.java | 26 ++ .../repository/AdjustmentRepository.java | 7 + .../AdjustmentRepositoryCustom.java | 17 + .../repository/AdjustmentRepositoryImpl.java | 146 +++++++ .../repository/dto/AdjustmentData.java | 2 +- .../adjustment/service/AdjustmentService.java | 173 ++++++++ .../request/AccountUpdateServiceRequest.java | 15 + .../service/dto/response/AccountResponse.java | 70 ++++ .../dto/response/AdjustmentResponse.java | 4 +- .../dto/response/MonthAdjustmentResponse.java | 37 ++ .../dto/response/ToTalAdjustmentResponse.java | 31 ++ .../server/domain/member/entity/Member.java | 7 + .../order/controller/OrderController.java | 2 +- .../repository/OrderRepositoryCustom.java | 2 +- .../order/repository/OrderRepositoryImpl.java | 3 +- .../domain/order/service/OrderService.java | 4 +- Server/src/main/resources/messages.properties | 6 +- .../controller/AdjustmentControllerTest.java | 370 ++++++++++++++++++ .../repository/AdjustmentRepositoryTest.java | 141 +++++++ .../order/controller/OrderControllerTest.java | 2 +- .../order/repository/OrderRepositoryTest.java | 2 +- .../global/restdocs/CommonControllerTest.java | 4 +- .../global/testhelper/ControllerTest.java | 7 +- .../global/testhelper/RepositoryTest.java | 20 + 36 files changed, 1464 insertions(+), 41 deletions(-) create mode 100644 Server/src/docs/asciidoc/snippets/adjustment/account.adoc create mode 100644 Server/src/docs/asciidoc/snippets/adjustment/adjustment.adoc create mode 100644 Server/src/docs/asciidoc/snippets/adjustment/docinfo.html create mode 100644 Server/src/docs/asciidoc/snippets/common/adjustmentstatus.adoc delete mode 100644 Server/src/docs/asciidoc/snippets/order/adjustment.adoc create mode 100644 Server/src/main/java/com/server/domain/account/repository/AccountRepository.java create mode 100644 Server/src/main/java/com/server/domain/adjustment/controller/AdjustmentController.java create mode 100644 Server/src/main/java/com/server/domain/adjustment/controller/dto/request/AccountUpdateApiRequest.java create mode 100644 Server/src/main/java/com/server/domain/adjustment/domain/Adjustment.java create mode 100644 Server/src/main/java/com/server/domain/adjustment/domain/AdjustmentStatus.java create mode 100644 Server/src/main/java/com/server/domain/adjustment/repository/AdjustmentRepository.java create mode 100644 Server/src/main/java/com/server/domain/adjustment/repository/AdjustmentRepositoryCustom.java create mode 100644 Server/src/main/java/com/server/domain/adjustment/repository/AdjustmentRepositoryImpl.java rename Server/src/main/java/com/server/domain/{order => adjustment}/repository/dto/AdjustmentData.java (92%) create mode 100644 Server/src/main/java/com/server/domain/adjustment/service/AdjustmentService.java create mode 100644 Server/src/main/java/com/server/domain/adjustment/service/dto/request/AccountUpdateServiceRequest.java create mode 100644 Server/src/main/java/com/server/domain/adjustment/service/dto/response/AccountResponse.java rename Server/src/main/java/com/server/domain/{order => adjustment}/service/dto/response/AdjustmentResponse.java (83%) create mode 100644 Server/src/main/java/com/server/domain/adjustment/service/dto/response/MonthAdjustmentResponse.java create mode 100644 Server/src/main/java/com/server/domain/adjustment/service/dto/response/ToTalAdjustmentResponse.java create mode 100644 Server/src/test/java/com/server/domain/adjustment/controller/AdjustmentControllerTest.java create mode 100644 Server/src/test/java/com/server/domain/adjustment/repository/AdjustmentRepositoryTest.java diff --git a/Server/src/docs/asciidoc/index.adoc b/Server/src/docs/asciidoc/index.adoc index 7ee63d21..9800069c 100644 --- a/Server/src/docs/asciidoc/index.adoc +++ b/Server/src/docs/asciidoc/index.adoc @@ -33,7 +33,10 @@ include::overview.adoc[] === Order API * link:snippets/order/createorder.html[주문 생성/요청 API, onclick="window.location.href='snippets/order/createorder.html'"] * link:snippets/order/cancelorder.html[주문 전체/개별 취소 API, onclick="window.location.href='snippets/order/cancelorder.html'"] -* link:snippets/order/adjustment.html[주문 정산 API, onclick="window.location.href='snippets/order/adjustment.html'"] + +=== Adjustment API +* link:snippets/adjustment/adjustment.html[비디오별 정산 내역/월 및 연도별 정산액 API, onclick="window.location.href='snippets/adjustment/adjustment.html'"] +* link:snippets/adjustment/account.html[계좌 조회 및 등록 API, onclick="window.location.href='snippets/adjustment/account.html'"] === Video API * link:snippets/video/getvideo.html[비디오 조회 API, onclick="window.location.href='snippets/video/getvideo.html'"] diff --git a/Server/src/docs/asciidoc/snippets/adjustment/account.adoc b/Server/src/docs/asciidoc/snippets/adjustment/account.adoc new file mode 100644 index 00000000..5f0ed95c --- /dev/null +++ b/Server/src/docs/asciidoc/snippets/adjustment/account.adoc @@ -0,0 +1,42 @@ +:doctype: book +:icons: font +:source-highlighter: highlightjs +:toc: left +:toclevels: 2 +:sectlinks: +:docinfo: shared-head + +[[Account]] += 계좌 정보 API + +== 계좌 조회 +=== HTTP Request +include::{snippets}/adjustment/getaccount/http-request.adoc[] +==== Request Headers +include::{snippets}/adjustment/getaccount/request-headers.adoc[] +=== HTTP Response +include::{snippets}/adjustment/getaccount/http-response.adoc[] +==== Response Fields +include::{snippets}/adjustment/getaccount/response-fields.adoc[] + +== 계좌 조회(계좌 미등록 시) +=== HTTP Request +include::{snippets}/adjustment/getaccountnull/http-request.adoc[] +==== Request Headers +include::{snippets}/adjustment/getaccountnull/request-headers.adoc[] +=== HTTP Response +include::{snippets}/adjustment/getaccountnull/http-response.adoc[] +==== Response Fields +include::{snippets}/adjustment/getaccountnull/response-fields.adoc[] + +== 계좌 생성/수정 (put 요청입니다.) +=== HTTP Request +include::{snippets}/adjustment/calculateamount/http-request.adoc[] +==== Request Headers +include::{snippets}/adjustment/calculateamount/request-headers.adoc[] +==== Request Query Parameters +include::{snippets}/adjustment/calculateamount/request-parameters.adoc[] +=== HTTP Response +include::{snippets}/adjustment/calculateamount/http-response.adoc[] +==== Response Fields +include::{snippets}/adjustment/calculateamount/response-fields.adoc[] \ No newline at end of file diff --git a/Server/src/docs/asciidoc/snippets/adjustment/adjustment.adoc b/Server/src/docs/asciidoc/snippets/adjustment/adjustment.adoc new file mode 100644 index 00000000..3f528827 --- /dev/null +++ b/Server/src/docs/asciidoc/snippets/adjustment/adjustment.adoc @@ -0,0 +1,51 @@ +:doctype: book +:icons: font +:source-highlighter: highlightjs +:toc: left +:toclevels: 2 +:sectlinks: +:docinfo: shared-head + +[[Adjustment]] += 정산 내역 API + +IMPORTANT: month 를 null 로 주면 연도별 조회, year 을 null 로 주면 전체 조회 + +[[Adjustment-list]] +== 비디오별 정산 내역 +=== HTTP Request +include::{snippets}/adjustment/adjustment/http-request.adoc[] +==== Request Headers +include::{snippets}/adjustment/adjustment/request-headers.adoc[] +==== Request Query Parameters +include::{snippets}/adjustment/adjustment/request-parameters.adoc[] +=== HTTP Response +include::{snippets}/adjustment/adjustment/http-response.adoc[] +==== Response Fields +include::{snippets}/adjustment/adjustment/response-fields.adoc[] + +[[Adjustment-total]] +== 특정 월의 정산 총 금액 및 월별 정산 내역 +=== HTTP Request +include::{snippets}/adjustment/calculateamount/http-request.adoc[] +==== Request Headers +include::{snippets}/adjustment/calculateamount/request-headers.adoc[] +==== Request Query Parameters +include::{snippets}/adjustment/calculateamount/request-parameters.adoc[] +=== HTTP Response +include::{snippets}/adjustment/calculateamount/http-response.adoc[] +==== Response Fields +include::{snippets}/adjustment/calculateamount/response-fields.adoc[] + +[[Adjustment-totalyear]] +== 특정 연도의 정산 총 금액 및 월별 정산 내역 +=== HTTP Request +include::{snippets}/adjustment/calculateamountyear/http-request.adoc[] +==== Request Headers +include::{snippets}/adjustment/calculateamountyear/request-headers.adoc[] +==== Request Query Parameters +include::{snippets}/adjustment/calculateamountyear/request-parameters.adoc[] +=== HTTP Response +include::{snippets}/adjustment/calculateamountyear/http-response.adoc[] +==== Response Fields +include::{snippets}/adjustment/calculateamountyear/response-fields.adoc[] \ No newline at end of file diff --git a/Server/src/docs/asciidoc/snippets/adjustment/docinfo.html b/Server/src/docs/asciidoc/snippets/adjustment/docinfo.html new file mode 100644 index 00000000..ba403f51 --- /dev/null +++ b/Server/src/docs/asciidoc/snippets/adjustment/docinfo.html @@ -0,0 +1,36 @@ + \ No newline at end of file diff --git a/Server/src/docs/asciidoc/snippets/common/adjustmentstatus.adoc b/Server/src/docs/asciidoc/snippets/common/adjustmentstatus.adoc new file mode 100644 index 00000000..46ace74f --- /dev/null +++ b/Server/src/docs/asciidoc/snippets/common/adjustmentstatus.adoc @@ -0,0 +1,6 @@ +:doctype: book +:icons: font + +[[VideoStatus]] + +include::{snippets}/common/enums/custom-response-fields-AdjustmentStatus.adoc[] \ No newline at end of file diff --git a/Server/src/docs/asciidoc/snippets/order/adjustment.adoc b/Server/src/docs/asciidoc/snippets/order/adjustment.adoc deleted file mode 100644 index 23a1eb41..00000000 --- a/Server/src/docs/asciidoc/snippets/order/adjustment.adoc +++ /dev/null @@ -1,25 +0,0 @@ -:doctype: book -:icons: font -:source-highlighter: highlightjs -:toc: left -:toclevels: 2 -:sectlinks: -:docinfo: shared-head - -[[Order]] -= 정산 내역 API - -IMPORTANT: month 를 null 로 주면 연도별 조회, year 을 null 로 주면 전체 조회 - -[[Order-List]] -== 월별 정산 내역 -=== HTTP Request -include::{snippets}/order/adjustment/http-request.adoc[] -==== Request Headers -include::{snippets}/order/adjustment/request-headers.adoc[] -==== Request Query Parameters -include::{snippets}/order/adjustment/request-parameters.adoc[] -=== HTTP Response -include::{snippets}/order/adjustment/http-response.adoc[] -==== Response Fields -include::{snippets}/order/adjustment/response-fields.adoc[] diff --git a/Server/src/main/java/com/server/ServerApplication.java b/Server/src/main/java/com/server/ServerApplication.java index 0d96c9ed..c281282e 100644 --- a/Server/src/main/java/com/server/ServerApplication.java +++ b/Server/src/main/java/com/server/ServerApplication.java @@ -8,7 +8,6 @@ @SpringBootApplication @EnableJpaAuditing -@EnableBatchProcessing @EnableScheduling public class ServerApplication { diff --git a/Server/src/main/java/com/server/domain/account/domain/Account.java b/Server/src/main/java/com/server/domain/account/domain/Account.java index d85aaaff..3c5b198a 100644 --- a/Server/src/main/java/com/server/domain/account/domain/Account.java +++ b/Server/src/main/java/com/server/domain/account/domain/Account.java @@ -2,12 +2,15 @@ import com.server.domain.member.entity.Member; import com.server.global.entity.BaseEntity; +import lombok.AccessLevel; import lombok.Getter; +import lombok.NoArgsConstructor; import javax.persistence.*; @Entity @Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Account extends BaseEntity { @Id @@ -23,4 +26,19 @@ public class Account extends BaseEntity { @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id") private Member member; + + private Account(String name, String account, String bank, Member member) { + this.name = name; + this.account = account; + this.bank = bank; + this.member = member; + } + + public static Account createAccount(String name, String accountNumber, String bank, Member member) { + Account account = new Account(name, accountNumber, bank, member); + + member.updateAccount(account); + + return account; + } } diff --git a/Server/src/main/java/com/server/domain/account/repository/AccountRepository.java b/Server/src/main/java/com/server/domain/account/repository/AccountRepository.java new file mode 100644 index 00000000..238a680f --- /dev/null +++ b/Server/src/main/java/com/server/domain/account/repository/AccountRepository.java @@ -0,0 +1,13 @@ +package com.server.domain.account.repository; + +import com.server.domain.account.domain.Account; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.Optional; + +public interface AccountRepository extends JpaRepository { + + @Query("select a from Account a where a.member.memberId = :memberId") + Optional findByMemberId(Long memberId); +} \ No newline at end of file diff --git a/Server/src/main/java/com/server/domain/adjustment/controller/AdjustmentController.java b/Server/src/main/java/com/server/domain/adjustment/controller/AdjustmentController.java new file mode 100644 index 00000000..fde04e2b --- /dev/null +++ b/Server/src/main/java/com/server/domain/adjustment/controller/AdjustmentController.java @@ -0,0 +1,103 @@ +package com.server.domain.adjustment.controller; + +import com.server.domain.adjustment.controller.dto.request.AccountUpdateApiRequest; +import com.server.domain.adjustment.service.AdjustmentService; +import com.server.domain.adjustment.service.dto.response.AccountResponse; +import com.server.domain.adjustment.service.dto.response.ToTalAdjustmentResponse; +import com.server.domain.order.controller.dto.request.AdjustmentSort; +import com.server.domain.adjustment.service.dto.response.AdjustmentResponse; +import com.server.global.annotation.LoginId; +import com.server.global.exception.businessexception.orderexception.AdjustmentDateException; +import com.server.global.reponse.ApiPageResponse; +import com.server.global.reponse.ApiSingleResponse; +import org.springframework.data.domain.Page; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import javax.validation.Valid; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.Positive; +import java.net.URI; + +@RestController +@RequestMapping("/adjustments") +@Validated +public class AdjustmentController { + + private final AdjustmentService adjustmentService; + + public AdjustmentController(AdjustmentService adjustmentService) { + this.adjustmentService = adjustmentService; + } + + @GetMapping + public ResponseEntity> adjustment( + @RequestParam(defaultValue = "1") @Positive(message = "{validation.positive}") int page, + @RequestParam(defaultValue = "10") @Positive(message = "{validation.positive}") int size, + @RequestParam(required = false) @Min(value = 1) @Max(value = 12) Integer month, + @RequestParam(required = false) @Min(value = 2020) Integer year, + @RequestParam(defaultValue = "video-created-date") AdjustmentSort sort, + @LoginId Long loginMemberId) { + + checkValidDate(month, year); + + Page response = adjustmentService.adjustment(loginMemberId, page - 1, size, month, year, sort.getSort()); + + return ResponseEntity.ok(ApiPageResponse.ok(response, getAdjustmentMessage(month, year))); + } + + @GetMapping("/total-adjustment") + public ResponseEntity> calculateAmount( + @RequestParam(required = false) @Min(value = 1) @Max(value = 12) Integer month, + @RequestParam(required = false) @Min(value = 2020) Integer year, + @LoginId Long loginMemberId) { + + checkValidDate(month, year); + + ToTalAdjustmentResponse total = adjustmentService.totalAdjustment(loginMemberId, month, year); + + return ResponseEntity.ok(ApiSingleResponse.ok(total, getAdjustmentMessage(month, year))); + } + + @GetMapping("/account") + public ResponseEntity> getAccount( + @LoginId Long loginMemberId) { + + AccountResponse account = adjustmentService.getAccount(loginMemberId); + + String message = account.getName().equals("계좌 정보가 없습니다.") ? "계좌 정보가 없습니다." : "계좌 정보 조회 성공"; + + return ResponseEntity.ok(ApiSingleResponse.ok(account, message)); + } + + @PutMapping("/account") + public ResponseEntity> updateAccount( + @RequestBody @Valid AccountUpdateApiRequest request, + @LoginId Long loginMemberId) { + + adjustmentService.updateAccount(loginMemberId, request.toServiceRequest()); + + return ResponseEntity.noContent().build(); + } + + private void checkValidDate(Integer month, Integer year) { + if(month != null && year == null) { + throw new AdjustmentDateException(); + } + } + + private String getAdjustmentMessage(Integer month, Integer year) { + if(month == null && year == null) { + return "전체 정산 내역"; + } + + if(month != null && year != null) { + return year + "년 " + month + "월 정산 내역"; + } + + return year + "년 정산 내역"; + } +} diff --git a/Server/src/main/java/com/server/domain/adjustment/controller/dto/request/AccountUpdateApiRequest.java b/Server/src/main/java/com/server/domain/adjustment/controller/dto/request/AccountUpdateApiRequest.java new file mode 100644 index 00000000..f1b78ab2 --- /dev/null +++ b/Server/src/main/java/com/server/domain/adjustment/controller/dto/request/AccountUpdateApiRequest.java @@ -0,0 +1,32 @@ +package com.server.domain.adjustment.controller.dto.request; + +import com.server.domain.adjustment.service.dto.request.AccountUpdateServiceRequest; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +@AllArgsConstructor +@Getter +@Builder +public class AccountUpdateApiRequest { + + @NotBlank(message = "{validation.account.name.notBlank}") + private String name; + @NotBlank(message = "{validation.account.account.notBlank}") + private String account; + @NotBlank(message = "{validation.account.bank.notBlank}") + private String bank; + + public AccountUpdateServiceRequest toServiceRequest() { + return AccountUpdateServiceRequest.builder() + .name(name) + .account(account) + .bank(bank) + .build(); + } + + +} diff --git a/Server/src/main/java/com/server/domain/adjustment/domain/Adjustment.java b/Server/src/main/java/com/server/domain/adjustment/domain/Adjustment.java new file mode 100644 index 00000000..cadbec7d --- /dev/null +++ b/Server/src/main/java/com/server/domain/adjustment/domain/Adjustment.java @@ -0,0 +1,75 @@ +package com.server.domain.adjustment.domain; + +import com.server.domain.member.entity.Member; +import com.server.global.entity.BaseEntity; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Adjustment extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long adjustmentId; + + private Integer adjustmentYear; + + private Integer adjustmentMonth; + + private String name; + + private String account; + + private String bank; + + private Integer amount; + + @Enumerated(EnumType.STRING) + private AdjustmentStatus adjustmentStatus; + + private String reason; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + private Adjustment(Integer year, Integer month, String name, String account, String bank, Integer amount, AdjustmentStatus adjustmentStatus, String reason, Member member) { + this.adjustmentYear = year; + this.adjustmentMonth = month; + this.name = name; + this.account = account; + this.bank = bank; + this.amount = amount; + this.adjustmentStatus = adjustmentStatus; + this.reason = reason; + this.member = member; + } + + public static Adjustment createAdjustment(Integer year, + Integer month, + Member member, + Integer amount, + AdjustmentStatus adjustmentStatus, + String reason) { + return new Adjustment( + year, + month, + member.getAccount().getName(), + member.getAccount().getAccount(), + member.getAccount().getBank(), + amount, + adjustmentStatus, + reason, + member + ); + } + + public boolean isSameMonthAndYear(Integer year, Integer month) { + return this.adjustmentYear.equals(year) && this.adjustmentMonth.equals(month); + } +} diff --git a/Server/src/main/java/com/server/domain/adjustment/domain/AdjustmentStatus.java b/Server/src/main/java/com/server/domain/adjustment/domain/AdjustmentStatus.java new file mode 100644 index 00000000..9c55c641 --- /dev/null +++ b/Server/src/main/java/com/server/domain/adjustment/domain/AdjustmentStatus.java @@ -0,0 +1,26 @@ +package com.server.domain.adjustment.domain; + +import com.server.global.entity.BaseEnum; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum AdjustmentStatus implements BaseEnum { + TOTAL("전체 결과 조회"), + NO_ADJUSTMENT("정산 미진행"), + NOT_ADJUSTED("정산 전"), + ADJUSTING("정산중"), + ADJUSTED("정산완료"), + FAILED("정산실패"); + + private final String description; + + @Override + public String getName() { + return name(); + } + + @Override + public String getDescription() { + return this.description; + } +} diff --git a/Server/src/main/java/com/server/domain/adjustment/repository/AdjustmentRepository.java b/Server/src/main/java/com/server/domain/adjustment/repository/AdjustmentRepository.java new file mode 100644 index 00000000..7e38f816 --- /dev/null +++ b/Server/src/main/java/com/server/domain/adjustment/repository/AdjustmentRepository.java @@ -0,0 +1,7 @@ +package com.server.domain.adjustment.repository; + +import com.server.domain.adjustment.domain.Adjustment; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AdjustmentRepository extends JpaRepository, AdjustmentRepositoryCustom { +} \ No newline at end of file diff --git a/Server/src/main/java/com/server/domain/adjustment/repository/AdjustmentRepositoryCustom.java b/Server/src/main/java/com/server/domain/adjustment/repository/AdjustmentRepositoryCustom.java new file mode 100644 index 00000000..f990e89c --- /dev/null +++ b/Server/src/main/java/com/server/domain/adjustment/repository/AdjustmentRepositoryCustom.java @@ -0,0 +1,17 @@ +package com.server.domain.adjustment.repository; + +import com.server.domain.adjustment.domain.Adjustment; +import com.server.domain.adjustment.repository.dto.AdjustmentData; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +public interface AdjustmentRepositoryCustom { + + Page findByPeriod(Long memberId, Pageable pageable, Integer month, Integer year, String sort); + + Integer calculateAmount(Long memberId, Integer month, Integer year); + + List findMonthlyData(Long memberId, Integer year); +} diff --git a/Server/src/main/java/com/server/domain/adjustment/repository/AdjustmentRepositoryImpl.java b/Server/src/main/java/com/server/domain/adjustment/repository/AdjustmentRepositoryImpl.java new file mode 100644 index 00000000..b6953263 --- /dev/null +++ b/Server/src/main/java/com/server/domain/adjustment/repository/AdjustmentRepositoryImpl.java @@ -0,0 +1,146 @@ +package com.server.domain.adjustment.repository; + +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.server.domain.adjustment.domain.Adjustment; +import com.server.domain.adjustment.repository.dto.AdjustmentData; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import java.util.List; +import java.util.stream.Collectors; + +import static com.server.domain.adjustment.domain.QAdjustment.*; +import static com.server.domain.order.entity.QOrderVideo.orderVideo; +import static com.server.domain.video.entity.QVideo.video; + +public class AdjustmentRepositoryImpl implements AdjustmentRepositoryCustom { + + private final JPAQueryFactory queryFactory; + private final EntityManager em; + + public AdjustmentRepositoryImpl(EntityManager em) { + this.queryFactory = new JPAQueryFactory(em); + this.em = em; + } + + @Override + public Page findByPeriod(Long memberId, Pageable pageable, Integer month, Integer year, String sort) { + + TypedQuery jpqlQuery = em.createQuery( + "SELECT v.videoId, " + + "v.videoName, " + + "SUM(ov.price) AS totalSaleAmount, " + + "SUM(CASE WHEN ov.orderStatus = 'CANCELED' THEN ov.price ELSE 0 END) AS refundAmount " + + "FROM Video v " + + "LEFT JOIN v.orderVideos ov " + + "LEFT JOIN ov.order o " + + "WHERE o.paymentKey != null " + + "AND v.channel.id = :memberId " + + getDateCondition(month, year) + + "GROUP BY v.videoId " + + getAdjustmentSort(sort), Object[].class) + .setParameter("memberId", memberId) + .setFirstResult((int) pageable.getOffset()) + .setMaxResults(pageable.getPageSize()); + + setDateCondition(month, year, jpqlQuery); + + List resultList = jpqlQuery.getResultList(); + + List videoReportDatas = resultList.stream() + .map(arr -> new AdjustmentData( + (Long) arr[0], + (String) arr[1], + ((Number) arr[2]).intValue(), + ((Number) arr[3]).intValue() + )) + .collect(Collectors.toList()); + + JPAQuery countQuery = queryFactory.select(video.count()) + .from(video) + .join(video.orderVideos, orderVideo) + .where(video.channel.channelId.eq(memberId)); + + return new PageImpl<>(videoReportDatas, pageable, countQuery.fetchOne()); + } + + @Override + public Integer calculateAmount(Long memberId, Integer month, Integer year) { + + TypedQuery jpqlCountQuery = em.createQuery( + "SELECT SUM(ov.price) - SUM(CASE WHEN ov.orderStatus = 'CANCELED' THEN ov.price ELSE 0 END) AS total " + + "FROM Video v " + + "JOIN v.orderVideos ov " + + "JOIN ov.order o " + + "WHERE o.paymentKey != null " + + "AND v.channel.id = :memberId " + + getDateCondition(month, year), Long.class) + .setParameter("memberId", memberId); + + setDateCondition(month, year, jpqlCountQuery); + + Long singleResult = jpqlCountQuery.getSingleResult(); + + return singleResult == null ? 0 : singleResult.intValue(); + } + + @Override + public List findMonthlyData(Long memberId, Integer year) { + + return queryFactory.selectFrom(adjustment) + .where(adjustment.member.memberId.eq(memberId) + .and(YearEq(year))) + .fetch(); + } + + private BooleanExpression YearEq(Integer year) { + + if(year == null) return null; + + return adjustment.adjustmentYear.eq(year); + } + + private String getDateCondition(Integer month, Integer year) { + + String dateCondition = ""; + + if (year != null) { + dateCondition += "AND FUNCTION('YEAR', o.completedDate) = :year "; + } + + if (month != null) { + dateCondition += "AND FUNCTION('MONTH', o.completedDate) = :month "; + } + + return dateCondition; + } + + private void setDateCondition(Integer month, Integer year, TypedQuery jpqlQuery) { + + if (year != null) { + jpqlQuery.setParameter("year", year); + } + + if (month != null) { + jpqlQuery.setParameter("month", month); + } + } + + private String getAdjustmentSort(String sort) { + + String order = "ORDER BY "; + + if (sort.equals("totalSaleAmount")) { + order += "totalSaleAmount DESC, "; + } else if (sort.equals("refundAmount")) { + order += "refundAmount DESC, "; + } + + return order + "v.createdDate DESC"; + } +} diff --git a/Server/src/main/java/com/server/domain/order/repository/dto/AdjustmentData.java b/Server/src/main/java/com/server/domain/adjustment/repository/dto/AdjustmentData.java similarity index 92% rename from Server/src/main/java/com/server/domain/order/repository/dto/AdjustmentData.java rename to Server/src/main/java/com/server/domain/adjustment/repository/dto/AdjustmentData.java index 198c905f..bdc6f96a 100644 --- a/Server/src/main/java/com/server/domain/order/repository/dto/AdjustmentData.java +++ b/Server/src/main/java/com/server/domain/adjustment/repository/dto/AdjustmentData.java @@ -1,4 +1,4 @@ -package com.server.domain.order.repository.dto; +package com.server.domain.adjustment.repository.dto; import com.querydsl.core.annotations.QueryProjection; diff --git a/Server/src/main/java/com/server/domain/adjustment/service/AdjustmentService.java b/Server/src/main/java/com/server/domain/adjustment/service/AdjustmentService.java new file mode 100644 index 00000000..fa7e1b90 --- /dev/null +++ b/Server/src/main/java/com/server/domain/adjustment/service/AdjustmentService.java @@ -0,0 +1,173 @@ +package com.server.domain.adjustment.service; + +import com.server.domain.account.domain.Account; +import com.server.domain.account.repository.AccountRepository; +import com.server.domain.adjustment.domain.Adjustment; +import com.server.domain.adjustment.domain.AdjustmentStatus; +import com.server.domain.adjustment.repository.AdjustmentRepository; +import com.server.domain.adjustment.repository.dto.AdjustmentData; +import com.server.domain.adjustment.service.dto.request.AccountUpdateServiceRequest; +import com.server.domain.adjustment.service.dto.response.AccountResponse; +import com.server.domain.adjustment.service.dto.response.AdjustmentResponse; +import com.server.domain.adjustment.service.dto.response.MonthAdjustmentResponse; +import com.server.domain.adjustment.service.dto.response.ToTalAdjustmentResponse; +import com.server.domain.member.entity.Member; +import com.server.domain.member.repository.MemberRepository; +import com.server.global.exception.businessexception.memberexception.MemberNotFoundException; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@Transactional(readOnly = true) +public class AdjustmentService { + + private final AdjustmentRepository adjustmentRepository; + private final AccountRepository accountRepository; + private final MemberRepository memberRepository; + + public AdjustmentService(AdjustmentRepository adjustmentRepository, + AccountRepository accountRepository, MemberRepository memberRepository) { + this.adjustmentRepository = adjustmentRepository; + this.accountRepository = accountRepository; + this.memberRepository = memberRepository; + } + + public Page adjustment(Long loginMemberId, int page, int size, Integer month, Integer year, String sort) { + + Pageable pageable = PageRequest.of(page, size); + + Page datas = adjustmentRepository.findByPeriod(loginMemberId, pageable, month, year, sort); + + return datas.map(AdjustmentResponse::of); + } + + public Integer calculateAmount(Long loginMemberId, Integer month, Integer year) { + + return adjustmentRepository.calculateAmount(loginMemberId, month, year); + } + + public ToTalAdjustmentResponse totalAdjustment(Long loginMemberId, Integer month, Integer year) { + + List monthlyData = adjustmentRepository.findMonthlyData(loginMemberId, year); + + List monthAdjustmentResponses = monthlyData.stream() + .map(MonthAdjustmentResponse::of) + .collect(Collectors.toList()); + + //최근 2달 데이터 넣기 + addCurrentMonthData(monthAdjustmentResponses); + + //필요한 데이터 세팅 + return getTotalAdjustment(monthAdjustmentResponses, monthlyData, month, year); + } + + private void addCurrentMonthData(List monthAdjustmentResponses) { + + LocalDate now = LocalDate.now(); + int month = now.getMonthValue(); + int year = now.getYear(); + + //이번달 데이터 세팅 + int amount = adjustmentRepository.calculateAmount(1L, month, year); + monthAdjustmentResponses.add(MonthAdjustmentResponse.of(month, year, amount)); + + //지난달 데이터가 있는지 확인 + if (month == 1) { + month = 12; + year -= 1; + } else { + month -= 1; + } + + boolean hasLastMonth = false; + + for(MonthAdjustmentResponse monthAdjustmentResponse : monthAdjustmentResponses) { + if (monthAdjustmentResponse.isSameMonthAndYear(month, year)) { + hasLastMonth = true; + break; + } + } + + //지난달 데이터가 없다면 세팅 + if (!hasLastMonth) { + amount = adjustmentRepository.calculateAmount(1L, month, year); + monthAdjustmentResponses.add(MonthAdjustmentResponse.of(month, year, amount)); + } + } + + private ToTalAdjustmentResponse getTotalAdjustment(List monthAdjustmentResponses, + List monthlyData, + Integer month, + Integer year) { + + int amount = 0; + AdjustmentStatus status = AdjustmentStatus.NO_ADJUSTMENT; + String reason = null; + + if(year == null) { + + amount = adjustmentRepository.calculateAmount(1L, null, null); + status = AdjustmentStatus.TOTAL; + + return ToTalAdjustmentResponse.of(amount, status, reason, monthAdjustmentResponses); + } + + if(month == null) { + + amount = adjustmentRepository.calculateAmount(1L, null, year); + status = AdjustmentStatus.TOTAL; + + return ToTalAdjustmentResponse.of(amount, status, reason, monthAdjustmentResponses); + } + + for(MonthAdjustmentResponse monthAdjustmentResponse : monthAdjustmentResponses) { + if (monthAdjustmentResponse.isSameMonthAndYear(month, year)) { + amount = monthAdjustmentResponse.getAmount(); + status = AdjustmentStatus.NOT_ADJUSTED; + } + } + + for(Adjustment adjustment : monthlyData) { + if (adjustment.isSameMonthAndYear(month, year)) { + status = adjustment.getAdjustmentStatus(); + reason = adjustment.getReason() == null ? status.getDescription() : adjustment.getReason(); + } + } + + return ToTalAdjustmentResponse.of(amount, status, reason, monthAdjustmentResponses); + } + + public AccountResponse getAccount(Long loginMemberId) { + + Account account = getAccountOrNull(loginMemberId); + + return AccountResponse.of(account); + } + + public void updateAccount(Long loginMemberId, AccountUpdateServiceRequest request) { + + Account account = getAccountOrNull(loginMemberId); + + if(account == null) { + Member member = verifiedMember(loginMemberId); + Account.createAccount(request.getName(), request.getAccount(), request.getBank(), member); + } + + } + + private Account getAccountOrNull(Long loginMemberId) { + return accountRepository.findByMemberId(loginMemberId).orElse(null); + } + + private Member verifiedMember(Long memberId) { + return memberRepository.findById(memberId) + .orElseThrow(MemberNotFoundException::new); + } +} diff --git a/Server/src/main/java/com/server/domain/adjustment/service/dto/request/AccountUpdateServiceRequest.java b/Server/src/main/java/com/server/domain/adjustment/service/dto/request/AccountUpdateServiceRequest.java new file mode 100644 index 00000000..0c463c7c --- /dev/null +++ b/Server/src/main/java/com/server/domain/adjustment/service/dto/request/AccountUpdateServiceRequest.java @@ -0,0 +1,15 @@ +package com.server.domain.adjustment.service.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@AllArgsConstructor +@Getter +@Builder +public class AccountUpdateServiceRequest { + + private String name; + private String account; + private String bank; +} diff --git a/Server/src/main/java/com/server/domain/adjustment/service/dto/response/AccountResponse.java b/Server/src/main/java/com/server/domain/adjustment/service/dto/response/AccountResponse.java new file mode 100644 index 00000000..d51e614a --- /dev/null +++ b/Server/src/main/java/com/server/domain/adjustment/service/dto/response/AccountResponse.java @@ -0,0 +1,70 @@ +package com.server.domain.adjustment.service.dto.response; + +import com.server.domain.account.domain.Account; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@AllArgsConstructor +@Getter +@Builder +public class AccountResponse { + + private String name; + private String account; + private String bank; + + public static AccountResponse of(Account account) { + + if(account == null) { + return AccountResponse.builder() + .name("계좌 정보가 없습니다.") + .account("계좌 정보가 없습니다.") + .bank("계좌 정보가 없습니다.") + .build(); + } + + String name = putAsteriskName(account.getName()); + String accountNumber = putAsteriskAccount(account.getAccount()); + + return AccountResponse.builder() + .name(name) + .account(accountNumber) + .bank(account.getBank()) + .build(); + } + + private static String putAsteriskName(String name) { + if(name.length() == 2) { + return name.substring(0, 1) + "*"; + }else { + return name.substring(0, 2) + "*"; + } + } + + private static String putAsteriskAccount(String account) { + + String[] split = account.split("-"); + + if(split.length > 2) { + StringBuilder accountNumber = new StringBuilder(); + + for(int i = 0; i < split.length; i++) { + if(i != 1) { + accountNumber.append(split[i]); + }else { + accountNumber.append("****"); + } + if(i != split.length - 1) { + accountNumber.append("-"); + } + } + + return accountNumber.toString(); + } + if(split.length == 2) { + return split[0] + "-****"; + } + return split[0].substring(0, 5) + "****"; + } +} diff --git a/Server/src/main/java/com/server/domain/order/service/dto/response/AdjustmentResponse.java b/Server/src/main/java/com/server/domain/adjustment/service/dto/response/AdjustmentResponse.java similarity index 83% rename from Server/src/main/java/com/server/domain/order/service/dto/response/AdjustmentResponse.java rename to Server/src/main/java/com/server/domain/adjustment/service/dto/response/AdjustmentResponse.java index f6ad4f9c..1ac0c81a 100644 --- a/Server/src/main/java/com/server/domain/order/service/dto/response/AdjustmentResponse.java +++ b/Server/src/main/java/com/server/domain/adjustment/service/dto/response/AdjustmentResponse.java @@ -1,6 +1,6 @@ -package com.server.domain.order.service.dto.response; +package com.server.domain.adjustment.service.dto.response; -import com.server.domain.order.repository.dto.AdjustmentData; +import com.server.domain.adjustment.repository.dto.AdjustmentData; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; diff --git a/Server/src/main/java/com/server/domain/adjustment/service/dto/response/MonthAdjustmentResponse.java b/Server/src/main/java/com/server/domain/adjustment/service/dto/response/MonthAdjustmentResponse.java new file mode 100644 index 00000000..0f7ee770 --- /dev/null +++ b/Server/src/main/java/com/server/domain/adjustment/service/dto/response/MonthAdjustmentResponse.java @@ -0,0 +1,37 @@ +package com.server.domain.adjustment.service.dto.response; + +import com.server.domain.adjustment.domain.Adjustment; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@AllArgsConstructor +@Getter +@Builder +public class MonthAdjustmentResponse { + + private Integer year; + private Integer month; + private Integer amount; + + public static MonthAdjustmentResponse of(Adjustment adjustment) { + return MonthAdjustmentResponse.builder() + .year(adjustment.getAdjustmentYear()) + .month(adjustment.getAdjustmentMonth()) + .amount(adjustment.getAmount()) + .build(); + } + + public static MonthAdjustmentResponse of(int year, int month, int amount) { + return MonthAdjustmentResponse.builder() + .year(year) + .month(month) + .amount(amount) + .build(); + } + + public boolean isSameMonthAndYear(Integer year, Integer month) { + return this.year.equals(year) && this.month.equals(month); + } + +} diff --git a/Server/src/main/java/com/server/domain/adjustment/service/dto/response/ToTalAdjustmentResponse.java b/Server/src/main/java/com/server/domain/adjustment/service/dto/response/ToTalAdjustmentResponse.java new file mode 100644 index 00000000..5a37f4e1 --- /dev/null +++ b/Server/src/main/java/com/server/domain/adjustment/service/dto/response/ToTalAdjustmentResponse.java @@ -0,0 +1,31 @@ +package com.server.domain.adjustment.service.dto.response; + +import com.server.domain.adjustment.domain.AdjustmentStatus; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.util.List; + +@AllArgsConstructor +@Getter +@Builder +public class ToTalAdjustmentResponse { + + private Integer amount; + private AdjustmentStatus adjustmentStatus; + private String reason; + private List monthData; + + public static ToTalAdjustmentResponse of(Integer amount, + AdjustmentStatus adjustmentStatus, + String reason, + List monthData) { + return ToTalAdjustmentResponse.builder() + .amount(amount) + .adjustmentStatus(adjustmentStatus) + .reason(reason) + .monthData(monthData) + .build(); + } +} diff --git a/Server/src/main/java/com/server/domain/member/entity/Member.java b/Server/src/main/java/com/server/domain/member/entity/Member.java index 48da12d6..ba3a17a4 100644 --- a/Server/src/main/java/com/server/domain/member/entity/Member.java +++ b/Server/src/main/java/com/server/domain/member/entity/Member.java @@ -2,6 +2,7 @@ import javax.persistence.*; +import com.server.domain.account.domain.Account; import com.server.domain.answer.entity.Answer; import com.server.domain.cart.entity.Cart; import com.server.domain.channel.entity.Channel; @@ -65,6 +66,9 @@ public class Member extends BaseEntity { @OneToOne(mappedBy = "member", cascade = CascadeType.ALL) private Channel channel; + @OneToOne(mappedBy = "member", cascade = CascadeType.ALL) + private Account account; + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) @Builder.Default private List answers = new ArrayList<>(); @@ -166,4 +170,7 @@ private void checkEnoughReward(int reward) { if(this.reward - reward < 0) throw new RewardNotEnoughException(); } + public void updateAccount(Account account) { + this.account = account; + } } diff --git a/Server/src/main/java/com/server/domain/order/controller/OrderController.java b/Server/src/main/java/com/server/domain/order/controller/OrderController.java index fe9d609a..ed4b4769 100644 --- a/Server/src/main/java/com/server/domain/order/controller/OrderController.java +++ b/Server/src/main/java/com/server/domain/order/controller/OrderController.java @@ -5,7 +5,7 @@ import com.server.domain.order.controller.dto.response.PaymentApiResponse; import com.server.domain.order.controller.dto.response.VideoCancelApiResponse; import com.server.domain.order.service.OrderService; -import com.server.domain.order.service.dto.response.AdjustmentResponse; +import com.server.domain.adjustment.service.dto.response.AdjustmentResponse; import com.server.domain.order.service.dto.response.OrderResponse; import com.server.domain.order.service.dto.response.PaymentServiceResponse; import com.server.domain.order.service.dto.response.CancelServiceResponse; diff --git a/Server/src/main/java/com/server/domain/order/repository/OrderRepositoryCustom.java b/Server/src/main/java/com/server/domain/order/repository/OrderRepositoryCustom.java index e6585721..ed64e087 100644 --- a/Server/src/main/java/com/server/domain/order/repository/OrderRepositoryCustom.java +++ b/Server/src/main/java/com/server/domain/order/repository/OrderRepositoryCustom.java @@ -2,7 +2,7 @@ import com.server.domain.order.entity.Order; import com.server.domain.order.entity.OrderVideo; -import com.server.domain.order.repository.dto.AdjustmentData; +import com.server.domain.adjustment.repository.dto.AdjustmentData; import com.server.domain.video.entity.Video; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; diff --git a/Server/src/main/java/com/server/domain/order/repository/OrderRepositoryImpl.java b/Server/src/main/java/com/server/domain/order/repository/OrderRepositoryImpl.java index 7df92398..86f62b73 100644 --- a/Server/src/main/java/com/server/domain/order/repository/OrderRepositoryImpl.java +++ b/Server/src/main/java/com/server/domain/order/repository/OrderRepositoryImpl.java @@ -5,7 +5,7 @@ import com.server.domain.order.entity.Order; import com.server.domain.order.entity.OrderStatus; import com.server.domain.order.entity.OrderVideo; -import com.server.domain.order.repository.dto.AdjustmentData; +import com.server.domain.adjustment.repository.dto.AdjustmentData; import com.server.domain.video.entity.Video; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -18,7 +18,6 @@ import java.util.Optional; import java.util.stream.Collectors; -import static com.querydsl.core.group.GroupBy.sum; import static com.server.domain.cart.entity.QCart.cart; import static com.server.domain.member.entity.QMember.*; import static com.server.domain.order.entity.QOrder.*; diff --git a/Server/src/main/java/com/server/domain/order/service/OrderService.java b/Server/src/main/java/com/server/domain/order/service/OrderService.java index 5ea9e2f1..30080b34 100644 --- a/Server/src/main/java/com/server/domain/order/service/OrderService.java +++ b/Server/src/main/java/com/server/domain/order/service/OrderService.java @@ -6,9 +6,9 @@ import com.server.domain.order.entity.OrderStatus; import com.server.domain.order.entity.OrderVideo; import com.server.domain.order.repository.OrderRepository; -import com.server.domain.order.repository.dto.AdjustmentData; +import com.server.domain.adjustment.repository.dto.AdjustmentData; import com.server.domain.order.service.dto.request.OrderCreateServiceRequest; -import com.server.domain.order.service.dto.response.AdjustmentResponse; +import com.server.domain.adjustment.service.dto.response.AdjustmentResponse; import com.server.domain.order.service.dto.response.OrderResponse; import com.server.domain.order.service.dto.response.PaymentServiceResponse; import com.server.domain.order.service.dto.response.CancelServiceResponse; diff --git a/Server/src/main/resources/messages.properties b/Server/src/main/resources/messages.properties index f2495d68..d0e40d0a 100644 --- a/Server/src/main/resources/messages.properties +++ b/Server/src/main/resources/messages.properties @@ -53,4 +53,8 @@ validation.member.day.min=최소 1일 이상 부터 조회 가능합니다. validation.member.day.max=최대 30일 까지 조회 가능합니다. validation.member.image.name.null=이미지 이름은 필수입니다. -validation.report.content=신고 내용은 필수입니다. \ No newline at end of file +validation.report.content=신고 내용은 필수입니다. + +validation.account.name.notBlank=이름은 필수입니다. +validation.account.bank.notBlank=은행은 필수입니다. +validation.account.account.notBlank=계좌 번호는 필수입니다. diff --git a/Server/src/test/java/com/server/domain/adjustment/controller/AdjustmentControllerTest.java b/Server/src/test/java/com/server/domain/adjustment/controller/AdjustmentControllerTest.java new file mode 100644 index 00000000..439d6eb0 --- /dev/null +++ b/Server/src/test/java/com/server/domain/adjustment/controller/AdjustmentControllerTest.java @@ -0,0 +1,370 @@ +package com.server.domain.adjustment.controller; + +import com.server.domain.adjustment.controller.dto.request.AccountUpdateApiRequest; +import com.server.domain.adjustment.domain.AdjustmentStatus; +import com.server.domain.adjustment.service.dto.response.AccountResponse; +import com.server.domain.adjustment.service.dto.response.AdjustmentResponse; +import com.server.domain.adjustment.service.dto.response.MonthAdjustmentResponse; +import com.server.domain.adjustment.service.dto.response.ToTalAdjustmentResponse; +import com.server.domain.order.controller.dto.request.AdjustmentSort; +import com.server.global.reponse.ApiPageResponse; +import com.server.global.reponse.ApiSingleResponse; +import com.server.global.testhelper.ControllerTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Page; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.ResultActions; + +import java.util.ArrayList; +import java.util.List; + +import static com.server.global.testhelper.RestDocsUtil.pageResponseFields; +import static com.server.global.testhelper.RestDocsUtil.singleResponseFields; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.requestParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class AdjustmentControllerTest extends ControllerTest { + + private final String BASE_URL = "/adjustments"; + + @Test + @DisplayName("주문 정산 API") + void adjustment() throws Exception { + //given + int page = 1; + int size = 5; + int month = 9; + int year = 2023; + String sort = "video-created-date"; + + Page pageResponse = createPage(createAdjustmentResponse(size), page, size, 100); + + given(adjustmentService.adjustment(anyLong(), anyInt(), anyInt(), anyInt(), anyInt(), anyString())) + .willReturn(pageResponse); + + String apiResponse = objectMapper.writeValueAsString(ApiPageResponse.ok(pageResponse, year + "년 " + month + "월 정산 내역")); + + //when + ResultActions actions = mockMvc.perform( + get(BASE_URL) + .header(AUTHORIZATION, TOKEN) + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size)) + .param("month", String.valueOf(month)) + .param("year", String.valueOf(year)) + .param("sort", sort) + ); + + //then + actions + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().string(apiResponse)); + + //restDocs + actions.andDo( + documentHandler.document( + requestHeaders( + headerWithName(AUTHORIZATION).description("액세스 토큰") + ), + requestParameters( + parameterWithName("page").description("페이지 번호").optional(), + parameterWithName("size").description("페이지 크기").optional(), + parameterWithName("month").description("정산 월").optional(), + parameterWithName("year").description("정산 년도").optional(), + parameterWithName("sort").description(generateLinkCode(AdjustmentSort.class)).optional() + ), + pageResponseFields( + fieldWithPath("data").description("정산 내역"), + fieldWithPath("data[].videoId").description("비디오 ID"), + fieldWithPath("data[].videoName").description("비디오 이름"), + fieldWithPath("data[].totalSaleAmount").description("기간 내 총 판매 금액"), + fieldWithPath("data[].refundAmount").description("기간 내 환불 금액") + ) + ) + ); + } + + @Test + @DisplayName("연/월별 총 판매 금액 조회 API - 특정 달") + void calculateAmount() throws Exception { + //given + int month = 9; + int year = 2023; + + ToTalAdjustmentResponse response = createToTalAdjustmentResponse(9, 2023); + + given(adjustmentService.totalAdjustment(anyLong(), anyInt(), anyInt())) + .willReturn(response); + + String apiResponse = objectMapper.writeValueAsString(ApiSingleResponse.ok(response, year + "년 " + month + "월 정산 내역")); + + //when + ResultActions actions = mockMvc.perform( + get(BASE_URL + "/total-adjustment") + .header(AUTHORIZATION, TOKEN) + .param("month", String.valueOf(month)) + .param("year", String.valueOf(year)) + ); + + //then + actions + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().string(apiResponse)); + + //restDocs + actions.andDo( + documentHandler.document( + requestHeaders( + headerWithName(AUTHORIZATION).description("액세스 토큰") + ), + requestParameters( + parameterWithName("month").description("정산 월").optional(), + parameterWithName("year").description("정산 년도").optional() + ), + singleResponseFields( + fieldWithPath("data").description("정산 내역"), + fieldWithPath("data.amount").description("정산 금액"), + fieldWithPath("data.adjustmentStatus").description(generateLinkCode(AdjustmentStatus.class)), + fieldWithPath("data.reason").description("정산 실패 시 사유"), + fieldWithPath("data.monthData").description("월별 정산 내역"), + fieldWithPath("data.monthData[].month").description("월"), + fieldWithPath("data.monthData[].year").description("년"), + fieldWithPath("data.monthData[].amount").description("정산 금액") + ) + ) + ); + } + + @Test + @DisplayName("연/월별 총 판매 금액 조회 API - 전체 연도") + void calculateAmountYear() throws Exception { + //given + int year = 2023; + + ToTalAdjustmentResponse response = createToTalAdjustmentResponse(null, 2023); + + given(adjustmentService.totalAdjustment(anyLong(), isNull(), anyInt())) + .willReturn(response); + + String apiResponse = objectMapper.writeValueAsString(ApiSingleResponse.ok(response, year + "년 정산 내역")); + + //when + ResultActions actions = mockMvc.perform( + get(BASE_URL + "/total-adjustment") + .header(AUTHORIZATION, TOKEN) + .param("year", String.valueOf(year)) + ); + + //then + actions + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().string(apiResponse)); + + //restDocs + actions.andDo( + documentHandler.document( + requestHeaders( + headerWithName(AUTHORIZATION).description("액세스 토큰") + ), + requestParameters( + parameterWithName("year").description("정산 년도").optional() + ), + singleResponseFields( + fieldWithPath("data").description("정산 내역"), + fieldWithPath("data.amount").description("정산 금액"), + fieldWithPath("data.adjustmentStatus").description(generateLinkCode(AdjustmentStatus.class)), + fieldWithPath("data.reason").description("정산 실패 시 사유"), + fieldWithPath("data.monthData").description("월별 정산 내역"), + fieldWithPath("data.monthData[].month").description("월"), + fieldWithPath("data.monthData[].year").description("년"), + fieldWithPath("data.monthData[].amount").description("정산 금액") + ) + ) + ); + } + + @Test + @DisplayName("계좌 정보 조회 API") + void getAccount() throws Exception { + //given + AccountResponse response = AccountResponse.builder() + .name("홍길*") + .account("123-****-12314") + .bank("국민은행") + .build(); + + given(adjustmentService.getAccount(anyLong())).willReturn(response); + + //when + ResultActions actions = mockMvc.perform( + get(BASE_URL + "/account") + .header(AUTHORIZATION, TOKEN) + ); + + //then + actions + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().string(objectMapper.writeValueAsString(ApiSingleResponse.ok(response, "계좌 정보 조회 성공")))); + + //restDocs + actions.andDo( + documentHandler.document( + requestHeaders( + headerWithName(AUTHORIZATION).description("액세스 토큰") + ), + singleResponseFields( + fieldWithPath("data").description("계좌 정보"), + fieldWithPath("data.name").description("예금주"), + fieldWithPath("data.account").description("계좌번호"), + fieldWithPath("data.bank").description("은행") + ) + ) + ); + } + + @Test + @DisplayName("계좌 정보 조회 API - 계좌정보를 등록하지 않았을 때") + void getAccountNull() throws Exception { + //given + AccountResponse response = AccountResponse.builder() + .name("계좌 정보가 없습니다.") + .account("계좌 정보가 없습니다.") + .bank("계좌 정보가 없습니다.") + .build(); + + given(adjustmentService.getAccount(anyLong())).willReturn(response); + + //when + ResultActions actions = mockMvc.perform( + get(BASE_URL + "/account") + .header(AUTHORIZATION, TOKEN) + ); + + //then + actions + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().string(objectMapper.writeValueAsString(ApiSingleResponse.ok(response, "계좌 정보가 없습니다.")))); + + //restDocs + actions.andDo( + documentHandler.document( + requestHeaders( + headerWithName(AUTHORIZATION).description("액세스 토큰") + ), + singleResponseFields( + fieldWithPath("data").description("계좌 정보"), + fieldWithPath("data.name").description("예금주"), + fieldWithPath("data.account").description("계좌번호"), + fieldWithPath("data.bank").description("은행") + ) + ) + ); + } + + @Test + @DisplayName("계좌번호 변경 API") + void updateAccount() throws Exception { + //given + AccountUpdateApiRequest request = AccountUpdateApiRequest.builder() + .name("홍길동") + .account("123-123-123123") + .bank("국민은행") + .build(); + + //when + ResultActions actions = mockMvc.perform( + put(BASE_URL + "/account") + .header(AUTHORIZATION, TOKEN) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request)) + ); + + //then + actions + .andDo(print()) + .andExpect(status().isNoContent()); + + //restdocs + setConstraintClass(AccountUpdateApiRequest.class); + + actions.andDo( + documentHandler.document( + requestHeaders( + headerWithName(AUTHORIZATION).description("액세스 토큰") + ), + requestFields( + fieldWithPath("name").description("예금주").attributes(getConstraint("name")), + fieldWithPath("account").description("계좌번호").attributes(getConstraint("account")), + fieldWithPath("bank").description("은행").attributes(getConstraint("bank")) + ) + ) + ); + + + } + + private ToTalAdjustmentResponse createToTalAdjustmentResponse(Integer month, Integer year) { + + List monthAdjustmentResponses = new ArrayList<>(); + + if(month != null) { + for(int i = 1; i <= month; i++) { + monthAdjustmentResponses.add(MonthAdjustmentResponse.builder() + .month(i) + .year(year) + .amount(10000) + .build()); + } + } + else { + + for(int i = 1; i <= 9; i++) { + monthAdjustmentResponses.add(MonthAdjustmentResponse.builder() + .month(i) + .year(year) + .amount(10000) + .build()); + } + } + + if(month == null || year == null) { + return ToTalAdjustmentResponse.of(90000, AdjustmentStatus.TOTAL, "이유", monthAdjustmentResponses); + } + + return ToTalAdjustmentResponse.of(10000, AdjustmentStatus.NO_ADJUSTMENT, "이유", monthAdjustmentResponses); + } + + private List createAdjustmentResponse(int size) { + + List responses = new ArrayList<>(); + + for(int i = 1; i <= size; i++) { + responses.add(AdjustmentResponse.builder() + .videoId((long) i) + .videoName("비디오 " + i) + .totalSaleAmount(100000) + .refundAmount(1000) + .build()); + } + + return responses; + } +} \ No newline at end of file diff --git a/Server/src/test/java/com/server/domain/adjustment/repository/AdjustmentRepositoryTest.java b/Server/src/test/java/com/server/domain/adjustment/repository/AdjustmentRepositoryTest.java new file mode 100644 index 00000000..2075571c --- /dev/null +++ b/Server/src/test/java/com/server/domain/adjustment/repository/AdjustmentRepositoryTest.java @@ -0,0 +1,141 @@ +package com.server.domain.adjustment.repository; + +import com.server.domain.account.domain.Account; +import com.server.domain.adjustment.domain.Adjustment; +import com.server.domain.adjustment.domain.AdjustmentStatus; +import com.server.domain.adjustment.repository.dto.AdjustmentData; +import com.server.domain.member.entity.Member; +import com.server.domain.order.entity.Order; +import com.server.domain.order.repository.OrderRepository; +import com.server.domain.video.entity.Video; +import com.server.global.testhelper.RepositoryTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class AdjustmentRepositoryTest extends RepositoryTest { + + @Autowired private AdjustmentRepository adjustmentRepository; + + @Test + @DisplayName("기간 내 정산 결과를 얻는다.") + void findByPeriod() { + //given + Member owner = createMemberWithChannel(); + Video video1 = createAndSaveVideo(owner.getChannel()); + Video video2 = createAndSaveVideo(owner.getChannel()); + + Member member1 = createMemberWithChannel(); + Member member2 = createMemberWithChannel(); + + Order order1 = createAndSaveOrderComplete(member1, List.of(video1, video2)); + Order order2 = createAndSaveOrderComplete(member2, List.of(video1, video2)); + + order1.cancelVideoOrder(order1.getOrderVideos().get(0)); + + LocalDateTime now = LocalDateTime.now(); + int month = now.getMonthValue(); + int year = now.getYear(); + Pageable pageable = PageRequest.of(0, 10); + + int totalSaleAmount = video1.getPrice() + video2.getPrice(); + String sort = "sellingPrice"; + + //when + Page adjustment = adjustmentRepository.findByPeriod(owner.getMemberId(), pageable, month, year, sort); + + //then + assertThat(adjustment.getContent()).hasSize(2) + .extracting("videoName") + .containsExactly(video1.getVideoName(), video2.getVideoName()); + + assertThat(adjustment.getContent()).extracting("totalSaleAmount") + .containsExactly(totalSaleAmount, totalSaleAmount); + + assertThat(adjustment.getContent()).extracting("refundAmount") + .containsExactlyInAnyOrder(0, video1.getPrice()); + } + + @Test + @DisplayName("해당 월의 총 판매금액을 얻는다.") + void calculateAmount() { + //given + Member owner = createMemberWithChannel(); + Video video1 = createAndSaveVideo(owner.getChannel()); + Video video2 = createAndSaveVideo(owner.getChannel()); + + Member member1 = createMemberWithChannel(); + Member member2 = createMemberWithChannel(); + + Order order1 = createAndSaveOrderComplete(member1, List.of(video1, video2)); + Order order2 = createAndSaveOrderComplete(member2, List.of(video1, video2)); + + order1.cancelVideoOrder(order1.getOrderVideos().get(0)); + + LocalDateTime now = LocalDateTime.now(); + int month = now.getMonthValue(); + int year = now.getYear(); + + int totalSaleAmount = video1.getPrice() * 2 + video2.getPrice(); + + //when + Integer total = adjustmentRepository.calculateAmount(owner.getMemberId(), month, year); + + //then + assertThat(total).isEqualTo(totalSaleAmount); + } + + @Test + @DisplayName("해당 연도의 정산 데이터를 얻는다.") + void findMonthlyData() { + //given + Member member = createMemberWithChannel(); + Account account = createAndSaveAccount(member); + + Adjustment adjustment1 = createAndSaveAdjustment(member, 2023, 1); + Adjustment adjustment2 = createAndSaveAdjustment(member, 2023, 2); + Adjustment adjustment3 = createAndSaveAdjustment(member, 2023, 3); + Adjustment adjustment4 = createAndSaveAdjustment(member, 2022, 1); + + //when + List adjustments = adjustmentRepository.findMonthlyData(member.getMemberId(), 2023); + + //then + assertThat(adjustments).hasSize(3) + .extracting("adjustmentId") + .containsExactly(adjustment1.getAdjustmentId(), adjustment2.getAdjustmentId(), adjustment3.getAdjustmentId()); + } + + @Test + @DisplayName("정산 데이터를 받을 때 연도를 특정하지 않으면 전체 데이터를 얻는다.") + void findMonthlyDataTotal() { + //given + Member member = createMemberWithChannel(); + Account account = createAndSaveAccount(member); + + Adjustment adjustment1 = createAndSaveAdjustment(member, 2023, 1); + Adjustment adjustment2 = createAndSaveAdjustment(member, 2023, 2); + Adjustment adjustment3 = createAndSaveAdjustment(member, 2023, 3); + Adjustment adjustment4 = createAndSaveAdjustment(member, 2022, 1); + + //when + List adjustments = adjustmentRepository.findMonthlyData(member.getMemberId(), null); + + //then + assertThat(adjustments).hasSize(4) + .extracting("adjustmentId") + .containsExactly(adjustment1.getAdjustmentId(), + adjustment2.getAdjustmentId(), + adjustment3.getAdjustmentId(), + adjustment4.getAdjustmentId()); + + } +} \ No newline at end of file diff --git a/Server/src/test/java/com/server/domain/order/controller/OrderControllerTest.java b/Server/src/test/java/com/server/domain/order/controller/OrderControllerTest.java index 3d207587..b365c525 100644 --- a/Server/src/test/java/com/server/domain/order/controller/OrderControllerTest.java +++ b/Server/src/test/java/com/server/domain/order/controller/OrderControllerTest.java @@ -5,7 +5,7 @@ import com.server.domain.order.controller.dto.response.PaymentApiResponse; import com.server.domain.order.controller.dto.response.VideoCancelApiResponse; import com.server.domain.order.service.dto.request.OrderCreateServiceRequest; -import com.server.domain.order.service.dto.response.AdjustmentResponse; +import com.server.domain.adjustment.service.dto.response.AdjustmentResponse; import com.server.domain.order.service.dto.response.OrderResponse; import com.server.domain.order.service.dto.response.PaymentServiceResponse; import com.server.domain.order.service.dto.response.CancelServiceResponse; diff --git a/Server/src/test/java/com/server/domain/order/repository/OrderRepositoryTest.java b/Server/src/test/java/com/server/domain/order/repository/OrderRepositoryTest.java index af20ae29..9c402f93 100644 --- a/Server/src/test/java/com/server/domain/order/repository/OrderRepositoryTest.java +++ b/Server/src/test/java/com/server/domain/order/repository/OrderRepositoryTest.java @@ -6,7 +6,7 @@ import com.server.domain.member.entity.Member; import com.server.domain.order.entity.Order; import com.server.domain.order.entity.OrderVideo; -import com.server.domain.order.repository.dto.AdjustmentData; +import com.server.domain.adjustment.repository.dto.AdjustmentData; import com.server.domain.video.entity.Video; import com.server.domain.watch.entity.Watch; import com.server.global.testhelper.RepositoryTest; diff --git a/Server/src/test/java/com/server/global/restdocs/CommonControllerTest.java b/Server/src/test/java/com/server/global/restdocs/CommonControllerTest.java index e8cbd850..1411de32 100644 --- a/Server/src/test/java/com/server/global/restdocs/CommonControllerTest.java +++ b/Server/src/test/java/com/server/global/restdocs/CommonControllerTest.java @@ -1,5 +1,6 @@ package com.server.global.restdocs; +import com.server.domain.adjustment.domain.AdjustmentStatus; import com.server.domain.answer.entity.AnswerStatus; import com.server.domain.member.entity.Grade; import com.server.domain.member.entity.MemberStatus; @@ -167,7 +168,8 @@ void enums() throws Exception { VideoStatus.class, Grade.class, AdjustmentSort.class, - MemberStatus.class + MemberStatus.class, + AdjustmentStatus.class ); // Enum의 이름값들을 요청 데이터로 사용하기 위해 Json 형태로 변환 diff --git a/Server/src/test/java/com/server/global/testhelper/ControllerTest.java b/Server/src/test/java/com/server/global/testhelper/ControllerTest.java index 9c728bb8..4455cac3 100644 --- a/Server/src/test/java/com/server/global/testhelper/ControllerTest.java +++ b/Server/src/test/java/com/server/global/testhelper/ControllerTest.java @@ -16,6 +16,8 @@ import javax.validation.metadata.ConstraintDescriptor; import javax.validation.metadata.PropertyDescriptor; +import com.server.domain.adjustment.controller.AdjustmentController; +import com.server.domain.adjustment.service.AdjustmentService; import com.server.domain.announcement.controller.AnnouncementController; import com.server.domain.announcement.service.AnnouncementService; import com.server.domain.category.controller.CategoryController; @@ -90,7 +92,8 @@ AnnouncementController.class, CategoryController.class, SearchController.class, - ReportController.class + ReportController.class, + AdjustmentController.class }) @ExtendWith({RestDocumentationExtension.class}) @ActiveProfiles("local") @@ -127,6 +130,8 @@ public class ControllerTest { protected SearchEngine searchEngine; @MockBean protected WarmupState warmupState; + @MockBean + protected AdjustmentService adjustmentService; // 컨트롤러 테스트에 필요한 것들 @Autowired diff --git a/Server/src/test/java/com/server/global/testhelper/RepositoryTest.java b/Server/src/test/java/com/server/global/testhelper/RepositoryTest.java index 583233eb..4d61eece 100644 --- a/Server/src/test/java/com/server/global/testhelper/RepositoryTest.java +++ b/Server/src/test/java/com/server/global/testhelper/RepositoryTest.java @@ -1,5 +1,8 @@ package com.server.global.testhelper; +import com.server.domain.account.domain.Account; +import com.server.domain.adjustment.domain.Adjustment; +import com.server.domain.adjustment.domain.AdjustmentStatus; import com.server.domain.answer.entity.Answer; import com.server.domain.answer.entity.AnswerStatus; import com.server.domain.cart.entity.Cart; @@ -309,4 +312,21 @@ protected Reply createAndSaveReplies(Member member, Video video) { return reply; } + + protected Account createAndSaveAccount(Member member) { + Account account = Account.createAccount("kim", "1234", "하나", member); + + em.persist(account); + + return account; + } + + protected Adjustment createAndSaveAdjustment(Member member, int year, int month) { + + Adjustment adjustment = Adjustment.createAdjustment(year, month, member, 1000, AdjustmentStatus.ADJUSTED, "test"); + + em.persist(adjustment); + + return adjustment; + } }