refactor(bpm): 重构回款结算接口并添加客户转移功能

- 重构了 BpmOAReceiptService 接口,使用 ReceiptSettlementVO 替代 LocalDateTime[] 参数
- 添加了客户转移相关的 API 和服务实现
- 优化了销售业绩结算相关的数据结构和接口
- 调整了权限控制相关的配置
This commit is contained in:
aikai 2025-03-10 16:41:14 +08:00
parent f3f91f7350
commit 1f65ca883c
37 changed files with 426 additions and 129 deletions

View File

@ -145,7 +145,7 @@ public class WxMpMsgTemplateUtils {
message.setJumpWxMaFlag(true);
message.setMiniProgramState(dto.getMiniProgramState());
message.setPage(dto.getPage());
message.setSocialType(SocialTypeEnum.WECHAT_MINI_APP.getType());
message.setSocialType(SocialTypeEnum.WECHAT_MINI_APP_CRM.getType());
return message;
}

View File

@ -1,10 +1,7 @@
package cn.iocoder.yudao.module.bpm.api.oa;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.BpmOAReceiptDTO;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.BpmOAReceiptVO;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.ReceiptSettlementDTO;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.ReceiptStatisticsDTO;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.*;
import cn.iocoder.yudao.module.bpm.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@ -36,7 +33,7 @@ public interface BpmOAReceiptApi {
CommonResult<List<ReceiptStatisticsDTO>> getContractStatistics(@RequestParam("userId") List<Long> userIds,
@RequestParam(name = "createTime", required = false) LocalDateTime[] createTime);
@GetMapping(PREFIX + "/getReceiptSettlement")
@PostMapping(PREFIX + "/getReceiptSettlement")
@Operation(summary = "获取用户回款结算信息")
CommonResult<List<ReceiptSettlementDTO>> getReceiptSettlement(@RequestParam(name = "times", required = false) LocalDateTime[] times);
CommonResult<List<ReceiptSettlementDTO>> getReceiptSettlement(@RequestBody ReceiptSettlementVO vo);
}

View File

