IncentiveCommandServiceImpl.java
package com.deveagles.be15_deveagles_be.features.staffsales.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.sales.command.domain.aggregate.PaymentsMethod;
import com.deveagles.be15_deveagles_be.features.shops.command.domain.aggregate.Shop;
import com.deveagles.be15_deveagles_be.features.shops.command.repository.ShopRepository;
import com.deveagles.be15_deveagles_be.features.staffsales.command.application.dto.ProductIncentiveRates;
import com.deveagles.be15_deveagles_be.features.staffsales.command.application.dto.StaffIncentiveInfo;
import com.deveagles.be15_deveagles_be.features.staffsales.command.application.dto.request.SetIncentiveRequest;
import com.deveagles.be15_deveagles_be.features.staffsales.command.application.dto.response.IncentiveListResult;
import com.deveagles.be15_deveagles_be.features.staffsales.command.application.dto.response.StaffSimpleInfo;
import com.deveagles.be15_deveagles_be.features.staffsales.command.application.service.IncentiveCommandService;
import com.deveagles.be15_deveagles_be.features.staffsales.command.domain.aggregate.Incentive;
import com.deveagles.be15_deveagles_be.features.staffsales.command.domain.aggregate.ProductType;
import com.deveagles.be15_deveagles_be.features.staffsales.command.domain.aggregate.StaffSalesSettingType;
import com.deveagles.be15_deveagles_be.features.staffsales.command.repository.IncentiveRepository;
import com.deveagles.be15_deveagles_be.features.users.command.domain.aggregate.Staff;
import com.deveagles.be15_deveagles_be.features.users.command.repository.UserRepository;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@RequiredArgsConstructor
public class IncentiveCommandServiceImpl implements IncentiveCommandService {
private final ShopRepository shopRepository;
private final UserRepository userRepository;
private final IncentiveRepository incentiveRepository;
@Override
@Transactional(readOnly = true)
public IncentiveListResult getIncentives(Long shopId) {
// 1. 매장 인센티브 활성화 여부 조회
boolean incentiveEnabled = shopRepository.findIncentiveStatusByShopId(shopId).orElse(false);
// 2. 직원 목록 조회
List<Staff> staffList = userRepository.findByShopIdAndLeftDateIsNull(shopId);
// 3. 매장 인센티브 조회
List<Incentive> incentives = incentiveRepository.findByShopIdAndIsActiveTrue(shopId);
// 4. 직원 목록 변환
List<StaffSimpleInfo> staffSimpleList =
staffList.stream()
.map(
staff ->
StaffSimpleInfo.builder()
.staffId(staff.getStaffId())
.staffName(staff.getStaffName())
.build())
.toList();
// 5. 직원별 인센티브 여부 판단
boolean isStaffBased = incentives.stream().anyMatch(i -> i.getStaffId() != null);
StaffSalesSettingType type =
isStaffBased ? StaffSalesSettingType.STAFF : StaffSalesSettingType.BULK;
// 6. 직원별 인센티브
List<StaffIncentiveInfo> incentiveInfoList =
buildStaffIncentiveInfoList(incentives, staffList, type);
return IncentiveListResult.builder()
.shopId(shopId)
.incentiveEnabled(incentiveEnabled)
.staffSalesSettingType(type)
.staffList(staffSimpleList)
.incentiveList(incentiveInfoList)
.build();
}
@Override
@Transactional
public void setIncentive(Long shopId, SetIncentiveRequest request) {
Shop shop =
shopRepository
.findByShopId(shopId)
.orElseThrow(() -> new BusinessException(ErrorCode.SHOP_NOT_FOUNT));
if (Boolean.FALSE.equals(request.isActive())) {
shop.setIncentive(false);
shopRepository.save(shop);
return;
} else {
shop.setIncentive(true);
shopRepository.save(shop);
}
Map<PaymentsMethod, ProductIncentiveRates> map = request.incentiveInfo().getIncentives();
Long staffId =
request.type() == StaffSalesSettingType.STAFF ? request.incentiveInfo().getStaffId() : null;
for (Map.Entry<PaymentsMethod, ProductIncentiveRates> entry : map.entrySet()) {
PaymentsMethod method = entry.getKey();
ProductIncentiveRates rates = entry.getValue();
saveOrUpdateIncentive(shopId, staffId, method, ProductType.SERVICE, rates.getService());
saveOrUpdateIncentive(shopId, staffId, method, ProductType.PRODUCT, rates.getProduct());
saveOrUpdateIncentive(
shopId, staffId, method, ProductType.SESSION_PASS, rates.getSessionPass());
saveOrUpdateIncentive(
shopId, staffId, method, ProductType.PREPAID_PASS, rates.getPrepaidPass());
}
}
private List<StaffIncentiveInfo> buildStaffIncentiveInfoList(
List<Incentive> incentives, List<Staff> staffList, StaffSalesSettingType type) {
if (type == StaffSalesSettingType.BULK) {
// 일괄 인센티브만 필터
Map<PaymentsMethod, ProductIncentiveRates> rateMap = mapIncentivesToRate(incentives, null);
return List.of(
StaffIncentiveInfo.builder().staffId(null).staffName(null).incentives(rateMap).build());
}
// 직원별
return staffList.stream()
.map(
staff -> {
Map<PaymentsMethod, ProductIncentiveRates> rates =
mapIncentivesToRate(incentives, staff.getStaffId());
// 해당 직원 인센티브 없으면 일괄 적용
if (rates.isEmpty()) {
rates = mapIncentivesToRate(incentives, null);
}
return StaffIncentiveInfo.builder()
.staffId(staff.getStaffId())
.staffName(staff.getStaffName())
.incentives(rates.isEmpty() ? ZERO_RATES : rates)
.build();
})
.toList();
}
private Map<PaymentsMethod, ProductIncentiveRates> mapIncentivesToRate(
List<Incentive> incentives, Long staffId) {
return incentives.stream()
.filter(i -> Objects.equals(i.getStaffId(), staffId))
.collect(
Collectors.groupingBy(
Incentive::getPaymentsMethod,
Collectors.collectingAndThen(
Collectors.toList(),
list -> {
Map<ProductType, Integer> productMap =
list.stream()
.collect(
Collectors.toMap(Incentive::getType, Incentive::getIncentive));
return ProductIncentiveRates.builder()
.service(productMap.getOrDefault(ProductType.SERVICE, 0))
.product(productMap.getOrDefault(ProductType.PRODUCT, 0))
.sessionPass(productMap.getOrDefault(ProductType.SESSION_PASS, 0))
.prepaidPass(productMap.getOrDefault(ProductType.PREPAID_PASS, 0))
.build();
})));
}
private static final Map<PaymentsMethod, ProductIncentiveRates> ZERO_RATES =
Arrays.stream(PaymentsMethod.values())
.collect(
Collectors.toMap(
Function.identity(),
method ->
ProductIncentiveRates.builder()
.service(0)
.product(0)
.sessionPass(0)
.prepaidPass(0)
.build()));
private void saveOrUpdateIncentive(
Long shopId, Long staffId, PaymentsMethod method, ProductType type, int ratio) {
if (staffId == null) {
// 1. 직원별 설정 isActive false 처리
List<Incentive> staffIncentives =
incentiveRepository.findStaffSpecificIncentives(shopId, method, type);
for (Incentive i : staffIncentives) {
i.setActive(false);
}
incentiveRepository.saveAll(staffIncentives);
// 2. 일괄 설정 insert or update
Optional<Incentive> existing = incentiveRepository.findCommonIncentives(shopId, method, type);
if (existing.isPresent()) {
existing.get().setIncentiveRatio(ratio);
existing.get().setActive(true); // 혹시 비활성화돼 있었을 수도 있으니
incentiveRepository.save(existing.get());
} else {
Incentive newIncentive =
Incentive.builder()
.shopId(shopId)
.staffId(null)
.paymentsMethod(method)
.type(type)
.incentive(ratio)
.isActive(true)
.build();
incentiveRepository.save(newIncentive);
}
} else {
// 직원별 설정
Optional<Incentive> existing =
incentiveRepository.findByShopIdAndStaffIdAndPaymentsMethodAndType(
shopId, staffId, method, type);
if (existing.isPresent()) {
existing.get().setIncentiveRatio(ratio);
existing.get().setActive(true);
incentiveRepository.save(existing.get());
} else {
Incentive newIncentive =
Incentive.builder()
.shopId(shopId)
.staffId(staffId)
.paymentsMethod(method)
.type(type)
.incentive(ratio)
.isActive(true)
.build();
incentiveRepository.save(newIncentive);
}
}
}
}