Merge #111 into master from cc_20260417_bonus

fix:工资奖金

* cc_20260417_bonus: (28 commits squashed)

  - feat:工资奖金发放

  - fix:工资奖金发放

  - fix:工资奖金发放

  - fix:工资奖金发放

  - fix:工资奖金发放

  - fix:门店实收相关接口

  - fix:新品销售门店级接口

  - fix:新品销售菜品列表接口;规则新增返回id

  - fix:新品销售菜品详情接口

  - fix:新品销售菜品接口补充

  - fix:小程序接口补充

  - fix

  - fix:新增实收、新品销售测试接口

  - fix:新增实收、新品销售测试接口

  - fix

  - fix:新增门店菜品列表接口

  - fix:同规则下无法新增相同菜品规则;新增小程序门店列表接口

  - fix

  - fix:小程序用户获取来源修改

  - fix:实收规则新增上月日均实收字段

  - fix:规则限制

  - fix:小程序上月日均实收

  - fix:查询异常

  - fix:查询异常

  - fix:菜品员工明细字段赋值异常

  - fix:排序

  - fix:排序

  - fix:员工实收列表新增门店筛选条件

Signed-off-by: 王非凡 <accounts_67eba0c5fee9c49c80c8e2b4@mail.teambition.com>
Merged-by: 正新 <accounts_6964c7bcd2a2c377c5bbd01b@mail.teambition.com>

CR-link: https://codeup.aliyun.com/692ea314dec569489f6f167c/hangzhou/java/custom_zxjp/change/111
This commit is contained in:
王非凡
2026-04-27 10:41:08 +00:00
committed by 正新
parent 97f8a8669c
commit 22fcfc6a90
95 changed files with 5466 additions and 8 deletions

View File