@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.bpm.api.oa.vo.receipt;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Data
@ToString(callSuper = true)
public class ReceiptSettlementVO {
@Schema(description = "时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -2,14 +2,12 @@ package cn.iocoder.yudao.module.bpm.api.oa;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.BpmOAReceiptDTO;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.BpmOAReceiptVO;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.ReceiptSettlementDTO;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.ReceiptStatisticsDTO;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.*;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.receipt.ReceiptStatisticsVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAReceiptDO;
import cn.iocoder.yudao.module.bpm.service.oa.BpmOAReceiptService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@ -48,7 +46,7 @@ public class BpmOAReceiptApiImpl implements BpmOAReceiptApi{
}
@Override
public CommonResult<List<ReceiptSettlementDTO>> getReceiptSettlement(LocalDateTime[] times) {
return success(receiptService.getReceiptSettlement(times));
public CommonResult<List<ReceiptSettlementDTO>> getReceiptSettlement(ReceiptSettlementVO vo) {
return success(receiptService.getReceiptSettlement(vo));
}
}

View File

@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.ReceiptSettlementDTO;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.ReceiptSettlementVO;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.ReceiptStatisticsDTO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.receipt.BpmOAReceiptPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.receipt.BpmOAReceiptRespVO;
@ -50,8 +51,7 @@ public interface BpmOAReceiptMapper extends BaseMapperX<BpmOAReceiptDO> {
/**
* 获取用户结算回款信息
*
* @param times
* @return
*/
List<ReceiptSettlementDTO> getReceiptSettlement(@Param("times") LocalDateTime[] times);
List<ReceiptSettlementDTO> getReceiptSettlement(@Param("vo") ReceiptSettlementVO vo);
}

View File

@ -276,7 +276,9 @@ public class BpmOAContractServiceImpl extends BpmOABaseService implements BpmOAC
// 查询当前用户 所有下级用户编号
userIds.addAll(userLiveTreeApi.getItemIdsByUserId(getLoginUserId()).getCheckedData());
}
if (CollUtil.isEmpty(userIds)) {
return PageResult.empty();
}
return contractMapper.selectPage(pageReqVO, userIds);
}
@ -327,9 +329,10 @@ public class BpmOAContractServiceImpl extends BpmOABaseService implements BpmOAC
@Override
public List<BpmOAContractDO> getContractList(BpmOAContractVO respVO) {
Date[] dateArray = new Date[]{respVO.getStarTime(), respVO.getEndTime()};
if (CollUtil.isEmpty(respVO.getUserId())) {
return Collections.emptyList();
}
return contractMapper.selectList(new LambdaQueryWrapperX<BpmOAContractDO>()
.inIfPresent(BpmOAContractDO::getUserId, respVO.getUserId())
.eqIfPresent(BpmOAContractDO::getContractType, respVO.getContractType())
@ -370,30 +373,30 @@ public class BpmOAContractServiceImpl extends BpmOABaseService implements BpmOAC
}
private void createContractProductList(Long contractId, List<CrmContractProductVO> list) {
List<StoreProductAttrValueApiVO> storeProductAttrValueList = new ArrayList<>();
if (CollUtil.isNotEmpty(list)) {
List<Long> productIds = list.stream().map(CrmContractProductVO::getProductId).collect(Collectors.toList());
List<String> productAttrUnique = list.stream().map(CrmContractProductVO::getProductAttrUnique).collect(Collectors.toList());
storeProductAttrValueList = storeProductAttrValueApi.getStoreProductAttrValueList(new StoreProductAttrValueApiDTO()
.setProductIds(productIds)
.setUniques(productAttrUnique)).getCheckedData();
}
Map<String, List<StoreProductAttrValueApiVO>> map = storeProductAttrValueList.stream().collect(Collectors.groupingBy(a -> a.getProductId() + "_" + a.getUnique()));
list.forEach(o -> {
o.setContractId(contractId);
//库存处理
List<StoreProductAttrValueApiVO> storeProductAttrValueApiVOS = map.get(o.getProductId() + "_" + o.getProductAttrUnique());
int count = 0;
if (CollUtil.isNotEmpty(storeProductAttrValueApiVOS)) {
count = storeProductAttrValueApiVOS.stream().mapToInt(StoreProductAttrValueApiVO::getStock).sum();
}
if (NumberUtil.compare(count, o.getNums()) < 0) {
throw exception(new ErrorCode(202408250, "该商品ID" + o.getProductId() + "库存不足"));
}
// TODO: 2024/11/21 这里的库存扣件完后 - 如果审批不通过 库存退回
this.decProductStock(o.getNums(), o.getProductId(), o.getProductAttrUnique());
});
// List<StoreProductAttrValueApiVO> storeProductAttrValueList = new ArrayList<>();
// if (CollUtil.isNotEmpty(list)) {
// List<Long> productIds = list.stream().map(CrmContractProductVO::getProductId).collect(Collectors.toList());
// List<String> productAttrUnique = list.stream().map(CrmContractProductVO::getProductAttrUnique).collect(Collectors.toList());
// storeProductAttrValueList = storeProductAttrValueApi.getStoreProductAttrValueList(new StoreProductAttrValueApiDTO()
// .setProductIds(productIds)
// .setUniques(productAttrUnique)).getCheckedData();
// }
// Map<String, List<StoreProductAttrValueApiVO>> map = storeProductAttrValueList.stream().collect(Collectors.groupingBy(a -> a.getProductId() + "_" + a.getUnique()));
// list.forEach(o -> {
// o.setContractId(contractId);
// //库存处理
// List<StoreProductAttrValueApiVO> storeProductAttrValueApiVOS = map.get(o.getProductId() + "_" + o.getProductAttrUnique());
//
// int count = 0;
// if (CollUtil.isNotEmpty(storeProductAttrValueApiVOS)) {
// count = storeProductAttrValueApiVOS.stream().mapToInt(StoreProductAttrValueApiVO::getStock).sum();
// }
// if (NumberUtil.compare(count, o.getNums()) < 0) {
// throw exception(new ErrorCode(202408250, "该商品ID" + o.getProductId() + "库存不足"));
// }
// // TODO: 2024/11/21 这里的库存扣件完后 - 如果审批不通过 库存退回
// this.decProductStock(o.getNums(), o.getProductId(), o.getProductAttrUnique());
// });
contractApi.createProduct(BeanUtils.toBean(list, CrmContractProductDTO.class));
}

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.bpm.service.oa;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.BpmOAReceiptVO;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.ReceiptSettlementDTO;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.ReceiptSettlementVO;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.ReceiptStatisticsDTO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.receipt.BpmOAReceiptCreateReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.receipt.BpmOAReceiptPageReqVO;
@ -86,8 +87,8 @@ public interface BpmOAReceiptService {
/**
* 获取用户结算回款信息
*
* @param times
* @param vo
* @return
*/
List<ReceiptSettlementDTO> getReceiptSettlement(LocalDateTime[] times);
List<ReceiptSettlementDTO> getReceiptSettlement(ReceiptSettlementVO vo);
}

View File

@ -1,11 +1,13 @@
package cn.iocoder.yudao.module.bpm.service.oa;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.UploadUserFile;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.BpmOAReceiptVO;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.ReceiptSettlementDTO;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.ReceiptSettlementVO;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.ReceiptStatisticsDTO;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
@ -30,11 +32,7 @@ import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@ -44,7 +42,6 @@ import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.OA_RECEIPT_NO
* OA 回款申请 Service 实现类
*
* @author 符溶馨
*/
@Service
@Validated
@ -156,7 +153,9 @@ public class BpmOAReceiptServiceImpl extends BpmOABaseService implements BpmOARe
// 查询当前用户 所有下级用户编号
userIds.addAll(userLiveTreeApi.getItemIdsByUserId(getLoginUserId()).getCheckedData());
}
if (CollUtil.isEmpty(userIds)) {
return PageResult.empty();
}
return receiptMapper.selectReceiptPage(pageReqVO, userIds);
}
@ -171,7 +170,9 @@ public class BpmOAReceiptServiceImpl extends BpmOABaseService implements BpmOARe
@Override
public List<BpmOAReceiptDO> getReceiptList(BpmOAReceiptVO respVO) {
if (CollUtil.isEmpty(respVO.getUserId())) {
return Collections.emptyList();
}
return receiptMapper.selectList(new LambdaQueryWrapperX<BpmOAReceiptDO>()
.inIfPresent(BpmOAReceiptDO::getUserId, respVO.getUserId())
.eq(BpmOAReceiptDO::getResult, BpmProcessInstanceResultEnum.APPROVE.getResult())
@ -185,8 +186,8 @@ public class BpmOAReceiptServiceImpl extends BpmOABaseService implements BpmOARe
}
@Override
public List<ReceiptSettlementDTO> getReceiptSettlement(LocalDateTime[] times) {
return receiptMapper.getReceiptSettlement(times);
public List<ReceiptSettlementDTO> getReceiptSettlement(ReceiptSettlementVO vo) {
return receiptMapper.getReceiptSettlement(vo);
}
public static BigDecimal calculatePercentageChange(Object today, Object yesterday) {

View File

@ -69,12 +69,12 @@
WHERE
deleted = 0
AND result = 2
<if test="times != null and times.length > 0">
<if test="times[0] != null">
and create_time &gt;= #{times[0]}
<if test="vo.createTime != null and vo.createTime.length > 0">
<if test="vo.createTime[0] != null">
and create_time &gt;= #{vo.createTime[0]}
</if>
<if test="times[1] != null">
and create_time &lt;= #{times[1]}
<if test="vo.createTime[1] != null">
and create_time &lt;= #{vo.createTime[1]}
</if>
</if>
GROUP BY user_id

View File

@ -15,4 +15,6 @@ public interface ErrorCodeConstants {
ErrorCode INVOICE_NOT_EXISTS = new ErrorCode(200008, "发票不存在");
ErrorCode ACHIEVEMENT_NOT_EXISTS = new ErrorCode(200009, "业绩目标不存在");
ErrorCode NOT_EDITABLE_UNTIL_SALES_TARGET_HAS_BEEN_APPLIED = new ErrorCode(200010, "未申请销售目标前不可编辑");
ErrorCode THIS_CUSTOMER_ALREADY_EXISTS_IN_THE_SYSTEM_AND_CANNOT_BE_ENTERED_REPEATEDLY = new ErrorCode(200011, "系统已存在该客户,不可重复录入");
}

View File

@ -5,6 +5,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.crm.controller.admin.crmbusiness.vo.CrmBusinessPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.crmbusiness.vo.CrmBusinessRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.crmbusiness.vo.CrmBusinessSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.crmbusiness.vo.CrmBusinessTransferVO;
import cn.iocoder.yudao.module.crm.controller.admin.crmcustomer.vo.CrmCustomerTransferVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.crmbusiness.CrmBusinessProductDO;
import cn.iocoder.yudao.module.crm.service.crmbusiness.CrmBusinessService;
import io.swagger.v3.oas.annotations.Operation;
@ -62,6 +64,13 @@ public class CrmBusinessController {
return success(businessService.getBusiness(id));
}
@PostMapping("/transfer")
@Operation(summary = "转移商机")
public CommonResult<Boolean> transferBusiness(@Valid @RequestBody CrmBusinessTransferVO transferVO) {
businessService.transfer(transferVO);
return success(true);
}
@GetMapping("/page")
@Operation(summary = "获得商机分页")
// @PreAuthorize("@ss.hasPermission('crm:business:query')")

View File

@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.crm.controller.admin.crmbusiness.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - 客户转移 VO")
@Data
public class CrmBusinessTransferVO {
@Schema(description = "商机ID")
private List<Long> businessIds;
@Schema(description = "接受员工ID")
private List<Long> ownerAdminIds;
@Schema(description = "分配方式")
private Integer averageType;
}

View File

@ -41,7 +41,6 @@ public class SalesPerformanceSettlementController {
@PutMapping("/update")
@Operation(summary = "更新销售业绩结算记录")
@PreAuthorize("@ss.hasPermission('crm:sales-performance-settlement:update')")
public CommonResult<Boolean> updateSalesPerformanceSettlement(@Valid @RequestBody SalesPerformanceSettlementSaveReqVO updateReqVO) {
salesPerformanceSettlementService.updateSalesPerformanceSettlement(updateReqVO);
return success(true);
@ -67,7 +66,6 @@ public class SalesPerformanceSettlementController {
@GetMapping("/page")
@Operation(summary = "获得销售业绩结算记录分页")
@PreAuthorize("@ss.hasPermission('crm:sales-performance-settlement:query')")
public CommonResult<PageResult<SalesPerformanceSettlementRespVO>> getSalesPerformanceSettlementPage(@Valid SalesPerformanceSettlementPageReqVO pageReqVO) {
PageResult<SalesPerformanceSettlementDO> pageResult = salesPerformanceSettlementService.getSalesPerformanceSettlementPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, SalesPerformanceSettlementRespVO.class));

View File

@ -1,13 +1,15 @@
package cn.iocoder.yudao.module.crm.controller.admin.salesperformancesettlement.vo;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import java.math.BigDecimal;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ -59,6 +61,12 @@ public class SalesPerformanceSettlementPageReqVO extends PageParam {
@Schema(description = "是否可以确认 0否 1是")
private Integer canConfirmFlag;
@Schema(description = "评分状态 0待评分 1已评分")
private Integer scoreStatus;
@Schema(description = "结算状态 0待结算 1已结算待评分 2已结算")
private Integer settlementStatus;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
@ -66,4 +74,7 @@ public class SalesPerformanceSettlementPageReqVO extends PageParam {
@Schema(description = "用户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "27486")
private String nickname;
@Schema(description = "用户ids", example = "27486")
private List<Long> userIds;
}

View File

@ -44,6 +44,12 @@ public class SalesPerformanceSettlementRespVO {
@Schema(description = "实际销售额")
private Integer actualSale;
@Schema(description = "回款目标")
private BigDecimal paymentTarget;
@Schema(description = "销售目标")
private Integer saleTarget;
@Schema(description = "评分")
private BigDecimal score;
@ -68,6 +74,12 @@ public class SalesPerformanceSettlementRespVO {
@Schema(description = "是否可以确认 0否 1是")
private Integer canConfirmFlag;
@Schema(description = "评分状态 0待评分 1已评分")
private Integer scoreStatus;
@Schema(description = "结算状态 0待结算 1已结算待评分 2已结算")
private Integer settlementStatus;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;

View File

@ -59,4 +59,9 @@ public class SalesPerformanceSettlementSaveReqVO {
@Schema(description = "是否可以确认 0否 1是")
private Integer canConfirmFlag;
@Schema(description = "评分状态 0待评分 1已评分")
private Integer scoreStatus;
@Schema(description = "结算状态 0待结算 1已结算待评分 2已结算")
private Integer settlementStatus;
}

View File

@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.math.BigDecimal;
@ -86,9 +87,28 @@ public class SalesPerformanceSettlementDO extends BaseDO {
* 是否可以确认 0否 1是
*/
private Integer canConfirmFlag;
/**
* 评分状态 0待评分 1已评分
*/
private Integer scoreStatus;
/**
* 结算状态 0待结算 1已结算待评分 2已结算
*/
private Integer settlementStatus;
/**
* 昵称
*/
@TableField(exist = false)
private String nickname;
/**
* 回款目标
*/
@TableField(exist = false)
private BigDecimal paymentTarget;
/**
* 销售目标
*/
@TableField(exist = false)
private Integer saleTarget;
}

View File

@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
import cn.iocoder.yudao.module.system.api.mail.MailSendApi;
import cn.iocoder.yudao.module.system.api.notice.NoticeApi;
import cn.iocoder.yudao.module.system.api.permission.RoleApi;
import cn.iocoder.yudao.module.system.api.sms.SmsSendApi;
import cn.iocoder.yudao.module.system.api.subscribe.SubscribeMessageSendApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
@ -18,6 +19,6 @@ import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
@EnableFeignClients(clients = {DeptApi.class, DictDataApi.class, AdminUserApi.class, StoreProductApi.class, StoreProductAttrValueApi.class, SmsSendApi.class, MailSendApi.class, NoticeApi.class,
BpmOAContractApi.class, BpmOAReceiptApi.class, BpmOASalesPerformanceApi.class, AdminOauthUserOtherInfoApi.class, SubscribeMessageSendApi.class})
BpmOAContractApi.class, BpmOAReceiptApi.class, BpmOASalesPerformanceApi.class, AdminOauthUserOtherInfoApi.class, SubscribeMessageSendApi.class, RoleApi.class})
public class RpcConfiguration {
}

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.crm.job.salesperformance;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
import cn.iocoder.yudao.module.crm.service.salesperformancesettlement.SalesPerformanceSettlementService;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
@Slf4j
public class AutoConfirmJob {
// TODO: 2024/11/04 - 每个月月初03号 未确认的 自动确认上个月的结算记录
@Resource
private SalesPerformanceSettlementService salesPerformanceSettlementService;
@XxlJob("autoConfirmJob")
@TenantJob // --- 这个注解 会将租户列表拉出来 完了后逐个租户执行 定时任务需要注意
public ReturnT<String> execute() {
log.info("开始 自动确认");
salesPerformanceSettlementService.autoConfirmJob();
log.info("结束 自动确认");
return ReturnT.SUCCESS;
}
}

View File

@ -357,11 +357,6 @@ public class CrmAchievementServiceImpl implements CrmAchievementService {
.setStarTime(starTime)
.setEndTime(endTime)).getCheckedData();
// List<CrmContractReceivablesDO> crmContractReceivablesDOS = contractReceivablesMapper
// .selectList(new LambdaQueryWrapper<CrmContractReceivablesDO>()
// .in(CrmContractReceivablesDO::getOwnerUserId, userIds)
// .eq(CrmContractReceivablesDO::getCheckStatus, ContractStatusEnum.STATUS_2.getValue())
// .between(CrmContractReceivablesDO::getReturnTime, starTime, endTime));
BigDecimal receivablesSuccessMoney = BigDecimal.ZERO;
if (CollectionUtil.isNotEmpty(receiptDTOS)) {
receivablesSuccessMoney = receiptDTOS

View File

@ -198,6 +198,9 @@ public class AchievementServiceImpl implements AchievementService {
} else if (RelationEnum.SUB.getValue().equals(relation)) {
userIds = userLiveTreeService.getItemIdsByUserId(userId);
}
if (CollUtil.isEmpty(userIds)){
return Collections.emptyList();
}
//合同目标
List<CrmAchievementDO> crmAchievementDO = achievementMapper.selectList(new LambdaQueryWrapperX<CrmAchievementDO>()
.eq(CrmAchievementDO::getType, FlowStepEnum.TYPE_2.getValue())
@ -364,6 +367,9 @@ public class AchievementServiceImpl implements AchievementService {
} else if (RelationEnum.SUB.getValue().equals(relation)) {
userIds = userLiveTreeService.getItemIdsByUserId(userId);
}
if (CollUtil.isEmpty(userIds)){
return Collections.emptyList();
}
List<SalesVO> salesVOS = new ArrayList<>();
int i = 1;
while (i <= 12) {

View File

@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.crm.controller.admin.crmbusiness.vo.CrmBusinessPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.crmbusiness.vo.CrmBusinessRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.crmbusiness.vo.CrmBusinessSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.crmbusiness.vo.CrmBusinessTransferVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.crmbusiness.CrmBusinessProductDO;
import javax.validation.Valid;
@ -65,4 +66,10 @@ public interface CrmBusinessService {
*/
List<CrmBusinessProductDO> getBusinessProductListByBusinessId(Long businessId);
/**
* 转移商机
*
* @param transferVO
*/
void transfer(@Valid CrmBusinessTransferVO transferVO);
}

View File

@ -1,6 +1,8 @@
package cn.iocoder.yudao.module.crm.service.crmbusiness;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.enums.ShopCommonEnum;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
@ -8,7 +10,7 @@ import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.crm.controller.admin.crmbusiness.vo.CrmBusinessPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.crmbusiness.vo.CrmBusinessRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.crmbusiness.vo.CrmBusinessSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.crmclues.vo.CrmCluesRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.crmbusiness.vo.CrmBusinessTransferVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.crmbusiness.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.crmbusiness.CrmBusinessProductDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.crmcustomer.CrmCustomerDO;
@ -17,13 +19,13 @@ import cn.iocoder.yudao.module.crm.dal.mysql.crmbusiness.CrmBusinessMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.crmbusiness.CrmBusinessProductMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.crmcustomer.CrmCustomerMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.crmrecord.CrmRecordMapper;
import cn.iocoder.yudao.module.crm.service.crmoperatelog.CrmOperatelogService;
import cn.iocoder.yudao.module.crm.service.userlivetree.UserLiveTreeService;
import cn.iocoder.yudao.module.hrm.enums.RelationEnum;
import cn.iocoder.yudao.module.hrm.enums.TypesEnum;
import cn.iocoder.yudao.module.product.api.storeproductattrvalue.StoreProductAttrValueApi;
import cn.iocoder.yudao.module.product.api.storeproductattrvalue.dto.StoreProductAttrValueApiDTO;
import cn.iocoder.yudao.module.product.api.storeproductattrvalue.vo.StoreProductAttrValueApiVO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.stereotype.Service;
@ -34,6 +36,7 @@ import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -61,6 +64,8 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
private CrmRecordMapper crmRecordMapper;
@Resource
private UserLiveTreeService userLiveTreeService;
@Resource
private CrmOperatelogService crmOperatelogService;
@Override
@Transactional(rollbackFor = Exception.class)
@ -130,7 +135,9 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
} else if (RelationEnum.SUB.getValue().equals(pageReqVO.getRelation())) {
ids = userLiveTreeService.getItemIdsByUserId(adminId);
}
if (CollUtil.isEmpty(ids)) {
return PageResult.empty();
}
IPage mpPage = MyBatisUtils.buildPage(pageReqVO);
IPage<CrmBusinessRespVO> page = businessMapper.selectPageList(mpPage, pageReqVO, ids);
return new PageResult<>(page.getRecords(), page.getTotal());
@ -160,6 +167,44 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
return businessProductDOS;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void transfer(CrmBusinessTransferVO transferVO) {
//客户
List<CrmBusinessDO> crmBusinessDOS = businessMapper.selectBatchIds(transferVO.getBusinessIds());
if (crmBusinessDOS == null || crmBusinessDOS.isEmpty()) {
throw exception(new ErrorCode(202409091, "商机信息不存在"));
}
int adminCount = transferVO.getOwnerAdminIds().size();
int i = 0;
List<CrmBusinessDO> businessUpdateList = new ArrayList<>();
for (CrmBusinessDO businessDO : crmBusinessDOS) {
Long adminId;
if (ShopCommonEnum.AVG_1.getValue().equals(transferVO.getAverageType())) {
//平均分配
adminId = transferVO.getOwnerAdminIds().get(i);
if (i == (adminCount - 1)) {
i = 0;
} else {
i++;
}
} else {
//随机分配
Random random = new Random();
int num = random.nextInt(adminCount);
adminId = transferVO.getOwnerAdminIds().get(num);
}
businessDO.setOwnerUserId(adminId);
businessUpdateList.add(businessDO);
//处理日志
crmOperatelogService.createLog("转移商机", businessDO.getCustomerId(), 0L, 0L);
}
if (CollUtil.isNotEmpty(businessUpdateList)) {
businessMapper.updateBatch(businessUpdateList);
}
}
private void createBusinessProductList(Long businessId, List<CrmBusinessProductDO> list) {
list.forEach(o -> o.setBusinessId(businessId));
businessProductMapper.insertBatch(list);

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.crm.service.crmclues;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.ShopCommonEnum;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
@ -40,6 +41,7 @@ import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.CLUES_NOT_EXISTS;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.THIS_CUSTOMER_ALREADY_EXISTS_IN_THE_SYSTEM_AND_CANNOT_BE_ENTERED_REPEATEDLY;
/**
* 线索 Service 实现类
@ -118,7 +120,9 @@ public class CrmCluesServiceImpl implements CrmCluesService {
} else if (RelationEnum.SUB.getValue().equals(pageReqVO.getRelation())) {
ids = userLiveTreeService.getItemIdsByUserId(adminId);
}
if (CollUtil.isEmpty(ids)){
return PageResult.empty();
}
IPage mpPage = MyBatisUtils.buildPage(pageReqVO);
IPage<CrmCluesRespVO> page = cluesMapper.selectPageList(mpPage, pageReqVO, ids);
return new PageResult<>(page.getRecords(), page.getTotal());
@ -157,13 +161,18 @@ public class CrmCluesServiceImpl implements CrmCluesService {
if(CluesStatusEnum.STATUS_1.getValue().equals(crmCluesDO.getStatus())){
throw exception(new ErrorCode(202411160,"已经转化成客户!"));
}
// 判断客户名称是否唯一
Long count = customerMapper.selectCount(new LambdaQueryWrapper<CrmCustomerDO>()
.eq(CrmCustomerDO::getName, createReqVO.getName()));
if (count > 0) {
throw exception(THIS_CUSTOMER_ALREADY_EXISTS_IN_THE_SYSTEM_AND_CANNOT_BE_ENTERED_REPEATEDLY);
}
createReqVO.setId(null);
CrmCustomerDO customerDO = BeanUtils.toBean(createReqVO, CrmCustomerDO.class);
customerDO.setOwnerUserId(SecurityFrameworkUtils.getLoginUserId());
customerDO.setNextTime(LocalDateTime.now());
customerDO.setCollectTime(LocalDateTime.now());
customerMapper.insert(customerDO);
//更新线索
crmCluesDO.setStatus(CluesStatusEnum.STATUS_1.getValue());
crmCluesDO.setCustomerId(customerDO.getId());

View File

@ -408,30 +408,30 @@ public class CrmContractServiceImpl implements CrmContractService {
}
private void createContractProductList(Long contractId, List<CrmContractProductDO> list) {
List<StoreProductAttrValueApiVO> storeProductAttrValueList = new ArrayList<>();
if (CollUtil.isNotEmpty(list)) {
List<Long> productIds = list.stream().map(CrmContractProductDO::getProductId).collect(Collectors.toList());
List<String> productAttrUnique = list.stream().map(CrmContractProductDO::getProductAttrUnique).collect(Collectors.toList());
storeProductAttrValueList = storeProductAttrValueApi.getStoreProductAttrValueList(new StoreProductAttrValueApiDTO()
.setProductIds(productIds)
.setUniques(productAttrUnique)).getCheckedData();
}
Map<String, List<StoreProductAttrValueApiVO>> map = storeProductAttrValueList.stream().collect(Collectors.groupingBy(a -> a.getProductId() + "_" + a.getUnique()));
list.forEach(o -> {
o.setContractId(contractId);
//库存处理
List<StoreProductAttrValueApiVO> storeProductAttrValueApiVOS = map.get(o.getProductId() + "_" + o.getProductAttrUnique());
int count = 0;
if (CollUtil.isNotEmpty(storeProductAttrValueApiVOS)) {
count = storeProductAttrValueApiVOS.stream().mapToInt(StoreProductAttrValueApiVO::getStock).sum();
}
if (NumberUtil.compare(count, o.getNums()) < 0) {
throw exception(new ErrorCode(202408250, "该商品ID" + o.getProductId() + "库存不足"));
}
// TODO: 2024/11/21 这里的库存扣件完后 - 如果审批不通过 库存退回
this.decProductStock(o.getNums(), o.getProductId(), o.getProductAttrUnique());
});
// List<StoreProductAttrValueApiVO> storeProductAttrValueList = new ArrayList<>();
// if (CollUtil.isNotEmpty(list)) {
// List<Long> productIds = list.stream().map(CrmContractProductDO::getProductId).collect(Collectors.toList());
// List<String> productAttrUnique = list.stream().map(CrmContractProductDO::getProductAttrUnique).collect(Collectors.toList());
// storeProductAttrValueList = storeProductAttrValueApi.getStoreProductAttrValueList(new StoreProductAttrValueApiDTO()
// .setProductIds(productIds)
// .setUniques(productAttrUnique)).getCheckedData();
// }
// Map<String, List<StoreProductAttrValueApiVO>> map = storeProductAttrValueList.stream().collect(Collectors.groupingBy(a -> a.getProductId() + "_" + a.getUnique()));
// list.forEach(o -> {
// o.setContractId(contractId);
// //库存处理
// List<StoreProductAttrValueApiVO> storeProductAttrValueApiVOS = map.get(o.getProductId() + "_" + o.getProductAttrUnique());
//
// int count = 0;
// if (CollUtil.isNotEmpty(storeProductAttrValueApiVOS)) {
// count = storeProductAttrValueApiVOS.stream().mapToInt(StoreProductAttrValueApiVO::getStock).sum();
// }
// if (NumberUtil.compare(count, o.getNums()) < 0) {
// throw exception(new ErrorCode(202408250, "该商品ID" + o.getProductId() + "库存不足"));
// }
// // TODO: 2024/11/21 这里的库存扣件完后 - 如果审批不通过 库存退回
// this.decProductStock(o.getNums(), o.getProductId(), o.getProductAttrUnique());
// });
contractProductMapper.insertBatch(list);
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.crm.service.crmcontractreceivables;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.PatternPool;
import cn.hutool.core.util.ReUtil;
@ -186,6 +187,9 @@ public class CrmContractReceivablesServiceImpl implements CrmContractReceivables
} else if (RelationEnum.SUB.getValue().equals(pageReqVO.getRelation())) {
ids = userLiveTreeService.getItemIdsByUserId(loginAdminId);
}
if (CollUtil.isEmpty(ids)){
return PageResult.empty();
}
pageReqVO.setLoginAdminId(loginAdminId);
IPage mpPage = MyBatisUtils.buildPage(pageReqVO);
IPage<CrmContractReceivablesRespVO> pageResult = contractReceivablesMapper.selectPageList(mpPage, pageReqVO, ids);

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.crm.service.crmcustomer;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.ShopCommonEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
@ -49,6 +50,7 @@ import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.CUSTOMER_NOT_EXISTS;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.THIS_CUSTOMER_ALREADY_EXISTS_IN_THE_SYSTEM_AND_CANNOT_BE_ENTERED_REPEATEDLY;
/**
* 客户 Service 实现类
@ -87,8 +89,13 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
@Transactional(rollbackFor = Exception.class)
public Long createCustomer(CrmCustomerSaveReqVO createReqVO) {
// 插入
// 判断客户名称是否唯一
Long count = customerMapper.selectCount(new LambdaQueryWrapper<CrmCustomerDO>()
.eq(CrmCustomerDO::getName, createReqVO.getName()));
if (count > 0) {
throw exception(THIS_CUSTOMER_ALREADY_EXISTS_IN_THE_SYSTEM_AND_CANNOT_BE_ENTERED_REPEATEDLY);
}
CrmCustomerDO customer = BeanUtils.toBean(createReqVO, CrmCustomerDO.class);
customer.setCollectTime(LocalDateTime.now());
customer.setFollowTime(LocalDateTime.now());
@ -245,6 +252,9 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
ids = userLiveTreeService.getItemIdsByUserId(adminId);
}
}
if (CollUtil.isEmpty(ids)) {
return PageResult.empty();
}
return customerMapper.selectPage(pageReqVO, ids);
}
@ -262,6 +272,9 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
ids = userLiveTreeService.getItemIdsByUserId(adminId);
}
}
if (CollUtil.isEmpty(ids)) {
return PageResult.empty();
}
IPage mpPage = MyBatisUtils.buildPage(pageReqVO);
IPage<CrmCustomerRespVO> pageResult = customerMapper.selectPageList(mpPage, pageReqVO, ids);
return new PageResult<>(pageResult.getRecords(), pageResult.getTotal());

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.crm.service.crmcustomercontacts;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
@ -91,6 +92,9 @@ public class CrmCustomerContactsServiceImpl implements CrmCustomerContactsServic
} else if (RelationEnum.SUB.getValue().equals(pageReqVO.getRelation())) {
ids = userLiveTreeService.getItemIdsByUserId(adminId);
}
if (CollUtil.isEmpty(ids)){
return PageResult.empty();
}
IPage mpPage = MyBatisUtils.buildPage(pageReqVO);
IPage<CrmCustomerContactsRespVO> pageResult = customerContactsMapper.selectPageList(mpPage, pageReqVO, ids);
return new PageResult<>(pageResult.getRecords(), pageResult.getTotal());

View File

@ -1,9 +1,11 @@
package cn.iocoder.yudao.module.crm.service.crmindex;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.enums.ShopCommonEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.bpm.api.oa.BpmOAContractApi;
import cn.iocoder.yudao.module.bpm.api.oa.BpmOAReceiptApi;
@ -101,7 +103,9 @@ public class CrmIndexServiceImpl implements CrmIndexService {
ids = userLiveTreeService.getItemIdsByUserId(adminId);
}
}
if (CollUtil.isEmpty(ids)){
return BrieCountVO.builder().build();
}
Long count01 = businessMapper.selectCount(new LambdaQueryWrapper<CrmBusinessDO>()
.in(!ids.isEmpty(), CrmBusinessDO::getOwnerUserId, ids)
.between(CrmBusinessDO::getCreateTime, todayStart, todayEnd));

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.crm.service.crminvoice;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.ShopCommonEnum;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
@ -181,6 +182,9 @@ public class CrmInvoiceServiceImpl implements CrmInvoiceService {
} else if (RelationEnum.SUB.getValue().equals(pageReqVO.getRelation())) {
ids = userLiveTreeService.getItemIdsByUserId(loginAdminId);
}
if (CollUtil.isEmpty(ids)){
return PageResult.empty();
}
PageResult<CrmInvoiceRespVO> pageResult = invoiceMapper.selectPage2(pageReqVO, ids);
for (CrmInvoiceRespVO respVO : pageResult.getList()) {
List<String> adminIds = StrUtil.split(respVO.getFlowAdminId(), ",");

View File

@ -43,7 +43,6 @@ import java.util.Map;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.RECORD_NOT_EXISTS;
/**
@ -71,6 +70,7 @@ public class CrmRecordServiceImpl implements CrmRecordService {
private DictDataApi dictDataApi;
@Resource
private UserLiveTreeService userLiveTreeService;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createRecord(CrmRecordSaveReqVO createReqVO) {

View File

@ -63,4 +63,9 @@ public interface SalesPerformanceSettlementService extends IService<SalesPerform
* 生成下一个月的业绩
*/
void generateSalesPerformanceJob();
/**
* 自动确认上个月的业绩
*/
void autoConfirmJob();
}

View File

@ -11,10 +11,12 @@ import cn.iocoder.yudao.framework.common.util.number.BigDecimalUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.bpm.api.oa.BpmOAContractApi;
import cn.iocoder.yudao.module.bpm.api.oa.BpmOAReceiptApi;
import cn.iocoder.yudao.module.bpm.api.oa.BpmOASalesPerformanceApi;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.ReceiptSettlementDTO;
import cn.iocoder.yudao.module.bpm.api.oa.vo.receipt.ReceiptSettlementVO;
import cn.iocoder.yudao.module.bpm.api.oa.vo.salesperformance.SalesPerformanceDTO;
import cn.iocoder.yudao.module.crm.controller.admin.salesperformancesettlement.vo.SalesPerformanceSettlementPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.salesperformancesettlement.vo.SalesPerformanceSettlementSaveReqVO;
@ -31,12 +33,14 @@ import cn.iocoder.yudao.module.hrm.api.crmcontract.dto.CrmContractProductSettlem
import cn.iocoder.yudao.module.system.api.auth.AdminOauthUserOtherInfoApi;
import cn.iocoder.yudao.module.system.api.auth.dto.AdminOauthUserOtherInfoApiDTO;
import cn.iocoder.yudao.module.system.api.auth.vo.AdminOauthUserOtherInfoApiVO;
import cn.iocoder.yudao.module.system.api.permission.RoleApi;
import cn.iocoder.yudao.module.system.api.subscribe.SubscribeMessageSendApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.ognl.Ognl;
import org.apache.ibatis.ognl.OgnlException;
import org.springframework.stereotype.Service;
@ -60,6 +64,7 @@ import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.NOT_EDITABLE_
*/
@Service
@Validated
@Slf4j
public class SalesPerformanceSettlementServiceImpl extends ServiceImpl<SalesPerformanceSettlementMapper, SalesPerformanceSettlementDO> implements SalesPerformanceSettlementService {
@Resource
@ -84,6 +89,8 @@ public class SalesPerformanceSettlementServiceImpl extends ServiceImpl<SalesPerf
private AdminUserApi adminUserApi;
@Resource
private BpmOAContractApi bpmOAContractApi;
@Resource
private RoleApi roleApi;
//百分比
private static final BigDecimal PERCENTAGE = new BigDecimal("0.01");
@ -120,8 +127,8 @@ public class SalesPerformanceSettlementServiceImpl extends ServiceImpl<SalesPerf
// 未申请销售目标前不可编辑
throw exception(NOT_EDITABLE_UNTIL_SALES_TARGET_HAS_BEEN_APPLIED);
}
BigDecimal saleScore = BigDecimal.valueOf(updateObj.getActualSale())
.divide(BigDecimal.valueOf(salesPerformanceDTO.getSaleTarget()), 2, RoundingMode.HALF_UP);
BigDecimal saleScore = (salesPerformanceDTO.getSaleTarget() == null || salesPerformanceDTO.getSaleTarget() == 0) ? BigDecimal.ZERO :
(BigDecimal.valueOf(updateObj.getActualSale()).divide(BigDecimal.valueOf(salesPerformanceDTO.getSaleTarget()), 2, RoundingMode.HALF_UP));
saleScore = (saleScore.compareTo(BigDecimal.ONE) >= 0) ? salesPerformanceWeight.getSaleWeight() :
salesPerformanceWeight.getSaleWeight().multiply(saleScore);
// 销售任务最终得分
@ -133,8 +140,8 @@ public class SalesPerformanceSettlementServiceImpl extends ServiceImpl<SalesPerf
// 未申请销售目标前不可编辑
throw exception(NOT_EDITABLE_UNTIL_SALES_TARGET_HAS_BEEN_APPLIED);
}
BigDecimal paymentScore = updateObj.getActualPayment()
.divide(salesPerformanceDTO.getPaymentTarget(), 2, RoundingMode.HALF_UP);
BigDecimal paymentScore = BigDecimalUtil.isEqZero(salesPerformanceDTO.getPaymentTarget()) ? BigDecimal.ZERO :
(updateObj.getActualPayment().divide(salesPerformanceDTO.getPaymentTarget(), 2, RoundingMode.HALF_UP));
paymentScore = (paymentScore.compareTo(BigDecimal.ONE) >= 0) ? salesPerformanceWeight.getPaymentWeight() :
salesPerformanceWeight.getPaymentWeight().multiply(paymentScore);
// 回款任务最终得分
@ -149,6 +156,11 @@ public class SalesPerformanceSettlementServiceImpl extends ServiceImpl<SalesPerf
// 回款任务最终得分
updateObj.setScoreTask(score);
changeFlag = true;
// --
updateObj.setScoreStatus(1);
if (salesPerformanceSettlementDO.getSettlementStatus() == 1) {
updateObj.setSettlementStatus(2);
}
}
if (changeFlag) {
// 重新计算 -
@ -179,19 +191,26 @@ public class SalesPerformanceSettlementServiceImpl extends ServiceImpl<SalesPerf
@Override
public PageResult<SalesPerformanceSettlementDO> getSalesPerformanceSettlementPage(SalesPerformanceSettlementPageReqVO pageReqVO) {
Long userId = SecurityFrameworkUtils.getLoginUserId();
// -- 这里要做什么 要筛选出可见范围 - 但是crm用户不和system 用户 结构一体 - 所以 需要从代码中来区分 - 我们设定一个角色 可以看全部内容 - 完了后 其他的用户只能看自己以及下级的信息 通过pid id来判断
// 那么怎么获取到这个用户有没有包含这个角色呢
boolean calculator = roleApi.validCalculator(userId).getCheckedData();
if (!calculator) {
List<Long> userIds = userLiveTreeService.getItemIdsByUserId(userId);
userIds.add(userId);
pageReqVO.setUserIds(userIds);
}
IPage<SalesPerformanceSettlementDO> pageResult = salesPerformanceSettlementMapper.getSalesPerformanceSettlementPage(pageReqVO, MyBatisUtils.buildPage(pageReqVO));
return new PageResult<>(pageResult.getRecords(), pageResult.getTotal());
}
@Override
public void settlement() {
// LocalDateTime now = LocalDateTime.now().minusMonths(1L);
LocalDateTime now = LocalDateTime.now();
LocalDateTime now = LocalDateTime.now().minusMonths(1L);
String year = String.valueOf(now.getYear());
String month = String.format("%02d", now.getMonthValue());
// 获取CRM用户一下
// List<UserLiveTreeListVO> userLiveList = userLiveTreeService.getUserLiveList();
List<UserLiveTreeListVO> userLiveList = Arrays.asList(new UserLiveTreeListVO().setUserId(152L));
List<UserLiveTreeListVO> userLiveList = userLiveTreeService.getUserLiveList();
// 计算当前月所有销售的 销售额 - 销售台数
// -- 合同产品数量 后续可能需要根据不同的产品来定目标 - 所以这里 我们预判下 - 将合同对应的projectId也查询出来 好方便后续做产品区分
List<CrmContractProductSettlementDTO> contractProductSettlementDTOList = crmContractService.getContractProductSettlement();
@ -204,7 +223,9 @@ public class SalesPerformanceSettlementServiceImpl extends ServiceImpl<SalesPerf
LocalDateTime beginTime = LocalDateTimeUtils.beginOfMonth(now);
LocalDateTime endTime = LocalDateTimeUtils.endOfMonth(now);
LocalDateTime[] times = {beginTime, endTime};
List<ReceiptSettlementDTO> receiptSettlementList = receiptApi.getReceiptSettlement(times).getCheckedData();
ReceiptSettlementVO receiptSettlementVO = new ReceiptSettlementVO();
receiptSettlementVO.setCreateTime(times);
List<ReceiptSettlementDTO> receiptSettlementList = receiptApi.getReceiptSettlement(receiptSettlementVO).getCheckedData();
receiptSettlementList = CollUtil.isEmpty(receiptSettlementList) ? Collections.emptyList() : receiptSettlementList;
Map<Long, BigDecimal> receiptSettlementMap = receiptSettlementList.stream()
.collect(Collectors.toMap(ReceiptSettlementDTO::getUserId, ReceiptSettlementDTO::getMoney));
@ -232,20 +253,25 @@ public class SalesPerformanceSettlementServiceImpl extends ServiceImpl<SalesPerf
salesPerformanceSettlementDO.setActualSale(productMap.getOrDefault(vo.getUserId(), 0));
salesPerformanceSettlementDO.setActualPayment(receiptSettlementMap.getOrDefault(vo.getUserId(), BigDecimal.ZERO));
SalesPerformanceDTO salesPerformanceDTO = salesPerformanceMap.get(vo.getUserId());
if (salesPerformanceDTO == null) {
continue;
}
// 计算得分
if (salesPerformanceWeight != null) {
// -- 如果实际/目标>0则拿到全额的权重
// ----------- 销售任务得分计算 -----------
BigDecimal saleScore = BigDecimal.valueOf(salesPerformanceSettlementDO.getActualSale())
.divide(BigDecimal.valueOf(salesPerformanceDTO.getSaleTarget()), 2, RoundingMode.HALF_UP);
BigDecimal saleScore = (salesPerformanceDTO.getSaleTarget() == null || salesPerformanceDTO.getSaleTarget() == 0) ? BigDecimal.ZERO :
(BigDecimal.valueOf(salesPerformanceSettlementDO.getActualSale())
.divide(BigDecimal.valueOf(salesPerformanceDTO.getSaleTarget()), 2, RoundingMode.HALF_UP));
saleScore = (saleScore.compareTo(BigDecimal.ONE) >= 0) ? salesPerformanceWeight.getSaleWeight() :
salesPerformanceWeight.getSaleWeight().multiply(saleScore);
// 销售任务最终得分
salesPerformanceSettlementDO.setSaleTask(saleScore);
// ----------- 回款任务得分计算 -----------
BigDecimal paymentScore = salesPerformanceSettlementDO.getActualPayment()
.divide(salesPerformanceDTO.getPaymentTarget(), 2, RoundingMode.HALF_UP);
BigDecimal paymentScore = BigDecimalUtil.isEqZero(salesPerformanceDTO.getPaymentTarget()) ? BigDecimal.ZERO :
(salesPerformanceSettlementDO.getActualPayment()
.divide(salesPerformanceDTO.getPaymentTarget(), 2, RoundingMode.HALF_UP));
paymentScore = (paymentScore.compareTo(BigDecimal.ONE) >= 0) ? salesPerformanceWeight.getPaymentWeight() :
salesPerformanceWeight.getPaymentWeight().multiply(paymentScore);
// 回款任务最终得分
@ -272,6 +298,7 @@ public class SalesPerformanceSettlementServiceImpl extends ServiceImpl<SalesPerf
}
}
salesPerformanceSettlementDO.setCanConfirmFlag(1);
salesPerformanceSettlementDO.setSettlementStatus(salesPerformanceSettlementDO.getScoreStatus() == 1 ? 2 : 1);
}
list.add(salesPerformanceSettlementDO);
}
@ -286,7 +313,8 @@ public class SalesPerformanceSettlementServiceImpl extends ServiceImpl<SalesPerf
List<AdminOauthUserOtherInfoApiVO> adminOauthUserOtherInfoApiVOS = adminOauthUserOtherInfoApi.getOpenIdByCondition(
new AdminOauthUserOtherInfoApiDTO().setUserIds(userIds)
.setSocialType(SocialTypeEnum.WECHAT_MP.getType())).getCheckedData();
log.info("用户列表:{}", userIds);
log.info("用户三方账号列表:{}", adminOauthUserOtherInfoApiVOS);
// - 结算完毕 - 通知所有填写申请到crm用户确认
for (AdminOauthUserOtherInfoApiVO adminOauthUserOtherInfoApiVO : adminOauthUserOtherInfoApiVOS) {
SubscribeMessageReqDTO dto = new WxMpMsgTemplateUtils().convertPerformanceResultConfirmationReminder(new PerformanceResultConfirmationReminderDTO()
@ -321,6 +349,16 @@ public class SalesPerformanceSettlementServiceImpl extends ServiceImpl<SalesPerf
}
}
@Override
public void autoConfirmJob() {
LocalDateTime now = LocalDateTime.now().minusMonths(1L);
salesPerformanceSettlementMapper.update(new SalesPerformanceSettlementDO().setConfirmFlag(1),
new LambdaQueryWrapper<SalesPerformanceSettlementDO>()
.eq(SalesPerformanceSettlementDO::getYear, String.valueOf(now.getYear()))
.eq(SalesPerformanceSettlementDO::getMonth, String.format("%02d", now.getMonthValue()))
.eq(SalesPerformanceSettlementDO::getConfirmFlag, 0));
}
/**
* 表达式 10<=n && n<=20
*

View File

@ -34,7 +34,7 @@
and a.is_end = #{dto.isEnd}
</if>
<if test="dto.customerName != null and dto.customerName != ''">
and b.name = #{dto.customerName}
and b.name like concat('%', #{dto.customerName}, '%')=
</if>
<if test="dto.customerId != null">
and a.customer_id = #{dto.customerId}

View File

@ -13,15 +13,24 @@
resultType="cn.iocoder.yudao.module.crm.dal.dataobject.salesperformancesettlement.SalesPerformanceSettlementDO">
select
a.*,
b.nickname
b.nickname,
c.payment_target as paymentTarget,
c.sale_target as saleTarget
from crm_sales_performance_settlement as a
left join system_users as b on a.user_id = b.id
left join bpm_oa_sales_performance as c on a.sales_performance_id = c.id
<where>
a.deleted = 0
and b.deleted = 0
<if test="vo.userId != null">
and a.user_id = #{vo.userId}
</if>
<if test="vo.userIds != null and vo.userIds.size() > 0">
and a.user_id in
<foreach collection="vo.userIds" item="userId" open="(" separator="," close=")">
#{userId}
</foreach>
</if>
<if test="vo.year != null and vo.year != ''">
and a.year = #{vo.year}
</if>
@ -31,15 +40,24 @@
<if test="vo.nickname != null and vo.nickname != ''">
and b.nickname like concat('%', #{vo.nickname}, '%')
</if>
<if test="vo.scoreStatus != null">
and a.score_status = #{vo.scoreStatus}
</if>
<if test="vo.settlementStatus != null">
and a.settlement_status = #{vo.settlementStatus}
</if>
</where>
</select>
<select id="getSalesPerformanceSettlement"
resultType="cn.iocoder.yudao.module.crm.dal.dataobject.salesperformancesettlement.SalesPerformanceSettlementDO">
select
a.*,
b.nickname
b.nickname,
c.payment_target as paymentTarget,
c.sale_target as saleTarget
from crm_sales_performance_settlement as a
left join system_users as b on a.user_id = b.id
left join bpm_oa_sales_performance as c on a.sales_performance_id = c.id
<where>
a.deleted = 0
and b.deleted = 0

View File

@ -22,4 +22,10 @@ public interface RoleApi {
@Parameter(name = "ids", description = "角色编号数组", example = "1,2", required = true)
CommonResult<Boolean> validRoleList(@RequestParam("ids") Collection<Long> ids);
@GetMapping(PREFIX + "/validCalculator")
@Operation(summary = "校验角色是否包含销售业绩核算员")
@Parameter(name = "userId", description = "用户id", example = "2", required = true)
CommonResult<Boolean> validCalculator(@RequestParam("userId") Long userId);
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.system.api.permission;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.system.service.permission.PermissionService;
import cn.iocoder.yudao.module.system.service.permission.RoleService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
@ -16,10 +17,19 @@ public class RoleApiImpl implements RoleApi {
@Resource
private RoleService roleService;
@Resource
private PermissionService permissionService;
private static final String ROLE = "calculator";
@Override
public CommonResult<Boolean> validRoleList(Collection<Long> ids) {
roleService.validateRoleList(ids);
return success(true);
}
@Override
public CommonResult<Boolean> validCalculator(Long userId) {
boolean flag = permissionService.hasAnyRoles(userId, ROLE);
return success(flag);
}
}