From 2eb51f7f80461bc455835913622d3675fc16a568 Mon Sep 17 00:00:00 2001 From: arlen02-01 Date: Wed, 14 Jan 2026 16:18:23 +0900 Subject: [PATCH] =?UTF-8?q?[UPLUS-57]=20=ED=95=A0=EC=9D=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=A0=9C=EA=B1=B0=20=EA=B5=AC=ED=98=84(CONTROLLER?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=EC=A4=91)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/controller/CustomerController.java | 55 +++++++++-- .../infra/entity/discount/DiscountPolicy.java | 54 ++++++++++ .../entity/discount/SubscriptionDiscount.java | 98 +++++++++++++++++++ .../infra/entity/discount/enums/Active.java | 6 ++ .../infra/entity/discount/enums/Category.java | 7 ++ .../entity/discount/enums/DiscountType.java | 6 ++ .../infra/entity/discount/enums/Status.java | 6 ++ .../entity/discount/enums/TargetScope.java | 6 ++ .../entity/subscription/Subscription.java | 9 +- .../customer/CustomerRepository.java | 3 +- .../discount/DiscountPolicyRepository.java | 9 ++ .../SubscriptionDiscountRepository.java | 13 +++ .../subscription/SubscriptionRepository.java | 5 + .../project/core/service/CustomerService.java | 23 +++-- .../project/core/service/DiscountService.java | 79 +++++++++++++++ .../core/service/SubscriptionService.java | 40 ++++++++ 16 files changed, 403 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/project/core/infra/entity/discount/DiscountPolicy.java create mode 100644 src/main/java/com/project/core/infra/entity/discount/SubscriptionDiscount.java create mode 100644 src/main/java/com/project/core/infra/entity/discount/enums/Active.java create mode 100644 src/main/java/com/project/core/infra/entity/discount/enums/Category.java create mode 100644 src/main/java/com/project/core/infra/entity/discount/enums/DiscountType.java create mode 100644 src/main/java/com/project/core/infra/entity/discount/enums/Status.java create mode 100644 src/main/java/com/project/core/infra/entity/discount/enums/TargetScope.java create mode 100644 src/main/java/com/project/core/infra/repository/discount/DiscountPolicyRepository.java create mode 100644 src/main/java/com/project/core/infra/repository/discount/SubscriptionDiscountRepository.java create mode 100644 src/main/java/com/project/core/service/DiscountService.java create mode 100644 src/main/java/com/project/core/service/SubscriptionService.java diff --git a/src/main/java/com/project/core/controller/CustomerController.java b/src/main/java/com/project/core/controller/CustomerController.java index c94fa9d..5e1dd14 100644 --- a/src/main/java/com/project/core/controller/CustomerController.java +++ b/src/main/java/com/project/core/controller/CustomerController.java @@ -1,11 +1,20 @@ package com.project.core.controller; +import java.util.List; + +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import com.project.core.controller.dto.request.FindCustomerRequest; +import com.project.core.controller.dto.request.ChangeEmailRequest; +import com.project.core.controller.dto.request.ChangeGradeRequest; +import com.project.core.controller.dto.response.ChangeEmailResponse; +import com.project.core.controller.dto.response.ChangeGradeResponse; import com.project.core.infra.entity.customer.Customer; import com.project.core.service.CustomerService; @@ -15,12 +24,44 @@ @RequestMapping("/user") @RequiredArgsConstructor public class CustomerController { - private final CustomerService customerService; + private final CustomerService customerService; + + /** + * 고객 조회 (전화번호 기준) + */ + @GetMapping + public ResponseEntity> loadByContactEnc( + @RequestParam String contactEnc + ) { + List customers = customerService.loadByContactEnc(contactEnc); + return ResponseEntity.ok(customers); + } + + /** + * 이메일 변경 + * @throws Exception + */ + @PostMapping("/{userId}/email") + public ResponseEntity changeEmail( + @PathVariable Long userId, + @RequestBody ChangeEmailRequest request + ) throws Exception { + ChangeEmailResponse response = + customerService.changeEmailEnc(userId, request); + return ResponseEntity.ok(response); + } - @GetMapping - public Long save(@RequestBody FindCustomerRequest request) { - Customer customer = customerService.loadByContactEnc(request.contactEnc()); - return customer.getCustomerId(); - } + /** + * 고객 등급 변경 + */ + @PostMapping("/{userId}/grade") + public ResponseEntity changeGrade( + @PathVariable Long userId, + @RequestBody ChangeGradeRequest request + ) { + ChangeGradeResponse response = + customerService.changeUserGrade(userId, request); + return ResponseEntity.ok(response); + } } diff --git a/src/main/java/com/project/core/infra/entity/discount/DiscountPolicy.java b/src/main/java/com/project/core/infra/entity/discount/DiscountPolicy.java new file mode 100644 index 0000000..b0b3b97 --- /dev/null +++ b/src/main/java/com/project/core/infra/entity/discount/DiscountPolicy.java @@ -0,0 +1,54 @@ +package com.project.core.infra.entity.discount; + +import java.util.ArrayList; +import java.util.List; + +import com.project.core.infra.entity.discount.enums.Active; +import com.project.core.infra.entity.discount.enums.Category; +import com.project.core.infra.entity.discount.enums.DiscountType; +import com.project.core.infra.entity.discount.enums.TargetScope; +import com.project.core.infra.entity.vas.SubscriptionVas; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "discount_policy") +public class DiscountPolicy { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "discount_id") + private Long discountId; + + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "discount_type", nullable = false) + private DiscountType discountType; + + @Column(name = "value", nullable = false) + private Double value; + + @Column(name = "category", nullable = false) + private Category category; + + @Column(name = "target_scope", nullable = false) + private TargetScope targetScope; + + @Column(name = "active", nullable = false) + private Active active; +//------------------------------------------------------------------ + @OneToMany(mappedBy = "discount_policy", cascade = CascadeType.ALL) + private List discountHistory = new ArrayList<>(); +} diff --git a/src/main/java/com/project/core/infra/entity/discount/SubscriptionDiscount.java b/src/main/java/com/project/core/infra/entity/discount/SubscriptionDiscount.java new file mode 100644 index 0000000..1032222 --- /dev/null +++ b/src/main/java/com/project/core/infra/entity/discount/SubscriptionDiscount.java @@ -0,0 +1,98 @@ +package com.project.core.infra.entity.discount; + +import java.time.LocalDateTime; + +import com.project.core.infra.entity.discount.enums.Active; +import com.project.core.infra.entity.discount.enums.Category; +import com.project.core.infra.entity.discount.enums.DiscountType; +import com.project.core.infra.entity.discount.enums.Status; +import com.project.core.infra.entity.discount.enums.TargetScope; +import com.project.core.infra.entity.plan.Plan; +import com.project.core.infra.entity.plan.SubscriptionPlan; +import com.project.core.infra.entity.subscription.Subscription; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "subscription_discount") +public class SubscriptionDiscount { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "sd_id") + private Long sdId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "discount_id", nullable = false) + private DiscountPolicy discountPolicy; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "sub_id", nullable = false) + private Subscription subscription; + + @Column(name = "discount_type", nullable = false) + private DiscountType discountType; + + @Column(name = "value", nullable = false) + private Double value; + + @Column(name = "target_scope", nullable = false) + private TargetScope targetScope; + + @Column(name = "start_date", nullable = false) + private LocalDateTime startDate; + + @Column(name = "end_date") + private LocalDateTime endDate; + + @Column(name = "status", nullable = false) + private Status status; + + public void setEndDate(LocalDateTime endDate) { + this.endDate = endDate; + } + public void setStatusTerminated() { + this.status = Status.TERMINATED; + } + + @Builder + private SubscriptionDiscount( + DiscountPolicy discountPolicy, + Subscription subscription, + DiscountType discountType, + Double value, + TargetScope targetScope, + LocalDateTime startDate + ) { + if (discountPolicy == null) throw new IllegalArgumentException("discountPolicy는 필수입니다."); + if (subscription == null) throw new IllegalArgumentException("subscription은 필수입니다."); + if (discountType == null) throw new IllegalArgumentException("discountType는 필수입니다."); + if (value == null) throw new IllegalArgumentException("value는 필수입니다."); + if (targetScope == null) throw new IllegalArgumentException("targetScope는 필수입니다."); + + this.discountPolicy = discountPolicy; + this.subscription = subscription; + this.discountType = discountType; + this.value = value; + this.targetScope = targetScope; + this.startDate = startDate != null ? startDate : LocalDateTime.now(); + + // 기본값 + this.endDate = null; + this.status = Status.ACTIVE; + } +} diff --git a/src/main/java/com/project/core/infra/entity/discount/enums/Active.java b/src/main/java/com/project/core/infra/entity/discount/enums/Active.java new file mode 100644 index 0000000..8e98ff0 --- /dev/null +++ b/src/main/java/com/project/core/infra/entity/discount/enums/Active.java @@ -0,0 +1,6 @@ +package com.project.core.infra.entity.discount.enums; + +public enum Active { + ACTIVE + ,INACTIVE +} diff --git a/src/main/java/com/project/core/infra/entity/discount/enums/Category.java b/src/main/java/com/project/core/infra/entity/discount/enums/Category.java new file mode 100644 index 0000000..5439a78 --- /dev/null +++ b/src/main/java/com/project/core/infra/entity/discount/enums/Category.java @@ -0,0 +1,7 @@ +package com.project.core.infra.entity.discount.enums; + +public enum Category { + 복지 + ,프로모션 + ,결합 +} diff --git a/src/main/java/com/project/core/infra/entity/discount/enums/DiscountType.java b/src/main/java/com/project/core/infra/entity/discount/enums/DiscountType.java new file mode 100644 index 0000000..aad9fc1 --- /dev/null +++ b/src/main/java/com/project/core/infra/entity/discount/enums/DiscountType.java @@ -0,0 +1,6 @@ +package com.project.core.infra.entity.discount.enums; + +public enum DiscountType { + Rate + ,Fixed +} diff --git a/src/main/java/com/project/core/infra/entity/discount/enums/Status.java b/src/main/java/com/project/core/infra/entity/discount/enums/Status.java new file mode 100644 index 0000000..7d193ba --- /dev/null +++ b/src/main/java/com/project/core/infra/entity/discount/enums/Status.java @@ -0,0 +1,6 @@ +package com.project.core.infra.entity.discount.enums; + +public enum Status { + ACTIVE + ,TERMINATED +} diff --git a/src/main/java/com/project/core/infra/entity/discount/enums/TargetScope.java b/src/main/java/com/project/core/infra/entity/discount/enums/TargetScope.java new file mode 100644 index 0000000..341e477 --- /dev/null +++ b/src/main/java/com/project/core/infra/entity/discount/enums/TargetScope.java @@ -0,0 +1,6 @@ +package com.project.core.infra.entity.discount.enums; + +public enum TargetScope { + PLAN_FEE + ,TOTAL_AMOUNT +} diff --git a/src/main/java/com/project/core/infra/entity/subscription/Subscription.java b/src/main/java/com/project/core/infra/entity/subscription/Subscription.java index 0c6fc6b..d9de4e0 100644 --- a/src/main/java/com/project/core/infra/entity/subscription/Subscription.java +++ b/src/main/java/com/project/core/infra/entity/subscription/Subscription.java @@ -1,6 +1,7 @@ package com.project.core.infra.entity.subscription; import com.project.core.infra.entity.customer.Customer; +import com.project.core.infra.entity.discount.SubscriptionDiscount; import com.project.core.infra.entity.plan.SubscriptionPlan; import com.project.core.infra.entity.subscription.enums.SubscriptionStatus; import com.project.core.infra.entity.vas.SubscriptionVas; @@ -57,7 +58,9 @@ public class Subscription { private List vasHistory = new ArrayList<>(); // 할인 이력 (1:N) - + @OneToMany(mappedBy = "subscription", cascade = CascadeType.ALL) + private List discountHistory = new ArrayList<>(); + @Builder public Subscription(Customer customer, String phoneNumber) { this.customer = customer; @@ -72,4 +75,8 @@ public void terminate() { this.status = SubscriptionStatus.TERMINATED; this.endDate = LocalDateTime.now(); } + //전화번호 마스킹된 버전 삽입 + public void setPhoneNumber(String maskedNum) { + this.phoneNumber = maskedNum; + } } diff --git a/src/main/java/com/project/core/infra/repository/customer/CustomerRepository.java b/src/main/java/com/project/core/infra/repository/customer/CustomerRepository.java index 45f604c..d6975f5 100644 --- a/src/main/java/com/project/core/infra/repository/customer/CustomerRepository.java +++ b/src/main/java/com/project/core/infra/repository/customer/CustomerRepository.java @@ -1,5 +1,6 @@ package com.project.core.infra.repository.customer; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -8,5 +9,5 @@ public interface CustomerRepository extends JpaRepository { - Optional findByContactEnc(String contactEnc); + List findByContactEnc(String contactEnc); } diff --git a/src/main/java/com/project/core/infra/repository/discount/DiscountPolicyRepository.java b/src/main/java/com/project/core/infra/repository/discount/DiscountPolicyRepository.java new file mode 100644 index 0000000..962e7a1 --- /dev/null +++ b/src/main/java/com/project/core/infra/repository/discount/DiscountPolicyRepository.java @@ -0,0 +1,9 @@ +package com.project.core.infra.repository.discount; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.project.core.infra.entity.discount.DiscountPolicy; + +public interface DiscountPolicyRepository extends JpaRepository { + +} diff --git a/src/main/java/com/project/core/infra/repository/discount/SubscriptionDiscountRepository.java b/src/main/java/com/project/core/infra/repository/discount/SubscriptionDiscountRepository.java new file mode 100644 index 0000000..de12366 --- /dev/null +++ b/src/main/java/com/project/core/infra/repository/discount/SubscriptionDiscountRepository.java @@ -0,0 +1,13 @@ +package com.project.core.infra.repository.discount; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.project.core.infra.entity.discount.SubscriptionDiscount; + +public interface SubscriptionDiscountRepository extends JpaRepository { + List findBySubId(Long subId); + Optional findBySdId(Long sdId); +} diff --git a/src/main/java/com/project/core/infra/repository/subscription/SubscriptionRepository.java b/src/main/java/com/project/core/infra/repository/subscription/SubscriptionRepository.java index f85dcb9..2f89474 100644 --- a/src/main/java/com/project/core/infra/repository/subscription/SubscriptionRepository.java +++ b/src/main/java/com/project/core/infra/repository/subscription/SubscriptionRepository.java @@ -1,12 +1,17 @@ package com.project.core.infra.repository.subscription; import com.project.core.infra.entity.customer.Customer; +import com.project.core.infra.entity.discount.SubscriptionDiscount; import com.project.core.infra.entity.subscription.Subscription; import com.project.core.infra.entity.subscription.enums.SubscriptionStatus; + +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; public interface SubscriptionRepository extends JpaRepository { long countByCustomerAndStatus(Customer customer, SubscriptionStatus status); boolean existsByPhoneNumberAndStatus(String phoneNumber, SubscriptionStatus status); boolean existsByPhoneNumber(String phoneNumber); + List findByCostomerId(Long customerId); } diff --git a/src/main/java/com/project/core/service/CustomerService.java b/src/main/java/com/project/core/service/CustomerService.java index 3e0d5c7..1cf42e5 100644 --- a/src/main/java/com/project/core/service/CustomerService.java +++ b/src/main/java/com/project/core/service/CustomerService.java @@ -1,5 +1,7 @@ package com.project.core.service; +import java.util.List; + import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -8,29 +10,36 @@ import com.project.core.controller.dto.response.ChangeEmailResponse; import com.project.core.controller.dto.response.ChangeGradeResponse; import com.project.core.infra.entity.customer.Customer; +import com.project.core.infra.entity.subscription.Subscription; import com.project.core.infra.repository.customer.CustomerRepository; +import com.project.global.util.AESUtil; import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor public class CustomerService { private final CustomerRepository customerRepository; + private final AESUtil aesUtil; @Transactional - public Customer loadByContactEnc(String contactEnc) { - return customerRepository.findByContactEnc(contactEnc).orElseThrow(()->new IllegalArgumentException("사용자를 찾을 수 없습니다")); + public List loadByContactEnc(String contactEnc) {//유저 조회 + List customers = customerRepository.findByContactEnc(contactEnc); + if(customers.isEmpty()) { + throw new IllegalStateException("해당하는 사용자가 없습니다"); + } + return customers; } @Transactional - public ChangeEmailResponse changeEmailEnc(Long userId, ChangeEmailRequest request) { + public ChangeEmailResponse changeEmailEnc(Long userId, ChangeEmailRequest request) throws Exception {//이메일 변경 Customer customer = customerRepository.findById(userId).orElseThrow(()->new IllegalArgumentException("사용자를 찾을 수 없습니다")); - - customer.changeEmailEnc(request.emailEnc()); - return new ChangeEmailResponse(customer.getEmailEnc()); + String encEmail = aesUtil.encrypt(request.emailEnc()); + customer.changeEmailEnc(encEmail); + return new ChangeEmailResponse(encEmail); } @Transactional - public ChangeGradeResponse changeUserGrade(Long userId, ChangeGradeRequest request) { + public ChangeGradeResponse changeUserGrade(Long userId, ChangeGradeRequest request) {//등급 변경 Customer customer = customerRepository.findById(userId).orElseThrow(()->new IllegalArgumentException("사용자를 찾을 수 없습니다")); customer.changeGrade(request.grade()); diff --git a/src/main/java/com/project/core/service/DiscountService.java b/src/main/java/com/project/core/service/DiscountService.java new file mode 100644 index 0000000..d8b0eaf --- /dev/null +++ b/src/main/java/com/project/core/service/DiscountService.java @@ -0,0 +1,79 @@ +package com.project.core.service; + +import java.time.LocalDateTime; +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.project.core.infra.entity.discount.DiscountPolicy; +import com.project.core.infra.entity.discount.SubscriptionDiscount; +import com.project.core.infra.entity.discount.enums.DiscountType; +import com.project.core.infra.entity.discount.enums.Status; +import com.project.core.infra.entity.discount.enums.TargetScope; +import com.project.core.infra.entity.subscription.Subscription; +import com.project.core.infra.repository.discount.DiscountPolicyRepository; +import com.project.core.infra.repository.discount.SubscriptionDiscountRepository; +import com.project.core.infra.repository.subscription.SubscriptionRepository; + +import lombok.RequiredArgsConstructor; + +@Service @RequiredArgsConstructor +public class DiscountService { + + private final SubscriptionDiscountRepository subscriptionDiscountRepository; + private final SubscriptionRepository subscriptionRepository; + private final DiscountPolicyRepository discountPolicyRepository; + + @Transactional(readOnly = true) //회선에 적용 된 할인 정책 확인 + public List loadRequiredBySubId(Long subId) { + + List discounts = + subscriptionDiscountRepository.findBySubId(subId); + + if (discounts.isEmpty()) { + throw new IllegalStateException("해당 회선에 적용된 할인이 없습니다"); + } + + return discounts; + } + + @Transactional + public Long addDiscount(Long subId, Long discountId) { + + Subscription subscription = + subscriptionRepository.findById(subId) + .orElseThrow(() -> new IllegalArgumentException("회선이 존재하지 않습니다")); + + DiscountPolicy policy = + discountPolicyRepository.findById(discountId) + .orElseThrow(() -> new IllegalArgumentException("할인 정책이 존재하지 않습니다")); + + SubscriptionDiscount discount = + SubscriptionDiscount.builder() + .discountPolicy(policy) + .subscription(subscription) + .discountType(policy.getDiscountType()) + .value(policy.getValue()) + .targetScope(policy.getTargetScope()) + .startDate(LocalDateTime.now()) + .build(); + + subscriptionDiscountRepository.save(discount); + + return discount.getSdId(); + } + + + @Transactional + public Long changeDiscount(Long discountId,Long sdId) { //할인 변경 + SubscriptionDiscount discount = + subscriptionDiscountRepository.findBySdId(sdId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 할인입니다.")); + //내부 정보 수정 - 기존 할인 종료 + discount.setEndDate(LocalDateTime.now()); + discount.setStatusTerminated(); + //새로운 할인 적용 + return addDiscount(discount.getSubscription().getSubId(),discountId); //생성된 할인 테이블 아이디 반환 + } +} diff --git a/src/main/java/com/project/core/service/SubscriptionService.java b/src/main/java/com/project/core/service/SubscriptionService.java new file mode 100644 index 0000000..0a401e1 --- /dev/null +++ b/src/main/java/com/project/core/service/SubscriptionService.java @@ -0,0 +1,40 @@ +package com.project.core.service; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.project.core.infra.entity.discount.SubscriptionDiscount; +import com.project.core.infra.entity.subscription.Subscription; +import com.project.core.infra.repository.discount.SubscriptionDiscountRepository; +import com.project.core.infra.repository.subscription.SubscriptionRepository; +import com.project.global.util.AESUtil; + +import lombok.RequiredArgsConstructor; + +@Service @RequiredArgsConstructor +public class SubscriptionService { + + private final SubscriptionRepository subscriptionRepository; + private final AESUtil aesUtil; + + @Transactional + public List findSubscription(Long customerId) throws Exception { + List subscriptions = + subscriptionRepository.findByCostomerId(customerId); + + if (subscriptions.isEmpty()) { + throw new IllegalStateException("보유중인 회선이 없습니다."); + }else { + //복호화 -> 마스킹 + for (int i = 0; i < subscriptions.size(); i++) { + String num = aesUtil.decrypt(subscriptions.get(i).getPhoneNumber()); + String maskedNum = num.substring(0,4)+"**"+num.substring(6,9)+"**"+num.substring(11,12); //"010-**34-**78" + subscriptions.get(i).setPhoneNumber(maskedNum); + } + } + + return subscriptions; + } +}