@@ -3,6 +3,7 @@ package com.cool.store.service;
import com.cool.store.dto.StoreDTO;
import com.cool.store.dto.StoreNameDTO;
import com.cool.store.dto.store.AuthStoreUserDTO;
import com.cool.store.dto.store.StoreUserDTO;
import com.cool.store.dto.store.StoreUserPositionDTO;
import com.cool.store.request.store.StoreListRequest;
import com.cool.store.response.MiniShopsResponse;
@@ -38,6 +39,11 @@ public interface StoreService {
List<AuthStoreUserDTO> authStoreUser(List<String> storeIdList, String positionType);
/**
* 同标品方法一致authStoreUser方法会查询出运营顾问等角色
*/
List<AuthStoreUserDTO> authStoreUserV2(List<String> storeIdList, String positionType);
List<AuthStoreUserDTO> getStorePositionUserList(List<String> storeIds,
List<String> positionIds,
List<String> nodePersonList,
@@ -51,4 +57,9 @@ public interface StoreService {
Boolean handleStoreLogLai(Integer flag,String specialStoreCode);
/**
* 获取门店人员的店内职位信息
*/
List<StoreUserDTO> getStoreUserPositionList(String storeId, String userName);
}

View File

@@ -0,0 +1,43 @@
package com.cool.store.service;
import com.cool.store.request.storeopen.StoreMonthRevenueRequest;
import com.cool.store.request.storeopen.StoreRecipeDailySalesRequest;
import com.cool.store.request.storeopen.StoreRecipesRequest;
import com.cool.store.response.storeopen.StoreMonthRevenueResponse;
import com.cool.store.response.storeopen.StoreRecipeDailySalesResponse;
import com.cool.store.response.storeopen.StoreRecipesResponse;
import java.util.List;
/**
* 第三方门店开放数据服务接口
*
* @author wangff
* @since 2026/4/20
*/
public interface ThirdStoreOpenDataService {
/**
* 查询门店月营收数据
*
* @param request 请求参数
* @return 月营收数据列表
*/
List<StoreMonthRevenueResponse> getMonthRevenue(StoreMonthRevenueRequest request);
/**
* 查询门店菜品日销量数据
*
* @param request 请求参数
* @return 菜品日销量数据列表
*/
List<StoreRecipeDailySalesResponse> getRecipeDailySales(StoreRecipeDailySalesRequest request);
/**
* 查询门店菜品种类
*
* @param request 请求参数
* @return 菜品种类列表
*/
List<StoreRecipesResponse> getRecipes(StoreRecipesRequest request);
}

View File

@@ -0,0 +1,156 @@
package com.cool.store.service.bonus;
import com.cool.store.entity.bonus.BonusDistributionRuleDO;
import com.cool.store.request.bonus.*;
import com.cool.store.response.storeopen.StoreRecipesResponse;
import com.cool.store.vo.bonus.*;
import com.github.pagehelper.PageInfo;
import java.math.BigDecimal;
import java.util.List;
/**
* <p>
* 奖金发放规则服务类
* </p>
*
* @author wangff
* @since 2026/4/20
*/
public interface BonusService {
/**
* 新增规则
*/
String addRule(BonusRuleAddRequest request, String createUserId);
/**
* 编辑规则
*/
Boolean updateRule(BonusRuleUpdateRequest request, String updateUserId);
/**
* 启用规则
*/
Boolean enableRule(BonusRuleEnableRequest request);
/**
* 规则详情
*/
BonusRuleVO getRuleDetail(Long ruleId);
/**
* 规则列表查询
*/
PageInfo<BonusRuleListVO> getList(BonusRuleQueryRequest request);
/**
* 实收实算
*/
BonusReceivedComputeVO receivedCompute(BonusReceivedComputeRequest request);
/**
* 新品销售实算
*/
BonusProductComputeVO productCompute(BonusProductComputeRequest request);
/**
* 处理单个实收规则
*/
void processReceivedRule(BonusDistributionRuleDO rule, String payMonth);
/**
* 处理单个新品销售规则
*/
void processNewProductRule(BonusDistributionRuleDO rule, String payDate);
/**
* 处理员工奖励明细
*/
void processEmployeeDetail(String payMonth);
/**
* 门店实收奖金发放列表
*/
PageInfo<BonusReceivedStoreListVO> getReceivedStoreList(BonusReceivedStoreQueryRequest request);
/**
* 门店实收奖金详情
*/
BonusReceivedStoreDetailVO getReceivedStoreDetail(Long id);
/**
* 员工实收奖金发放列表
*/
PageInfo<BonusReceivedEmployeeListVO> getReceivedEmployeeList(BonusReceivedEmployeeQueryRequest request);
/**
* 员工实收奖金详情
*/
BonusReceivedStoreDetailVO getReceivedEmployeeDetail(Long id);
/**
* 门店新品销售奖金发放列表-门店
*/
PageInfo<BonusProductStoreListVO> getNewProductStoreList(BonusProductQueryRequest request);
/**
* 门店新品销售奖金详情
*/
BonusProductStoreDetailVO getNewProductStoreDetail(BonusProductMonthlyQueryRequest request);
/**
* 门店新品销售奖金发放列表-菜品
*/
PageInfo<BonusProductRecipeListVO> getNewProductRecipeList(BonusProductQueryRequest request);
/**
* 门店新品销售奖金发放详情-菜品
*/
PageInfo<BonusProductRecipeDetailVO> getNewProductRecipeDetail(BonusProductRecipeQueryRequest request);
/**
* 门店新品销售奖金发放列表-员工
*/
PageInfo<BonusProductEmployeeListVO> getNewProductEmployeeList(BonusProductEmployeeQueryRequest request);
/**
* 门店新品销售奖金发放详情-员工
*/
BonusProductStoreDetailVO getNewProductEmployeeDetail(BonusProductMonthlyQueryRequest request);
/**
* 门店新品销售奖金方法列表-菜品+员工
*/
PageInfo<BonusProductRecipeEmployeeListVO> getNewProductRecipeEmployeeList(BonusProductEmployeeQueryRequest request);
/**
* 门店新品销售奖金发放详情-菜品+员工
*/
PageInfo<BonusProductRecipeEmployeeDetailVO> getNewProductRecipeEmployeeDetail(BonusProductRecipeEmployeeQueryRequest request);
/**
* 员工奖金明细
*/
PageInfo<BonusEmployeeDetailVO> getEmployeeMonthlyDetail(BonusProductEmployeeQueryRequest request);
/**
* 删除月度实收数据
*/
void deleteMonthlyReceived(BonusDistributionRuleDO ruleDO, String payMonth);
/**
* 删除每日新品销售数据
*/
void deleteMonthlyNewProduct(BonusDistributionRuleDO ruleDO, String payDate);
/**
* 查询门店菜品种类
*/
List<StoreRecipesResponse> getRecipes(String storeId);
/**
* 查询上月日均实收
*/
BigDecimal receivedDailyLast(String storeId);
}

View File

@@ -0,0 +1,898 @@
package com.cool.store.service.bonus.impl;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.lang.Pair;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.core.utils.StringUtils;
import com.cool.store.dao.StoreDao;
import com.cool.store.dao.bonus.*;
import com.cool.store.entity.StoreDO;
import com.cool.store.entity.bonus.*;
import com.cool.store.enums.ErrorCodeEnum;
import com.cool.store.exception.ServiceException;
import com.cool.store.request.bonus.*;
import com.cool.store.request.storeopen.StoreMonthRevenueRequest;
import com.cool.store.request.storeopen.StoreRecipeDailySalesRequest;
import com.cool.store.request.storeopen.StoreRecipesRequest;
import com.cool.store.response.storeopen.StoreMonthRevenueResponse;
import com.cool.store.response.storeopen.StoreRecipeDailySalesResponse;
import com.cool.store.response.storeopen.StoreRecipesResponse;
import com.cool.store.service.ThirdStoreOpenDataService;
import com.cool.store.service.bonus.BonusService;
import com.cool.store.utils.BeanUtil;
import com.cool.store.utils.poi.DateUtils;
import com.cool.store.vo.bonus.*;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
/**
* <p>
* 奖金发放规则服务实现类
* </p>
*
* @author wangff
* @since 2026/4/20
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class BonusServiceImpl implements BonusService {
private final BonusDistributionRuleDAO ruleDAO;
private final ThirdStoreOpenDataService storeOpenDataService;
private final BonusReceivedStoreDAO receivedStoreDAO;
private final StoreDao storeDao;
private final BonusEmployeeRewardDetailDAO rewardDetailDAO;
private final BonusReceivedEmployeeDAO receivedEmployeeDAO;
private final BonusNewProductEmployeeDAO newProductEmployeeDAO;
private final BonusNewProductStoreDAO newProductStoreDAO;
private final BonusNewProductRecipeDAO newProductRecipeDAO;
private final BonusNewProductRecipeEmployeeDAO newProductRecipeEmployeeDAO;
@Override
public String addRule(BonusRuleAddRequest request, String createUserId) {
if (ruleDAO.existOverlap(request.getStoreId(), request.getType(), request.getStartDate(), request.getEndDate())) {
throw new ServiceException(ErrorCodeEnum.BONUS_EXIST_OVERLAP_RULE);
}
BonusDistributionRuleDO ruleDO = BeanUtil.toBean(request, BonusDistributionRuleDO.class);
ruleDO.setCreateUserId(createUserId);
ruleDAO.addRule(ruleDO);
return ruleDO.getId().toString();
}
@Override
public Boolean updateRule(BonusRuleUpdateRequest request, String updateUserId) {
BonusDistributionRuleDO ruleDO = ruleDAO.getById(request.getId());
if (Objects.isNull(ruleDO)) {
throw new ServiceException(ErrorCodeEnum.BONUS_RULE_NOT_EXIST);
}
if (ruleDAO.existOverlap(ruleDO.getStoreId(), ruleDO.getType(), request.getStartDate(), request.getEndDate())) {
throw new ServiceException(ErrorCodeEnum.BONUS_EXIST_OVERLAP_RULE);
}
BigDecimal rewardRatio = request.getDistributeConfigs().stream()
.map(BonusRuleDistributeConfig::getRewardRatio)
.reduce(BigDecimal.ZERO, BigDecimal::add);
if (rewardRatio.compareTo(BigDecimal.valueOf(100)) > 0) {
throw new ServiceException(ErrorCodeEnum.BONUS_DISTRIBUTE_RATIO_OVER_100);
}
JSONObject config = new JSONObject();
if (ruleDO.getType().equals(1)) {
for (BonusRuleReceivedConfig receivedConfig : request.getReceivedConfigs()) {
if (receivedConfig.getGt().compareTo(receivedConfig.getLe()) >= 0) {
throw new ServiceException(ErrorCodeEnum.BONUS_RULE_CONFIG_ERROR);
}
}
config.put("receivedStart", request.getReceivedStart());
config.put("receivedConfigs", request.getReceivedConfigs());
} else {
// 校验是否有相同菜品
Map<String, List<BonusRuleProductConfig>> group = CollStreamUtil.groupByKey(request.getProductConfigs(), BonusRuleProductConfig::getRecipeNo);
for (List<BonusRuleProductConfig> value : group.values()) {
if (value.size() > 1) {
throw new ServiceException(ErrorCodeEnum.BONUS_PRODUCT_CONFIG_DUPLICATE);
}
}
config.put("productConfigs", request.getProductConfigs());
}
BonusDistributionRuleDO updateRule = BonusDistributionRuleDO.builder()
.id(request.getId())
.startDate(request.getStartDate())
.endDate(request.getEndDate())
.bonusConfig(config.toJSONString())
.distributeConfig(JSONObject.toJSONString(request.getDistributeConfigs()))
.updateUserId(updateUserId)
.build();
return ruleDAO.updateByPrimaryKeySelective(updateRule);
}
@Override
public Boolean enableRule(BonusRuleEnableRequest request) {
BonusDistributionRuleDO ruleDO = ruleDAO.getById(request.getId());
if (Objects.isNull(ruleDO)) {
throw new ServiceException(ErrorCodeEnum.BONUS_RULE_NOT_EXIST);
}
if (request.getEnable().equals(1) && ruleDAO.existOverlap(ruleDO.getStoreId(), ruleDO.getType(), ruleDO.getStartDate(), ruleDO.getEndDate())) {
throw new ServiceException(ErrorCodeEnum.BONUS_EXIST_OVERLAP_RULE);
}
// 校验两个规则是否配置
if (StringUtils.isBlank(ruleDO.getBonusConfig()) || StringUtils.isBlank(ruleDO.getDistributeConfig())) {
throw new ServiceException(ErrorCodeEnum.BONUS_RULE_NOT_CONFIG);
}
BonusDistributionRuleDO updateRule = new BonusDistributionRuleDO();
updateRule.setId(request.getId());
updateRule.setEnable(request.getEnable());
return ruleDAO.updateByPrimaryKeySelective(updateRule);
}
@Override
public BonusRuleVO getRuleDetail(Long ruleId) {
BonusDistributionRuleDO ruleDO = ruleDAO.getById(ruleId);
BonusRuleVO vo = BeanUtil.toBean(ruleDO, BonusRuleVO.class);
if (Objects.nonNull(vo)) {
JSONObject bonusConfig = JSONObject.parseObject(ruleDO.getBonusConfig());
BeanUtil.copyPropertiesIgnoreId(bonusConfig, vo);
vo.setDistributeConfigs(JSONObject.parseArray(ruleDO.getDistributeConfig(), BonusRuleDistributeConfig.class));
StoreDO storeDO = storeDao.getEffectiveByStoreId(vo.getStoreId());
if (Objects.nonNull(storeDO)) {
vo.setStoreNum(storeDO.getStoreNum());
vo.setStoreName(storeDO.getStoreName());
}
if (ruleDO.getType().equals(1)) {
// 查询上月日均实收
String lastMonth = LocalDate.now().minusMonths(1).format(DateTimeFormatter.ofPattern("yyyy-MM"));
StoreMonthRevenueRequest request = new StoreMonthRevenueRequest();
request.setStoreNum(storeDO.getStoreNum());
request.setStartDate(lastMonth);
request.setEndDate(lastMonth);
List<StoreMonthRevenueResponse> monthRevenue = storeOpenDataService.getMonthRevenue(request);
if (CollectionUtils.isNotEmpty(monthRevenue)) {
StoreMonthRevenueResponse storeMonthRevenueResponse = monthRevenue.get(0);
vo.setReceivedDailyLast(Objects.nonNull(storeMonthRevenueResponse.getReceivedAmountDaily()) ? storeMonthRevenueResponse.getReceivedAmountDaily() : BigDecimal.ZERO);
}
}
}
return vo;
}
@Override
public PageInfo<BonusRuleListVO> getList(BonusRuleQueryRequest request) {
PageHelper.startPage(request.getPageNum(), request.getPageSize());
List<BonusDistributionRuleDO> list = ruleDAO.getList(request.getStoreNumOrName(), request.getType(), request.getEnable(), request.getStoreId());
PageInfo<BonusRuleListVO> page = BeanUtil.toPage(new PageInfo<>(list), BonusRuleListVO.class);
fillBasicField(page.getList());
return page;
}
@Override
public BonusReceivedComputeVO receivedCompute(BonusReceivedComputeRequest request) {
if (Objects.isNull(request.getBusinessDays()) || request.getBusinessDays().equals(0)) {
List<BonusComputeUserVO> detail = CollStreamUtil.toList(request.getDistributeConfigs(), v -> new BonusComputeUserVO(BigDecimal.ZERO, v.getRewardUserId(), v.getRewardUserName()));
log.info("日营业天数为0");
return new BonusReceivedComputeVO(BigDecimal.ZERO, BigDecimal.ZERO, detail);
}
BigDecimal businessDays = BigDecimal.valueOf(request.getBusinessDays());
BigDecimal receivedDaily = request.getReceivedMonthly().divide(businessDays, 2, RoundingMode.HALF_UP);
if (receivedDaily.compareTo(request.getReceivedStart()) <= 0) {
List<BonusComputeUserVO> detail = CollStreamUtil.toList(request.getDistributeConfigs(), v -> new BonusComputeUserVO(BigDecimal.ZERO, v.getRewardUserId(), v.getRewardUserName()));
log.info("日均实收小于等于该值起始值");
return new BonusReceivedComputeVO(receivedDaily, BigDecimal.ZERO, detail);
}
BigDecimal amountTotal = BigDecimal.ZERO;
for (BonusRuleReceivedConfig config : request.getReceivedConfigs()) {
BigDecimal lowerBound = config.getGt();
BigDecimal upperBound = config.getLe();
if (lowerBound.compareTo(upperBound) >= 0) {
throw new ServiceException(ErrorCodeEnum.BONUS_RULE_CONFIG_ERROR);
}
if (receivedDaily.compareTo(lowerBound) > 0) {
BigDecimal intervalAmount;
if (upperBound == null || receivedDaily.compareTo(upperBound) <= 0) {
intervalAmount = receivedDaily.subtract(lowerBound);
} else {
intervalAmount = upperBound.subtract(lowerBound);
}
// 计算该区间的奖金:区间金额 × 奖励比例% × 月度营业天数
BigDecimal intervalBonus = intervalAmount.multiply(config.getRewardRatio())
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP)
.multiply(businessDays);
amountTotal = amountTotal.add(intervalBonus);
}
}
BigDecimal finalAmountTotal = amountTotal;
List<BonusComputeUserVO> detail = CollStreamUtil.toList(request.getDistributeConfigs(), d -> {
BigDecimal userAmount = finalAmountTotal.multiply(d.getRewardRatio())
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
return new BonusComputeUserVO(userAmount, d.getRewardUserId(), d.getRewardUserName());
});
return new BonusReceivedComputeVO(receivedDaily, amountTotal, detail);
}
@Override
public BonusProductComputeVO productCompute(BonusProductComputeRequest request) {
if (CollectionUtils.isEmpty(request.getRecipeSales())
|| CollectionUtils.isEmpty(request.getProductConfigs())
|| CollectionUtils.isEmpty(request.getDistributeConfigs())) {
log.info("参数为空");
return null;
}
Map<String, Integer> recipeSaleMap = CollStreamUtil.toMap(request.getRecipeSales(), BonusProductRecipeCompute::getRecipeNo, BonusProductRecipeCompute::getSalesVolume);
Map<String, BigDecimal> employeeAmount = new HashMap<>();
List<BonusProductRecipeComputeVO> resultRecipes = new ArrayList<>();
for (BonusRuleProductConfig productConfig : request.getProductConfigs()) {
BonusProductRecipeComputeVO recipeVO = BeanUtil.toBean(productConfig, BonusProductRecipeComputeVO.class);
Integer salesVolume = recipeSaleMap.getOrDefault(productConfig.getRecipeNo(), 0);
recipeVO.setSalesVolume(salesVolume);
BigDecimal salesVolumeBig = BigDecimal.valueOf(salesVolume).setScale(2, RoundingMode.HALF_UP);
BigDecimal basicRewardAmount = salesVolume.compareTo(productConfig.getMinStandardNum()) < 0
? BigDecimal.ZERO
: salesVolumeBig.multiply(productConfig.getRewardAmount());
BigDecimal excessRewardAmount = salesVolume.compareTo(productConfig.getExcessStandardNum()) < 0
? BigDecimal.ZERO
: salesVolumeBig.subtract(BigDecimal.valueOf(productConfig.getExcessStandardNum()))
.multiply(productConfig.getExcessAmount());
recipeVO.setBasicRewardAmount(basicRewardAmount);
recipeVO.setExcessRewardAmount(excessRewardAmount);
List<BonusComputeUserVO> detail = CollStreamUtil.toList(request.getDistributeConfigs(), d -> {
BigDecimal basic = basicRewardAmount.multiply(d.getRewardRatio())
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
BigDecimal excess = excessRewardAmount.multiply(d.getRewardRatio())
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
BigDecimal rewardAmount = basic.add(excess);
employeeAmount.compute(d.getRewardUserId(), (k, v) -> {
if (v == null) {
v = BigDecimal.ZERO;
}
return v.add(rewardAmount);
});
return new BonusComputeUserVO(rewardAmount, d.getRewardUserId(), d.getRewardUserName(), basic, excess, d.getRewardRatio());
});
recipeVO.setDetail(detail);
resultRecipes.add(recipeVO);
}
Map<String, String> userMap = CollStreamUtil.toMap(request.getDistributeConfigs(), BonusRuleDistributeConfig::getRewardUserId, BonusRuleDistributeConfig::getRewardUserName);
List<BonusComputeUserVO> detail = CollStreamUtil.toList(employeeAmount.entrySet(), v -> new BonusComputeUserVO(v.getValue(), v.getKey(), userMap.get(v.getKey())));
return new BonusProductComputeVO(resultRecipes, detail);
}
@Override
public void processReceivedRule(BonusDistributionRuleDO rule, String payMonth) {
String storeId = rule.getStoreId();
// 检查是否已统计过
if (receivedStoreDAO.existsByRuleIdAndPayDate(rule.getId(), payMonth)) {
log.info("规则已统计过, ruleId: {}, payMonth: {}", rule.getId(), payMonth);
return;
}
// 获取门店编号
StoreDO store = storeDao.getEffectiveByStoreId(storeId);
if (store == null) {
log.warn("门店不存在, storeId: {}", storeId);
return;
}
String storeNum = store.getStoreNum();
// 解析规则配置
JSONObject bonusConfig = JSONObject.parseObject(rule.getBonusConfig());
BigDecimal receivedStart = bonusConfig.getBigDecimal("receivedStart");
List<BonusRuleReceivedConfig> receivedConfigs = bonusConfig.getJSONArray("receivedConfigs")
.toJavaList(BonusRuleReceivedConfig.class);
List<BonusRuleDistributeConfig> distributeConfigs = JSONObject.parseArray(rule.getDistributeConfig(), BonusRuleDistributeConfig.class);
// 调用API获取门店月营收数据
StoreMonthRevenueRequest request = new StoreMonthRevenueRequest();
request.setStoreNum(storeNum);
request.setStartDate(payMonth);
request.setEndDate(payMonth);
List<StoreMonthRevenueResponse> revenueList = storeOpenDataService.getMonthRevenue(request);
if (org.apache.commons.collections4.CollectionUtils.isEmpty(revenueList)) {
log.warn("门店月营收数据为空, storeNum: {}", storeNum);
return;
}
StoreMonthRevenueResponse revenue = revenueList.get(0);
// 计算奖金
BonusReceivedComputeRequest computeRequest = new BonusReceivedComputeRequest();
computeRequest.setReceivedMonthly(revenue.getReceivedAmount());
computeRequest.setBusinessDays(revenue.getBusinessDays());
computeRequest.setReceivedStart(receivedStart);
computeRequest.setReceivedConfigs(receivedConfigs);
computeRequest.setDistributeConfigs(distributeConfigs);
BonusReceivedComputeVO computeVO = receivedCompute(computeRequest);
// 处理门店实收数据
saveReceivedData(rule, payMonth, revenue, computeVO, distributeConfigs);
// 处理员工奖金明细
processEmployeeDetail(payMonth);
}
@Override
public void processNewProductRule(BonusDistributionRuleDO rule, String payDate) {
String storeId = rule.getStoreId();
// 检查是否已统计过
if (newProductStoreDAO.existsByRuleIdAndPayDate(rule.getId(), payDate)) {
log.info("规则已统计过, ruleId: {}, payDate: {}", rule.getId(), payDate);
return;
}
// 获取门店编号
StoreDO store = storeDao.getEffectiveByStoreId(storeId);
if (store == null) {
log.warn("门店不存在, storeId: {}", storeId);
return;
}
String storeNum = store.getStoreNum();
// 解析规则配置
JSONObject bonusConfig = JSONObject.parseObject(rule.getBonusConfig());
List<BonusRuleProductConfig> productConfigs = bonusConfig.getJSONArray("productConfigs")
.toJavaList(BonusRuleProductConfig.class);
List<BonusRuleDistributeConfig> distributeConfigs = JSONObject.parseArray(rule.getDistributeConfig(), BonusRuleDistributeConfig.class);
// 调用API获取门店菜品日销量数据
StoreRecipeDailySalesRequest request = new StoreRecipeDailySalesRequest();
request.setStoreNum(storeNum);
request.setStartDate(payDate);
request.setEndDate(payDate);
List<StoreRecipeDailySalesResponse> salesList = storeOpenDataService.getRecipeDailySales(request);
if (CollectionUtils.isEmpty(salesList)) {
log.warn("门店菜品日销量数据为空, storeNum: {}", storeNum);
return;
}
// 构建销量请求
List<BonusProductRecipeCompute> recipeSales = CollStreamUtil.toList(salesList, v -> new BonusProductRecipeCompute(v.getRecipeNo(), v.getSales()));
// 计算奖金
BonusProductComputeRequest computeRequest = new BonusProductComputeRequest();
computeRequest.setRecipeSales(recipeSales);
computeRequest.setProductConfigs(productConfigs);
computeRequest.setDistributeConfigs(distributeConfigs);
BonusProductComputeVO computeVO = productCompute(computeRequest);
// 入库
saveNewProductData(rule, payDate, computeVO, productConfigs, distributeConfigs);
}
@Override
public void processEmployeeDetail(String payMonth) {
// 查询门店员工实收数据
List<BonusReceivedEmployeeDO> receiveList = receivedEmployeeDAO.getMonthlyStatisticsGroupByStore(payMonth);
// 查询门店员工新品销售数据
List<BonusNewProductEmployeeDO> productList = newProductEmployeeDAO.getMonthlyStatisticsGroupByStore(payMonth);
// 将实收数据转为 Map<storeId, Map<rewardUserId, BonusReceivedEmployeeDO>>
Map<String, Map<String, BonusReceivedEmployeeDO>> receiveMap = receiveList.stream()
.collect(Collectors.groupingBy(
BonusReceivedEmployeeDO::getStoreId,
Collectors.toMap(
BonusReceivedEmployeeDO::getRewardUserId,
v -> v,
(v1, v2) -> v1
)
));
// 将新品数据转为 Map<storeId, Map<rewardUserId, BonusNewProductEmployeeDO>>
Map<String, Map<String, BonusNewProductEmployeeDO>> productMap = productList.stream()
.collect(Collectors.groupingBy(
BonusNewProductEmployeeDO::getStoreId,
Collectors.toMap(
BonusNewProductEmployeeDO::getRewardUserId,
v -> v,
(v1, v2) -> v1
)
));
Set<Pair<String, String>> allStoreUser = new HashSet<>();
allStoreUser.addAll(CollStreamUtil.toList(receiveList, v -> Pair.of(v.getStoreId(), v.getRewardUserId())));
allStoreUser.addAll(CollStreamUtil.toList(productList, v -> Pair.of(v.getStoreId(), v.getRewardUserId())));
Date payDate = DateUtils.strToDate(payMonth, "yyyy-MM");
List<BonusEmployeeRewardDetailDO> detailList = CollStreamUtil.toList(allStoreUser, v -> {
String storeId = v.getKey();
String rewardUserId = v.getValue();
BonusEmployeeRewardDetailDO detailDO = new BonusEmployeeRewardDetailDO();
detailDO.setStoreId(storeId);
detailDO.setPayDate(payDate);
detailDO.setRewardUserId(rewardUserId);
BonusReceivedEmployeeDO receive = receiveMap.getOrDefault(storeId, Collections.emptyMap()).get(rewardUserId);
BonusNewProductEmployeeDO product = productMap.getOrDefault(storeId, Collections.emptyMap()).get(rewardUserId);
detailDO.setReceivedAmount(Objects.nonNull(receive) ? receive.getReceivedAmount() : BigDecimal.ZERO);
detailDO.setNewProjectAmount(Objects.nonNull(product) ? product.getRewardAmount() : BigDecimal.ZERO);
if (Objects.nonNull(receive)) {
detailDO.setRewardUserName(receive.getRewardUserName());
}
if (Objects.nonNull(product)) {
detailDO.setRewardUserName(product.getRewardUserName());
}
return detailDO;
});
if (CollectionUtils.isNotEmpty(detailList)) {
rewardDetailDAO.insertOrUpdateBatch(detailList);
}
}
/**
* 入库实收奖金数据
*/
private void saveReceivedData(BonusDistributionRuleDO rule, String payMonth,
StoreMonthRevenueResponse revenue, BonusReceivedComputeVO computeVO,
List<BonusRuleDistributeConfig> distributeConfigs) {
Date payDate = DateUtils.strToDate(payMonth, "yyyy-MM");
// 实收门店数据
BonusReceivedStoreDO receivedStoreDO = new BonusReceivedStoreDO();
receivedStoreDO.setRuleId(rule.getId());
receivedStoreDO.setStoreId(rule.getStoreId());
receivedStoreDO.setPayDate(payDate);
receivedStoreDO.setReceivedMonthly(revenue.getReceivedAmount());
receivedStoreDO.setBusinessDays(revenue.getBusinessDays());
receivedStoreDO.setReceivedDaily(revenue.getReceivedAmountDaily());
receivedStoreDO.setReceivedDailyLast(revenue.getReceivedAmountDailyLastMonth());
receivedStoreDO.setAmountTotal(computeVO.getAmountTotal());
receivedStoreDAO.insert(receivedStoreDO);
// 实收门店员工数据
List<BonusReceivedEmployeeDO> employeeList = new ArrayList<>();
for (BonusComputeUserVO userVO : computeVO.getDetail()) {
BonusReceivedEmployeeDO employeeDO = new BonusReceivedEmployeeDO();
employeeDO.setReceivedStoreId(receivedStoreDO.getId());
employeeDO.setRuleId(rule.getId());
employeeDO.setStoreId(rule.getStoreId());
employeeDO.setPayDate(payDate);
// 查找分配比例
BonusRuleDistributeConfig config = findDistributeConfigByName(distributeConfigs, userVO.getRewardUserId());
if (config != null) {
employeeDO.setRewardRatio(config.getRewardRatio());
employeeDO.setRewardUserId(config.getRewardUserId());
}
employeeDO.setReceivedAmount(userVO.getRewardAmount());
employeeDO.setRewardUserName(userVO.getRewardUserName());
employeeDO.setRewardUserId(userVO.getRewardUserId());
employeeList.add(employeeDO);
}
receivedEmployeeDAO.insertBatch(employeeList);
}
private BonusRuleDistributeConfig findDistributeConfigByName(List<BonusRuleDistributeConfig> distributeConfigs, String userId) {
if (org.apache.commons.collections4.CollectionUtils.isEmpty(distributeConfigs) || userId == null) {
return null;
}
return distributeConfigs.stream()
.filter(c -> c.getRewardUserId().equals(userId))
.findFirst()
.orElse(null);
}
/**
* 处理新品销售奖金数据
*/
private void saveNewProductData(BonusDistributionRuleDO rule, String payDate,
BonusProductComputeVO computeVO,
List<BonusRuleProductConfig> productConfigs,
List<BonusRuleDistributeConfig> distributeConfigs) {
Date payDateObj = DateUtils.strToDate(payDate, "yyyy-MM-dd");
// 计算总金额
BigDecimal amountTotal = computeVO.getDetail().stream()
.map(BonusComputeUserVO::getRewardAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
// 1. 新品销售奖金-门店
BonusNewProductStoreDO productStoreDO = new BonusNewProductStoreDO();
productStoreDO.setRuleId(rule.getId());
productStoreDO.setStoreId(rule.getStoreId());
productStoreDO.setPayDate(payDateObj);
productStoreDO.setAmountTotal(amountTotal);
newProductStoreDAO.insert(productStoreDO);
// 2. 入库新品销售奖金-员工
List<BonusNewProductEmployeeDO> employeeList = new ArrayList<>();
for (BonusComputeUserVO userVO : computeVO.getDetail()) {
BonusNewProductEmployeeDO employeeDO = new BonusNewProductEmployeeDO();
employeeDO.setProductStoreId(productStoreDO.getId());
employeeDO.setRuleId(rule.getId());
employeeDO.setStoreId(rule.getStoreId());
employeeDO.setPayDate(payDateObj);
BonusRuleDistributeConfig config = findDistributeConfigByName(distributeConfigs, userVO.getRewardUserId());
if (config != null) {
employeeDO.setRewardRatio(config.getRewardRatio());
employeeDO.setRewardUserId(config.getRewardUserId());
}
employeeDO.setRewardAmount(userVO.getRewardAmount());
employeeDO.setRewardUserName(userVO.getRewardUserName());
employeeList.add(employeeDO);
}
newProductEmployeeDAO.insertBatch(employeeList);
// 3. 新品销售奖金-菜品
List<BonusNewProductRecipeDO> recipeList = new ArrayList<>();
for (BonusProductRecipeComputeVO recipeVO : computeVO.getRecipes()) {
BonusNewProductRecipeDO recipeDO = new BonusNewProductRecipeDO();
recipeDO.setRuleId(rule.getId());
recipeDO.setStoreId(rule.getStoreId());
recipeDO.setPayDate(payDateObj);
recipeDO.setRecipeNo(recipeVO.getRecipeNo());
recipeDO.setRecipeName(recipeVO.getRecipeName());
// 查找对应的配置
BonusRuleProductConfig config = productConfigs.stream()
.filter(c -> c.getRecipeNo().equals(recipeVO.getRecipeNo()))
.findFirst().orElse(null);
if (config != null) {
recipeDO.setMinStandardNum(config.getMinStandardNum());
recipeDO.setRewardAmount(config.getRewardAmount());
recipeDO.setExcessStandardNum(config.getExcessStandardNum());
recipeDO.setExcessAmount(config.getExcessAmount());
}
recipeDO.setSalesVolume(recipeVO.getSalesVolume());
recipeDO.setBasicRewardAmount(recipeVO.getBasicRewardAmount());
recipeDO.setExcessRewardAmount(recipeVO.getExcessRewardAmount());
recipeList.add(recipeDO);
}
newProductRecipeDAO.insertBatch(recipeList);
// 4. 新品销售奖金-菜品-员工
List<BonusNewProductRecipeEmployeeDO> recipeEmployeeList = new ArrayList<>();
for (BonusProductRecipeComputeVO recipeVO : computeVO.getRecipes()) {
BonusNewProductRecipeDO savedRecipe = recipeList.stream()
.filter(r -> r.getRecipeNo().equals(recipeVO.getRecipeNo()))
.findFirst().orElse(null);
if (savedRecipe != null && CollectionUtils.isNotEmpty(recipeVO.getDetail())) {
for (BonusComputeUserVO userVO : recipeVO.getDetail()) {
BonusNewProductRecipeEmployeeDO recipeEmployeeDO = new BonusNewProductRecipeEmployeeDO();
recipeEmployeeDO.setProductRecipeId(savedRecipe.getId());
recipeEmployeeDO.setRuleId(rule.getId());
recipeEmployeeDO.setStoreId(rule.getStoreId());
recipeEmployeeDO.setPayDate(payDateObj);
recipeEmployeeDO.setRecipeNo(recipeVO.getRecipeNo());
recipeEmployeeDO.setRecipeName(recipeVO.getRecipeName());
recipeEmployeeDO.setSalesVolume(recipeVO.getSalesVolume());
// 计算基础和超额奖金
BigDecimal basicReward = userVO.getBasicRewardAmount();
BigDecimal excessReward = userVO.getExcessRewardAmount();
recipeEmployeeDO.setBasicRewardAmount(basicReward != null ? basicReward : BigDecimal.ZERO);
recipeEmployeeDO.setExcessRewardAmount(excessReward != null ? excessReward : BigDecimal.ZERO);
recipeEmployeeDO.setRewardRatio(userVO.getRewardRatio());
recipeEmployeeDO.setRewardUserName(userVO.getRewardUserName());
recipeEmployeeDO.setRewardUserId(userVO.getRewardUserId());
recipeEmployeeList.add(recipeEmployeeDO);
}
}
}
newProductRecipeEmployeeDAO.insertBatch(recipeEmployeeList);
}
@Override
public PageInfo<BonusReceivedStoreListVO> getReceivedStoreList(BonusReceivedStoreQueryRequest request) {
PageHelper.startPage(request.getPageNum(), request.getPageSize());
List<BonusReceivedStoreDO> list = receivedStoreDAO.selectListByCondition(
request.getStoreNumOrName(), request.getStartMonth(), request.getEndMonth(), request.getStoreId());
PageInfo<BonusReceivedStoreListVO> page = BeanUtil.toPage(new PageInfo<>(list), BonusReceivedStoreListVO.class);
fillBasicField(page.getList());
return page;
}
@Override
public BonusReceivedStoreDetailVO getReceivedStoreDetail(Long id) {
BonusReceivedStoreDO storeDO = receivedStoreDAO.getById(id);
if (Objects.isNull(storeDO)) {
return null;
}
BonusReceivedStoreDetailVO vo = BeanUtil.toBean(storeDO, BonusReceivedStoreDetailVO.class);
// 获取门店信息
StoreDO store = storeDao.getEffectiveByStoreId(storeDO.getStoreId());
if (Objects.nonNull(store)) {
vo.setStoreNum(store.getStoreNum());
vo.setStoreName(store.getStoreName());
}
// 获取奖金规则配置
BonusDistributionRuleDO ruleDO = ruleDAO.getById(storeDO.getRuleId());
if (Objects.nonNull(ruleDO)) {
JSONObject bonusConfig = JSONObject.parseObject(ruleDO.getBonusConfig());
BeanUtil.copyPropertiesIgnoreId(bonusConfig, vo);
}
// 获取员工本月奖金发放列表
List<BonusReceivedEmployeeDO> employeeList = receivedEmployeeDAO.selectByReceivedStoreId(id);
List<EmployeeBonusVO> employeeVOList = CollStreamUtil.toList(employeeList, e ->
new EmployeeBonusVO(e.getRewardUserId(), e.getRewardUserName(), e.getRewardRatio(), e.getReceivedAmount()));
vo.setEmployeeList(employeeVOList);
return vo;
}
@Override
public PageInfo<BonusReceivedEmployeeListVO> getReceivedEmployeeList(BonusReceivedEmployeeQueryRequest request) {
PageHelper.startPage(request.getPageNum(), request.getPageSize());
List<BonusReceivedEmployeeDO> list = receivedEmployeeDAO.selectEmployeeListByCondition(
request.getStoreNumOrName(), request.getStartMonth(), request.getEndMonth(), request.getRewardUserName(), request.getStoreId());
PageInfo<BonusReceivedEmployeeListVO> page = BeanUtil.toPage(new PageInfo<>(list), BonusReceivedEmployeeListVO.class);
if (CollectionUtils.isEmpty(list)) {
return page;
}
// 获取门店实收信息
Set<Long> receivedStoreIds = CollStreamUtil.toSet(list, BonusReceivedEmployeeDO::getReceivedStoreId);
Map<Long, BonusReceivedStoreDO> storeDOMap = receivedStoreDAO.getMapByIds(new ArrayList<>(receivedStoreIds));
// 获取门店信息
fillBasicField(page.getList());
page.getList().forEach(v -> {
// 填充门店实收信息
BonusReceivedStoreDO receivedStoreDO = storeDOMap.get(v.getReceivedStoreId());
if (Objects.nonNull(receivedStoreDO)) {
v.setReceivedMonthly(receivedStoreDO.getReceivedMonthly());
v.setBusinessDays(receivedStoreDO.getBusinessDays());
v.setReceivedDaily(receivedStoreDO.getReceivedDaily());
v.setReceivedDailyLast(receivedStoreDO.getReceivedDailyLast());
v.setAmountTotal(receivedStoreDO.getAmountTotal());
}
});
return page;
}
@Override
public BonusReceivedStoreDetailVO getReceivedEmployeeDetail(Long id) {
BonusReceivedEmployeeDO employeeDO = receivedEmployeeDAO.getById(id);
if (Objects.isNull(employeeDO)) {
return null;
}
BonusReceivedStoreDO storeDO = receivedStoreDAO.getById(employeeDO.getReceivedStoreId());
if (Objects.isNull(storeDO)) {
return null;
}
BonusReceivedStoreDetailVO vo = BeanUtil.toBean(storeDO, BonusReceivedStoreDetailVO.class);
// 获取门店信息
StoreDO store = storeDao.getEffectiveByStoreId(storeDO.getStoreId());
if (Objects.nonNull(store)) {
vo.setStoreNum(store.getStoreNum());
vo.setStoreName(store.getStoreName());
}
// 获取奖金规则配置
BonusDistributionRuleDO ruleDO = ruleDAO.getById(storeDO.getRuleId());
if (Objects.nonNull(ruleDO)) {
JSONObject bonusConfig = JSONObject.parseObject(ruleDO.getBonusConfig());
BeanUtil.copyPropertiesIgnoreId(bonusConfig, vo);
}
// 获取员工本月奖金发放列表
EmployeeBonusVO employeeVO = new EmployeeBonusVO(employeeDO.getRewardUserId(), employeeDO.getRewardUserName(), employeeDO.getRewardRatio(), employeeDO.getReceivedAmount());
vo.setEmployeeList(Collections.singletonList(employeeVO));
return vo;
}
@Override
public PageInfo<BonusProductStoreListVO> getNewProductStoreList(BonusProductQueryRequest request) {
PageHelper.startPage(request.getPageNum(), request.getPageSize());
List<BonusNewProductStoreDO> list = newProductStoreDAO.monthlyStatistics(request.getStoreNumOrName(), request.getStartDate(), request.getEndDate(), null, null);
PageInfo<BonusProductStoreListVO> page = BeanUtil.toPage(new PageInfo<>(list), BonusProductStoreListVO.class);
fillBasicField(page.getList());
return page;
}
@Override
public BonusProductStoreDetailVO getNewProductStoreDetail(BonusProductMonthlyQueryRequest request) {
if (Objects.isNull(request.getRuleId())) {
throw new ServiceException(ErrorCodeEnum.ERROR_MESSAGE, "规则id不能为空");
}
List<BonusNewProductStoreDO> list = newProductStoreDAO.monthlyStatistics(null, request.getPayDate(), request.getPayDate(), request.getStoreId(), request.getRuleId());
if (CollectionUtils.isEmpty(list)) {
return null;
}
BonusNewProductStoreDO productStoreDO = list.get(0);
BonusProductStoreDetailVO vo = new BonusProductStoreDetailVO();
BonusDistributionRuleDO ruleDO = ruleDAO.getById(productStoreDO.getRuleId());
if (Objects.nonNull(ruleDO)) {
JSONObject bonusConfig = JSONObject.parseObject(ruleDO.getBonusConfig());
BeanUtil.copyPropertiesIgnoreId(bonusConfig, vo);
}
vo.setPayDate(productStoreDO.getPayDate());
// 获取门店信息
StoreDO store = storeDao.getEffectiveByStoreId(productStoreDO.getStoreId());
if (Objects.nonNull(store)) {
vo.setStoreNum(store.getStoreNum());
vo.setStoreName(store.getStoreName());
}
// 获取发放明细列表
List<BonusNewProductEmployeeDO> employeeList = newProductEmployeeDAO.monthlyStatistics(request.getRuleId(), request.getStoreId(), request.getPayDate(), null);
List<EmployeeBonusVO> employeeVOList = CollStreamUtil.toList(employeeList, e ->
new EmployeeBonusVO(e.getRewardUserId(), e.getRewardUserName(), null, e.getRewardAmount()));
vo.setEmployeeList(employeeVOList);
return vo;
}
@Override
public PageInfo<BonusProductRecipeListVO> getNewProductRecipeList(BonusProductQueryRequest request) {
PageHelper.startPage(request.getPageNum(), request.getPageSize());
List<BonusNewProductRecipeDO> list = newProductRecipeDAO.monthlyStatistics(request.getStoreNumOrName(), request.getStartDate(), request.getEndDate(), request.getRecipeNoOrName());
PageInfo<BonusProductRecipeListVO> page = BeanUtil.toPage(new PageInfo<>(list), BonusProductRecipeListVO.class);
fillBasicField(page.getList());
return page;
}
@Override
public PageInfo<BonusProductRecipeDetailVO> getNewProductRecipeDetail(BonusProductRecipeQueryRequest request) {
PageHelper.startPage(request.getPageNum(), request.getPageSize());
List<BonusNewProductRecipeDO> list = newProductRecipeDAO.selectListByCondition(request.getStoreId(), request.getRecipeNo(), request.getStartDate(), request.getEndDate());
PageInfo<BonusProductRecipeDetailVO> page = BeanUtil.toPage(new PageInfo<>(list), BonusProductRecipeDetailVO.class);
fillBasicField(page.getList());
return page;
}
@Override
public PageInfo<BonusProductEmployeeListVO> getNewProductEmployeeList(BonusProductEmployeeQueryRequest request) {
PageHelper.startPage(request.getPageNum(), request.getPageSize());
List<BonusNewProductEmployeeDO> list = newProductEmployeeDAO.employeeMonthlyStatistics(request.getStartDate(),
request.getEndDate(), request.getStoreNumOrName(), request.getRewardUserName(), request.getStoreId());
List<BonusProductEmployeeListVO> result = CollStreamUtil.toList(list, v -> {
BonusProductEmployeeListVO vo = BeanUtil.toBean(v, BonusProductEmployeeListVO.class);
vo.setReceivedAmount(v.getRewardAmount());
return vo;
});
PageInfo<BonusProductEmployeeListVO> page = BeanUtil.toPage(new PageInfo<>(list), result);
fillBasicField(page.getList());
return page;
}
@Override
public BonusProductStoreDetailVO getNewProductEmployeeDetail(BonusProductMonthlyQueryRequest request) {
if (Objects.isNull(request.getRewardUserId())) {
throw new ServiceException(ErrorCodeEnum.ERROR_MESSAGE, "奖励人id不能为空");
}
List<BonusNewProductEmployeeDO> list = newProductEmployeeDAO.monthlyStatistics(null, request.getStoreId(), request.getPayDate(), request.getRewardUserId());
if (CollectionUtils.isEmpty(list)) {
return null;
}
BonusNewProductEmployeeDO employeeDO = list.get(0);
BonusProductStoreDetailVO vo = new BonusProductStoreDetailVO();
vo.setPayDate(request.getPayDate());
BonusDistributionRuleDO ruleDO = ruleDAO.getById(employeeDO.getRuleId());
if (Objects.nonNull(ruleDO)) {
JSONObject bonusConfig = JSONObject.parseObject(ruleDO.getBonusConfig());
BeanUtil.copyPropertiesIgnoreId(bonusConfig, vo);
}
vo.setPayDate(employeeDO.getPayDate());
// 获取门店信息
StoreDO store = storeDao.getEffectiveByStoreId(request.getStoreId());
if (Objects.nonNull(store)) {
vo.setStoreNum(store.getStoreNum());
vo.setStoreName(store.getStoreName());
}
List<EmployeeBonusVO> employeeVOList = Collections.singletonList(new EmployeeBonusVO(employeeDO.getRewardUserId(), employeeDO.getRewardUserName(), null, employeeDO.getRewardAmount()));
vo.setEmployeeList(employeeVOList);
return vo;
}
@Override
public PageInfo<BonusProductRecipeEmployeeListVO> getNewProductRecipeEmployeeList(BonusProductEmployeeQueryRequest request) {
PageHelper.startPage(request.getPageNum(), request.getPageSize());
List<BonusNewProductRecipeEmployeeDO> list = newProductRecipeEmployeeDAO.monthlyStatistics(request.getStoreNumOrName(), request.getStartDate(), request.getEndDate(), request.getRewardUserName(), request.getRecipeNoOrName());
PageInfo<BonusProductRecipeEmployeeListVO> page = BeanUtil.toPage(new PageInfo<>(list), BonusProductRecipeEmployeeListVO.class);
fillBasicField(page.getList());
return page;
}
@Override
public PageInfo<BonusProductRecipeEmployeeDetailVO> getNewProductRecipeEmployeeDetail(BonusProductRecipeEmployeeQueryRequest request) {
PageHelper.startPage(request.getPageNum(), request.getPageSize());
List<BonusNewProductRecipeEmployeeDO> list = newProductRecipeEmployeeDAO.selectListByCondition(request.getStoreId(), request.getRecipeNo(), request.getRewardUserId(), request.getStartDate(), request.getEndDate());
Set<Long> productRecipeIds = CollStreamUtil.toSet(list, BonusNewProductRecipeEmployeeDO::getProductRecipeId);
Map<Long, BonusNewProductRecipeDO> productRecipeMap = newProductRecipeDAO.getMapByIds(new ArrayList<>(productRecipeIds));
List<BonusProductRecipeEmployeeDetailVO> result = CollStreamUtil.toList(list, v -> {
BonusProductRecipeEmployeeDetailVO vo = BeanUtil.toBean(v, BonusProductRecipeEmployeeDetailVO.class);
BonusNewProductRecipeDO recipeDO = productRecipeMap.get(v.getProductRecipeId());
if (Objects.nonNull(recipeDO)) {
vo.setMinStandardNum(recipeDO.getMinStandardNum());
vo.setRewardAmount(recipeDO.getRewardAmount());
vo.setExcessStandardNum(recipeDO.getExcessStandardNum());
vo.setExcessAmount(recipeDO.getExcessAmount());
}
return vo;
});
PageInfo<BonusProductRecipeEmployeeDetailVO> page = BeanUtil.toPage(new PageInfo<>(list), result);
fillBasicField(page.getList());
return page;
}
@Override
public PageInfo<BonusEmployeeDetailVO> getEmployeeMonthlyDetail(BonusProductEmployeeQueryRequest request) {
PageHelper.startPage(request.getPageNum(), request.getPageSize());
List<BonusEmployeeRewardDetailDO> list = rewardDetailDAO.selectListByCondition(request.getStoreNumOrName(),
request.getStartDate(), request.getEndDate(), request.getRewardUserName(), request.getStoreId());
PageInfo<BonusEmployeeDetailVO> page = BeanUtil.toPage(new PageInfo<>(list), BonusEmployeeDetailVO.class);
fillBasicField(page.getList());
return page;
}
@Override
@Transactional
public void deleteMonthlyReceived(BonusDistributionRuleDO ruleDO, String payMonth) {
Date payDate = DateUtils.strToDate(payMonth, "yyyy-MM");
// 门店实收
receivedStoreDAO.deleteMonthlyReceived(ruleDO.getId(), ruleDO.getStoreId(), payDate);
// 员工
receivedEmployeeDAO.deleteMonthlyReceived(ruleDO.getId(), ruleDO.getStoreId(), payDate);
}
@Override
@Transactional
public void deleteMonthlyNewProduct(BonusDistributionRuleDO ruleDO, String payDate) {
Date payDateObj = DateUtils.strToDate(payDate, "yyyy-MM-dd");
newProductRecipeDAO.deleteDailyNewProduct(ruleDO.getId(), ruleDO.getStoreId(), payDateObj);
newProductRecipeEmployeeDAO.deleteDailyNewProduct(ruleDO.getId(), ruleDO.getStoreId(), payDateObj);
newProductEmployeeDAO.deleteDailyNewProduct(ruleDO.getId(), ruleDO.getStoreId(), payDateObj);
newProductStoreDAO.deleteDailyNewProduct(ruleDO.getId(), ruleDO.getStoreId(), payDateObj);
}
@Override
public List<StoreRecipesResponse> getRecipes(String storeId) {
StoreDO storeDO = storeDao.getEffectiveByStoreId(storeId);
if (Objects.isNull(storeDO)) {
throw new ServiceException(ErrorCodeEnum.STORE_NOT_FIND);
}
StoreRecipesRequest request = new StoreRecipesRequest();
request.setStoreNum(storeDO.getStoreNum());
return storeOpenDataService.getRecipes(request);
}
@Override
public BigDecimal receivedDailyLast(String storeId) {
StoreDO storeDO = storeDao.getEffectiveByStoreId(storeId);
if (Objects.isNull(storeDO)) {
throw new ServiceException(ErrorCodeEnum.STORE_NOT_FIND);
}
String lastMonth = LocalDate.now().minusMonths(1).format(DateTimeFormatter.ofPattern("yyyy-MM"));
StoreMonthRevenueRequest request = new StoreMonthRevenueRequest();
request.setStoreNum(storeDO.getStoreNum());
request.setStartDate(lastMonth);
request.setEndDate(lastMonth);
List<StoreMonthRevenueResponse> monthRevenue = storeOpenDataService.getMonthRevenue(request);
if (CollectionUtils.isNotEmpty(monthRevenue)) {
StoreMonthRevenueResponse storeMonthRevenueResponse = monthRevenue.get(0);
return Objects.nonNull(storeMonthRevenueResponse.getReceivedAmountDaily()) ? storeMonthRevenueResponse.getReceivedAmountDaily() : BigDecimal.ZERO;
}
return BigDecimal.ZERO;
}
private void fillBasicField(List<? extends BonusBasicVO> list) {
Set<String> storeIds = CollStreamUtil.toSet(list, BonusBasicVO::getStoreId);
Map<String, StoreDO> storeMap = storeDao.getStoreMapByStoreIds(new ArrayList<>(storeIds));
list.forEach(v -> {
StoreDO storeDO = storeMap.get(v.getStoreId());
if (Objects.nonNull(storeDO)) {
v.setStoreNum(storeDO.getStoreNum());
v.setStoreName(storeDO.getStoreName());
}
});
}
}

View File

@@ -353,6 +353,51 @@ public class StoreServiceImpl implements StoreService {
return Boolean.TRUE;
}
@Override
public List<StoreUserDTO> getStoreUserPositionList(String storeId, String userName) {
if (StrUtil.isBlank(storeId)) {
throw new ServiceException(ErrorCodeEnum.PARAMS_VALIDATE_ERROR.getCode(), "请选择门店后查询");
}
List<AuthStoreUserDTO> authStoreUserDTOList = this.authStoreUserV2(
Collections.singletonList(storeId), "store_inside");
if (CollectionUtils.isEmpty(authStoreUserDTOList)) {
return new ArrayList<>();
}
List<String> userIdList = ListUtils.emptyIfNull(authStoreUserDTOList)
.stream()
.map(AuthStoreUserDTO::getUserIdList)
.flatMap(Collection::stream)
.distinct()
.collect(Collectors.toList());
if (CollUtil.isEmpty(userIdList)) {
return new ArrayList<>();
}
List<StoreUserDTO> list = sysRoleMapper.userAndPositionList( userIdList, userName, "store_inside");
if (CollectionUtils.isNotEmpty(list)) {
//先去重
list = list.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() ->
new TreeSet<>(Comparator.comparing(StoreUserDTO::getUserId))), ArrayList::new));
}
Map<String, List<StoreUserDTO>> storeUserDTOMap = ListUtils.emptyIfNull(list).stream().collect(Collectors.groupingBy(k -> k.getUserId()));
List<StoreUserDTO> result = Lists.newArrayList();
for (String userId : storeUserDTOMap.keySet()) {
List<StoreUserDTO> singleUserDTOList = storeUserDTOMap.get(userId);
if (CollectionUtils.isEmpty(singleUserDTOList)) {
continue;
}
StoreUserDTO storeUserDTO = singleUserDTOList.get(0);
List<String> positionNameList = ListUtils.emptyIfNull(singleUserDTOList)
.stream().map(StoreUserDTO::getPositionName).collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(positionNameList)) {
storeUserDTO.setPositionName(String.join(Constants.COMMA, positionNameList));
}
result.add(storeUserDTO);
}
return result;
}
@Override
public List<AuthStoreUserDTO> authStoreUser(List<String> storeIdList, String positionType) {
@@ -402,6 +447,55 @@ public class StoreServiceImpl implements StoreService {
.map(data -> mapAuthStoreUserDTO(regionUserAuthMappingList, allStoreUserIdList, storeUserAuthMappingList, notIncludeRegionUserAuthMappingList, data))
.collect(Collectors.toList());
}
@Override
public List<AuthStoreUserDTO> authStoreUserV2(List<String> storeIdList, String positionType) {
List<AuthStoreUserDTO> result = new ArrayList<>();
if (CollectionUtils.isEmpty(storeIdList)) {
return result;
}
//将拥有管理员角色、角色属性为全企业数据的人查询出来
List<SysRoleVO> roleUserByRoleId = sysRoleMapper.getRoleUserByRoleEnum(Role.MASTER.getRoleEnum(),positionType);
List<SysRoleVO> roleUserByRoleAuth = sysRoleMapper.getRoleUserByRoleAuth(AuthRoleEnum.ALL.getCode(),positionType);
//组合出拥有所有门店信息的人
List<String> allStoreUserIdList = getAllStoreUserIdList(roleUserByRoleId, roleUserByRoleAuth);
//查询出有门店权限配置的的人员
// 1.将门店区域切分出门店所属于的区域ID
// 2.将配置了区域的人 查询出来
// 3.将配置了门店的人 查询出来
List<StoreAreaDTO> storeAreaList = storeMapper.getStoreAreaList(storeIdList);
if (CollectionUtils.isEmpty(storeAreaList)) {
return Collections.emptyList();
}
List<String> fullAreaIdList = ListUtils.emptyIfNull(storeAreaList)
.stream()
.map(StoreAreaDTO::getAreaIdList)
.flatMap(Collection::stream)
.distinct()
.collect(Collectors.toList());
List<String> lastAreaIdList = ListUtils.emptyIfNull(storeAreaList)
.stream()
.map(StoreAreaDTO::getAreaId)
.distinct()
.collect(Collectors.toList());
//除不包含子区域的可视化范围的区域配置。
List<UserAuthMappingDO> regionUserAuthMappingList = userAuthMappingMapper.listUserAuthMappingByAuthV2(
UserAuthMappingTypeEnum.REGION.getCode(), fullAreaIdList, positionType, AuthRoleEnum.NOT_INCLUDE_SUBORDINATE.getCode());
//不包含子区域的的直属连接门店的区域下的配置(会重复一些选择了上面数据)
List<UserAuthMappingDO> notIncludeRegionUserAuthMappingList = userAuthMappingMapper.listUserAuthMappingByAuthV2(
UserAuthMappingTypeEnum.REGION.getCode(), lastAreaIdList, positionType, null);
//配置了门店的的配置
List<UserAuthMappingDO> storeUserAuthMappingList = userAuthMappingMapper.listUserAuthMappingByMappingList(
storeIdList, UserAuthMappingTypeEnum.STORE.getCode());
return ListUtils.emptyIfNull(storeAreaList)
.stream()
.map(data -> mapAuthStoreUserDTO(regionUserAuthMappingList, allStoreUserIdList, storeUserAuthMappingList, notIncludeRegionUserAuthMappingList, data))
.collect(Collectors.toList());
}
private List<String> getAllStoreUserIdList(List<SysRoleVO> roleUserByRoleId, List<SysRoleVO> roleUserByRoleAuth) {
List<String> allUserIdList= new ArrayList<>();

View File

@@ -0,0 +1,172 @@
package com.cool.store.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.cool.store.enums.ErrorCodeEnum;
import com.cool.store.exception.ServiceException;
import com.cool.store.request.storeopen.StoreMonthRevenueRequest;
import com.cool.store.request.storeopen.StoreRecipeDailySalesRequest;
import com.cool.store.request.storeopen.StoreRecipesRequest;
import com.cool.store.response.storeopen.StoreMonthRevenueResponse;
import com.cool.store.response.storeopen.StoreOpenApiResponse;
import com.cool.store.response.storeopen.StoreRecipeDailySalesResponse;
import com.cool.store.response.storeopen.StoreRecipesResponse;
import com.cool.store.service.ThirdStoreOpenDataService;
import com.cool.store.utils.StoreOpenSigner;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
* 第三方门店开放数据服务实现类
*
* @author wangff
* @since 2026/4/20
*/
@Service
@Slf4j
public class ThirdStoreOpenDataServiceImpl implements ThirdStoreOpenDataService {
@Value("${store.open.url}")
private String apiUrl;
@Value("${store.open.appId}")
private String appId;
@Value("${store.open.secret}")
private String secret;
@Resource
private OkHttpClient okHttpClient;
@Resource
private ObjectMapper objectMapper;
private static final String METHOD_POST = "POST";
@Override
public List<StoreMonthRevenueResponse> getMonthRevenue(StoreMonthRevenueRequest request) {
String path = "/open/v1/store/monthRevenue";
String url = apiUrl + path;
return executeApiCall(url, path, request, StoreMonthRevenueResponse.class);
}
@Override
public List<StoreRecipeDailySalesResponse> getRecipeDailySales(StoreRecipeDailySalesRequest request) {
String path = "/open/v1/store/recipeDailySales";
String url = apiUrl + path;
return executeApiCall(url, path, request, StoreRecipeDailySalesResponse.class);
}
@Override
public List<StoreRecipesResponse> getRecipes(StoreRecipesRequest request) {
String path = "/open/v1/store/recipes";
String url = apiUrl + path;
return executeApiCall(url, path, request, StoreRecipesResponse.class);
}
private <T> List<T> executeApiCall(String url, String path, Object request, Class<T> responseType) {
logRequest(url, request);
try {
String rawJsonBody = JSONObject.toJSONString(request);
Request httpRequest = buildRequest(url, path, rawJsonBody);
try (Response response = okHttpClient.newCall(httpRequest).execute()) {
String responseBody = response.body().string();
logResponse(url, response.code(), responseBody);
if (!response.isSuccessful()) {
throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR,
"HTTP请求失败状态码: " + response.code());
}
JavaType javaType = objectMapper.getTypeFactory()
.constructParametricType(StoreOpenApiResponse.class,
objectMapper.getTypeFactory().constructCollectionType(List.class, responseType));
StoreOpenApiResponse<List<T>> apiResponse = objectMapper.readValue(responseBody, javaType);
if (apiResponse.getCode() != 200) {
throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR, apiResponse.getMsg());
}
return apiResponse.getData();
}
} catch (ServiceException e) {
throw e;
} catch (Exception e) {
log.error("API调用异常 - URL: {}, 错误: {}", url, e.getMessage(), e);
throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR, "接口调用异常: " + e.getMessage());
}
}
private Request buildRequest(String url, String path, String rawJsonBody) {
String signTime = StoreOpenSigner.generateSignTime();
String signRandom = StoreOpenSigner.generateRandom(16);
String signingString = StoreOpenSigner.buildSigningString(METHOD_POST, path, rawJsonBody, signTime, signRandom);
String sign = StoreOpenSigner.sign(signingString, secret);
log.debug("签名生成 - 原始字符串: {}, 签名结果: {}", signingString, sign);
RequestBody body = RequestBody.create(okhttp3.MediaType.parse("application/json"), rawJsonBody);
return new Request.Builder()
.url(url)
.post(body)
.addHeader("Content-Type", "application/json")
.addHeader("Accept", "application/json")
.addHeader("App-Id", appId)
.addHeader("X-ZhengXin-Sign", sign)
.addHeader("X-ZhengXin-SignTime", signTime)
.addHeader("X-ZhengXin-SignRandom", signRandom)
.build();
}
private void logRequest(String url, Object requestBody) {
if (log.isInfoEnabled()) {
try {
log.info("\n======= 请求开始 =======\n" +
"API地址: {}\n" +
"请求参数: {}\n" +
"======= 请求结束 =======",
url,
objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(requestBody));
} catch (JsonProcessingException e) {
log.warn("日志JSON序列化失败", e);
}
}
}
private void logResponse(String url, int statusCode, String responseBody) {
if (log.isInfoEnabled()) {
try {
Object json = objectMapper.readValue(responseBody, Object.class);
String prettyResponse = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(json);
log.info("\n======= 响应开始 =======\n" +
"API地址: {}\n" +
"HTTP状态码: {}\n" +
"响应内容: {}\n" +
"======= 响应结束 =======",
url, statusCode, prettyResponse);
} catch (Exception e) {
log.info("\n======= 响应开始 =======\n" +
"API地址: {}\n" +
"HTTP状态码: {}\n" +
"原始响应: {}\n" +
"======= 响应结束 =======",
url, statusCode, responseBody);
}
}
}
}