ItemSalesCommandServiceImpl.java

package com.deveagles.be15_deveagles_be.features.sales.command.application.service.impl;

import com.deveagles.be15_deveagles_be.common.exception.BusinessException;
import com.deveagles.be15_deveagles_be.common.exception.ErrorCode;
import com.deveagles.be15_deveagles_be.features.customers.command.domain.aggregate.Customer;
import com.deveagles.be15_deveagles_be.features.customers.command.domain.repository.CustomerRepository;
import com.deveagles.be15_deveagles_be.features.membership.command.domain.repository.CustomerPrepaidPassRepository;
import com.deveagles.be15_deveagles_be.features.membership.command.domain.repository.CustomerSessionPassRepository;
import com.deveagles.be15_deveagles_be.features.sales.command.application.dto.request.ItemSalesRequest;
import com.deveagles.be15_deveagles_be.features.sales.command.application.dto.request.PaymentsInfo;
import com.deveagles.be15_deveagles_be.features.sales.command.application.service.ItemSalesCommandService;
import com.deveagles.be15_deveagles_be.features.sales.command.domain.aggregate.CustomerMembershipHistory;
import com.deveagles.be15_deveagles_be.features.sales.command.domain.aggregate.ItemSales;
import com.deveagles.be15_deveagles_be.features.sales.command.domain.aggregate.Payments;
import com.deveagles.be15_deveagles_be.features.sales.command.domain.aggregate.Sales;
import com.deveagles.be15_deveagles_be.features.sales.command.domain.repository.CustomerMembershipHistoryRepository;
import com.deveagles.be15_deveagles_be.features.sales.command.domain.repository.ItemSalesRepository;
import com.deveagles.be15_deveagles_be.features.sales.command.domain.repository.PaymentsRepository;
import com.deveagles.be15_deveagles_be.features.sales.command.domain.repository.SalesRepository;
import jakarta.transaction.Transactional;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ItemSalesCommandServiceImpl implements ItemSalesCommandService {

  private final CustomerPrepaidPassRepository customerPrepaidPassRepository;
  private final CustomerSessionPassRepository customerSessionPassRepository;
  private final SalesRepository salesRepository;
  private final PaymentsRepository paymentsRepository;
  private final CustomerRepository customerRepository;
  private final ItemSalesRepository itemSalesRepository;
  private final CustomerMembershipHistoryRepository customerMembershipHistoryRepository;

  @Transactional
  @Override
  public void registItemSales(ItemSalesRequest request) {

    // 유효성 검사
    if (request.getRetailPrice() < 0) {
      throw new BusinessException(ErrorCode.SALES_RETAILPRICE_REQUIRED);
    }
    if (request.getTotalAmount() < 0) {
      throw new BusinessException(ErrorCode.SALES_TOTALAMOUNT_REQUIRED);
    }
    for (PaymentsInfo payment : request.getPayments()) {
      if (payment.getAmount() == null || payment.getAmount() <= 0) {
        throw new BusinessException(ErrorCode.SALES_PAYMENTSAMOUNT_REQUIRED);
      }
      if (payment.getPaymentsMethod() == null) {
        throw new BusinessException(ErrorCode.SALES_PAYMENTMETHOD_REQUIRED);
      }
    }

    // 1. Sales 저장
    Sales sales =
        Sales.builder()
            .shopId(request.getShopId())
            .customerId(request.getCustomerId())
            .staffId(request.getStaffId())
            .reservationId(request.getReservationId())
            .retailPrice(request.getRetailPrice())
            .discountRate(request.getDiscountRate())
            .discountAmount(request.getDiscountAmount())
            .totalAmount(request.getTotalAmount())
            .salesMemo(request.getSalesMemo())
            .salesDate(request.getSalesDate())
            .isRefunded(false)
            .build();
    salesRepository.save(sales);

    // 2. Payments 저장 및 패스 차감
    for (PaymentsInfo p : request.getPayments()) {
      Payments payment =
          Payments.builder()
              .salesId(sales.getSalesId())
              .paymentsMethod(p.getPaymentsMethod())
              .amount(p.getAmount())
              .build();
      paymentsRepository.save(payment);

      Long savedPaymentsId = payment.getPaymentsId(); // 저장된 결제 ID

      switch (p.getPaymentsMethod()) {
        case PREPAID_PASS -> {
          var pass =
              customerPrepaidPassRepository
                  .findById(p.getCustomerPrepaidPassId())
                  .orElseThrow(
                      () -> new BusinessException(ErrorCode.CUSTOMERPREPAIDPASS_NOT_FOUND));

          pass.useAmount(p.getAmount());

          customerMembershipHistoryRepository.save(
              CustomerMembershipHistory.builder()
                  .salesId(sales.getSalesId())
                  .paymentsId(savedPaymentsId)
                  .customerPrepaidPassId(pass.getCustomerPrepaidPassId())
                  .build());
        }

        case SESSION_PASS -> {
          var pass =
              customerSessionPassRepository
                  .findById(p.getCustomerSessionPassId())
                  .orElseThrow(
                      () -> new BusinessException(ErrorCode.CUSTOMERSESSIONPASS_NOT_FOUND));

          int useCount = p.getUsedCount() != null ? p.getUsedCount() : 1;
          pass.useCount(useCount);

          customerMembershipHistoryRepository.save(
              CustomerMembershipHistory.builder()
                  .salesId(sales.getSalesId())
                  .paymentsId(savedPaymentsId)
                  .customerSessionPassId(pass.getCustomerSessionPassId())
                  .usedCount(useCount)
                  .build());
        }
      }
    }
    // 3. item_sales 저장
    itemSalesRepository.save(
        ItemSales.builder()
            .salesId(sales.getSalesId())
            .secondaryItemId(request.getSecondaryItemId())
            .quantity(request.getQuantity())
            .discountRate(request.getDiscountRate())
            .couponId(request.getCouponId())
            .build());

    // 4. 고객 정보 갱신
    Customer customer =
        customerRepository
            .findById(request.getCustomerId())
            .orElseThrow(() -> new BusinessException(ErrorCode.CUSTOMER_NOT_FOUND));
    customer.incrementVisitCount();
    customer.addRevenue(request.getTotalAmount());
    customer.updateRecentVisitDate(request.getSalesDate().toLocalDate());
  }

  @Transactional
  @Override
  public void updateItemSales(Long salesId, ItemSalesRequest request) {
    // 1. 기존 Sales 조회
    Sales sales =
        salesRepository
            .findById(salesId)
            .orElseThrow(() -> new BusinessException(ErrorCode.SALES_NOT_FOUND));

    // 1-1. 수정 전 총금액을 로컬 변수에 저장
    int oldTotalAmount = sales.getTotalAmount();

    // 2. 기존 Payments 조회 및 soft delete + 패스 복원
    List<Payments> oldPayments = paymentsRepository.findAllBySalesId(salesId);

    for (Payments old : oldPayments) {
      old.softDelete();

      // customer_membership_history 기반으로 어떤 패스를 썼는지 확인
      customerMembershipHistoryRepository
          .findBySalesIdAndPaymentsId(salesId, old.getPaymentsId())
          .ifPresent(
              history -> {
                if (history.getCustomerPrepaidPassId() != null) {
                  var pass =
                      customerPrepaidPassRepository
                          .findById(history.getCustomerPrepaidPassId())
                          .orElseThrow(
                              () -> new BusinessException(ErrorCode.CUSTOMERPREPAIDPASS_NOT_FOUND));
                  pass.restoreAmount(old.getAmount());
                }

                if (history.getCustomerSessionPassId() != null) {
                  var pass =
                      customerSessionPassRepository
                          .findById(history.getCustomerSessionPassId())
                          .orElseThrow(
                              () -> new BusinessException(ErrorCode.CUSTOMERSESSIONPASS_NOT_FOUND));
                  pass.restoreCount(history.getUsedCount());
                }

                // history도 soft delete
                history.softDelete();
              });
    }

    // 3. Sales 필드 수정
    sales.updateSales(
        request.getCustomerId(),
        request.getStaffId(),
        request.getReservationId(),
        request.getRetailPrice(),
        request.getDiscountRate(),
        request.getDiscountAmount(),
        request.getTotalAmount(),
        request.getSalesMemo(),
        request.getSalesDate());

    // 4. 새로운 Payments 추가 + 패스 차감 + history 저장
    for (PaymentsInfo p : request.getPayments()) {
      Payments payment =
          Payments.builder()
              .salesId(salesId)
              .paymentsMethod(p.getPaymentsMethod())
              .amount(p.getAmount())
              .build();
      paymentsRepository.save(payment);

      // customer_membership_history 저장용 변수
      Long customerPrepaidPassId = null;
      Long customerSessionPassId = null;

      int usedCount = 0;
      switch (p.getPaymentsMethod()) {
        case PREPAID_PASS -> {
          var pass =
              customerPrepaidPassRepository
                  .findById(p.getCustomerPrepaidPassId())
                  .orElseThrow(
                      () -> new BusinessException(ErrorCode.CUSTOMERPREPAIDPASS_NOT_FOUND));
          pass.useAmount(p.getAmount());
          customerPrepaidPassId = pass.getCustomerPrepaidPassId();
        }

        case SESSION_PASS -> {
          var pass =
              customerSessionPassRepository
                  .findById(p.getCustomerSessionPassId())
                  .orElseThrow(
                      () -> new BusinessException(ErrorCode.CUSTOMERSESSIONPASS_NOT_FOUND));
          usedCount = p.getUsedCount() != null ? p.getUsedCount() : 1;
          pass.useCount(usedCount);
          customerSessionPassId = pass.getCustomerSessionPassId();
        }
      }

      customerMembershipHistoryRepository.save(
          CustomerMembershipHistory.builder()
              .salesId(salesId)
              .paymentsId(payment.getPaymentsId())
              .customerPrepaidPassId(customerPrepaidPassId)
              .customerSessionPassId(customerSessionPassId)
              .usedCount(usedCount)
              .build());
    }

    // 5. ItemSales 수정
    ItemSales itemSales =
        itemSalesRepository
            .findBySalesId(salesId)
            .orElseThrow(() -> new BusinessException(ErrorCode.ITEMSALES_NOT_FOUND));
    itemSales.updateItemSales(
        request.getSecondaryItemId(),
        request.getQuantity(),
        request.getDiscountRate(),
        request.getCouponId());

    // 6. 고객 정보 갱신 (금액 차이 반영)
    Customer customer =
        customerRepository
            .findById(request.getCustomerId())
            .orElseThrow(() -> new BusinessException(ErrorCode.CUSTOMER_NOT_FOUND));

    int amountDifference = request.getTotalAmount() - oldTotalAmount;
    customer.addRevenue(amountDifference);
    customer.updateRecentVisitDate(request.getSalesDate().toLocalDate());
  }

  @Transactional
  @Override
  public void refundItemSales(Long salesId) {
    // 1. 기존 Sales 조회 및 환불 여부 확인
    Sales sales =
        salesRepository
            .findById(salesId)
            .orElseThrow(() -> new BusinessException(ErrorCode.SALES_NOT_FOUND));

    if (sales.getIsRefunded()) {
      throw new BusinessException(ErrorCode.SALES_ALREADY_REFUNDED);
    }

    int oldTotalAmount = sales.getTotalAmount();

    // 2. 기존 Payments 조회 및 soft delete + 패스 복원
    List<Payments> oldPayments = paymentsRepository.findAllBySalesId(salesId);

    for (Payments old : oldPayments) {
      old.softDelete();

      customerMembershipHistoryRepository
          .findBySalesIdAndPaymentsId(salesId, old.getPaymentsId())
          .ifPresent(
              history -> {
                if (history.getCustomerPrepaidPassId() != null) {
                  var pass =
                      customerPrepaidPassRepository
                          .findById(history.getCustomerPrepaidPassId())
                          .orElseThrow(
                              () -> new BusinessException(ErrorCode.CUSTOMERPREPAIDPASS_NOT_FOUND));
                  pass.restoreAmount(old.getAmount());
                }

                if (history.getCustomerSessionPassId() != null) {
                  var pass =
                      customerSessionPassRepository
                          .findById(history.getCustomerSessionPassId())
                          .orElseThrow(
                              () -> new BusinessException(ErrorCode.CUSTOMERSESSIONPASS_NOT_FOUND));
                  Integer usedCount = history.getUsedCount();
                  if (usedCount == null) {
                    throw new BusinessException(ErrorCode.INVALID_MEMBERSHIP_HISTORY);
                  }
                  pass.restoreCount(usedCount);
                }

                history.softDelete();
              });
    }

    // 3. Sales 환불 처리
    sales.setRefunded(true);

    // 4. 고객 정보 롤백
    Customer customer =
        customerRepository
            .findById(sales.getCustomerId())
            .orElseThrow(() -> new BusinessException(ErrorCode.CUSTOMER_NOT_FOUND));
    customer.subtractRevenue(oldTotalAmount);
  }
}