diff --git a/ibs-group/pom.xml b/ibs-group/pom.xml new file mode 100644 index 0000000..ba8da8b --- /dev/null +++ b/ibs-group/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + com.ruoyi + ruoyi + 3.8.8 + + + ibs-group + + + 17 + 17 + UTF-8 + + + + + + + org.mockito + mockito-core + 3.3.3 + test + + + + junit + junit + 4.12 + test + + + + + + com.ruoyi + ruoyi-common + + + org.locationtech.jts + jts-core + 1.18.2 + + + org.projectlombok + lombok + + + org.springframework + spring-webmvc + 5.3.31 + + + + + + + + io.swagger + swagger-annotations + 1.6.2 + + + com.ruoyi + ruoyi-system + + + com.alibaba + easyexcel-core + 3.3.3 + + + com.opencsv + opencsv + 5.2 + + + com.ruoyi + ibs + + + + + \ No newline at end of file diff --git a/ibs-group/src/main/java/com/ruoyi/group/controller/CustGroupController.java b/ibs-group/src/main/java/com/ruoyi/group/controller/CustGroupController.java new file mode 100644 index 0000000..0150e1e --- /dev/null +++ b/ibs-group/src/main/java/com/ruoyi/group/controller/CustGroupController.java @@ -0,0 +1,164 @@ +package com.ruoyi.group.controller; + +import com.alibaba.fastjson2.JSON; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.group.domain.dto.CustGroupMemberTemplate; +import com.ruoyi.group.domain.dto.CustGroupQueryDTO; +import com.ruoyi.group.domain.dto.GridImportDTO; +import com.ruoyi.group.domain.entity.CustGroup; +import com.ruoyi.group.domain.vo.CustGroupVO; +import com.ruoyi.group.service.ICustGroupService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.util.List; + +/** + * 客群管理Controller + * + * @author ruoyi + */ +@Api(tags = "客群管理接口") +@RestController +@RequestMapping("/group/cust") +public class CustGroupController extends BaseController { + + @Resource + private ICustGroupService custGroupService; + + /** + * 查询客群列表 + */ + @ApiOperation("查询客群列表") + @Log(title = "客群管理-查询客群列表") + @GetMapping("/list") + public TableDataInfo listCustGroup(CustGroupQueryDTO dto) { + startPage(); + List list = custGroupService.listCustGroup(dto); + return getDataTable(list); + } + + /** + * 获取客群详情 + */ + @ApiOperation("获取客群详情") + @Log(title = "客群管理-获取客群详情") + @GetMapping("/{id}") + public AjaxResult getCustGroup(@PathVariable Long id) { + CustGroupVO vo = custGroupService.getCustGroup(id); + return AjaxResult.success(vo); + } + + /** + * 异步创建客群(网格导入) + */ + @ApiOperation("异步创建客群(网格导入)") + @Log(title = "客群管理-网格导入创建客群", businessType = BusinessType.INSERT) + @PostMapping("/createByGrid") + public AjaxResult createCustGroupByGrid(@RequestBody @Valid GridImportDTO gridImportDTO) { + String id = custGroupService.createCustGroupByGrid(gridImportDTO); + return AjaxResult.success(id); + } + + /** + * 异步创建客群(模板导入) + */ + @ApiOperation("异步创建客群(模板导入)") + @Log(title = "客群管理-异步创建客群", businessType = BusinessType.INSERT) + @PostMapping(value = "/createByTemplate", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public AjaxResult createCustGroupByTemplate(@RequestPart("dto") @Valid String dtoJson, + @RequestPart("file") MultipartFile file) { + CustGroup custGroup = JSON.parseObject(dtoJson, CustGroup.class); + return AjaxResult.success(custGroupService.createCustGroupByTemplate(custGroup, file)); + } + + /** + * 更新客群 + */ + @ApiOperation("更新客群") + @Log(title = "客群管理-更新客群", businessType = BusinessType.UPDATE) + @PostMapping("/update") + public AjaxResult updateCustGroup(@RequestBody @Valid CustGroup custGroup) { + String result = custGroupService.updateCustGroup(custGroup); + return AjaxResult.success(result); + } + + /** + * 更新客群(网格导入) + */ + @ApiOperation("更新客群(网格导入)") + @Log(title = "客群管理-更新客群(网格导入)", businessType = BusinessType.UPDATE) + @PostMapping("/updateByGrid") + public AjaxResult updateCustGroupByGrid(@RequestBody @Valid GridImportDTO gridImportDTO) { + String result = custGroupService.updateCustGroupByGrid(gridImportDTO); + return AjaxResult.success(result); + } + + /** + * 更新客群(模板导入) + */ + @ApiOperation("更新客群(模板导入)") + @Log(title = "客群管理-更新客群(模板导入)", businessType = BusinessType.UPDATE) + @PostMapping(value = "/updateByTemplate", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public AjaxResult updateCustGroupByTemplate(@RequestPart("dto") @Valid String dtoJson, + @RequestPart("file") MultipartFile file) { + CustGroup custGroup = JSON.parseObject(dtoJson, CustGroup.class); + return AjaxResult.success(custGroupService.updateCustGroupByTemplate(custGroup, file)); + } + + /** + * 轮询客群创建状态 + */ + @ApiOperation("轮询客群创建状态") + @Log(title = "客群管理-轮询客群创建状态") + @GetMapping("/createStatus/{id}") + public AjaxResult getCreateStatus(@PathVariable Long id) { + String status = custGroupService.getCreateStatus(id); + return AjaxResult.success(status); + } + + /** + * 客户信息模板下载 + */ + @ApiOperation("客户信息模板") + @Log(title = "客群管理-客户信息模板", businessType = BusinessType.EXPORT) + @PostMapping("/download") + public void download(HttpServletResponse response) { + ExcelUtil util = new ExcelUtil<>(CustGroupMemberTemplate.class); + util.exportExcel(response, null, "客户信息模板"); + } + + /** + * 删除客群 + */ + @ApiOperation("删除客群") + @Log(title = "客群管理-删除客群", businessType = BusinessType.DELETE) + @PostMapping("/delete") + public AjaxResult deleteCustGroup(@RequestBody List idList) { + String result = custGroupService.deleteCustGroup(idList); + return AjaxResult.success(result); + } + + /** + * 手动移除客群客户 + */ + @ApiOperation("手动移除客群客户") + @Log(title = "客群管理-手动移除客户", businessType = BusinessType.DELETE) + @PostMapping("/removeMembers") + public AjaxResult removeMembers(@RequestParam Long groupId, @RequestBody List memberIds) { + String result = custGroupService.removeMembers(groupId, memberIds); + return AjaxResult.success(result); + } + +} \ No newline at end of file diff --git a/ibs-group/src/main/java/com/ruoyi/group/domain/dto/CustGroupMemberTemplate.java b/ibs-group/src/main/java/com/ruoyi/group/domain/dto/CustGroupMemberTemplate.java new file mode 100644 index 0000000..d71049e --- /dev/null +++ b/ibs-group/src/main/java/com/ruoyi/group/domain/dto/CustGroupMemberTemplate.java @@ -0,0 +1,49 @@ +package com.ruoyi.group.domain.dto; + +import com.ruoyi.common.annotation.Excel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 客群客户Excel模板 + * + * @author ruoyi + */ +@Data +public class CustGroupMemberTemplate { + + /** + * 客户类型 + */ + @ApiModelProperty(value = "客户类型:个人/企业/商户", name = "custType") + @Excel(name = "客户类型(个人/企业/商户)", prompt = "必填", combo = "个人,企业,商户") + private String custType; + + /** + * 客户号 + */ + @ApiModelProperty(value = "客户号", name = "custId") + @Excel(name = "客户号", prompt = "必填") + private String custId; + + /** + * 客户姓名 + */ + @ApiModelProperty(value = "客户姓名", name = "custName") + @Excel(name = "客户姓名") + private String custName; + + /** + * 客户身份证号 + */ + @ApiModelProperty(value = "客户身份证号(个人客户必填)", name = "custIdc") + @Excel(name = "客户身份证号(个人客户必填)") + private String custIdc; + + /** + * 统信码 + */ + @ApiModelProperty(value = "统信码(企业/商户客户必填)", name = "socialCreditCode") + @Excel(name = "统信码(企业/商户客户必填)") + private String socialCreditCode; +} \ No newline at end of file diff --git a/ibs-group/src/main/java/com/ruoyi/group/domain/dto/CustGroupQueryDTO.java b/ibs-group/src/main/java/com/ruoyi/group/domain/dto/CustGroupQueryDTO.java new file mode 100644 index 0000000..8cd9afb --- /dev/null +++ b/ibs-group/src/main/java/com/ruoyi/group/domain/dto/CustGroupQueryDTO.java @@ -0,0 +1,53 @@ +package com.ruoyi.group.domain.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 客群查询DTO + * + * @author ruoyi + */ +@Data +public class CustGroupQueryDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 客群名称(模糊查询) + */ + @ApiModelProperty(value = "客群名称", name = "groupName") + private String groupName; + + /** + * 客群模式:0=静态, 1=动态 + */ + @ApiModelProperty(value = "客群模式", name = "groupMode") + private String groupMode; + + /** + * 创建方式:1=模板导入, 2=绩效网格, 3=地理网格, 4=自定义网格 + */ + @ApiModelProperty(value = "创建方式", name = "createMode") + private String createMode; + + /** + * 客群状态:0=正常, 1=已禁用 + */ + @ApiModelProperty(value = "客群状态", name = "groupStatus") + private String groupStatus; + + /** + * 页码 + */ + @ApiModelProperty(value = "页码", name = "pageNum") + private Integer pageNum = 1; + + /** + * 每页大小 + */ + @ApiModelProperty(value = "每页大小", name = "pageSize") + private Integer pageSize = 10; +} \ No newline at end of file diff --git a/ibs-group/src/main/java/com/ruoyi/group/domain/dto/GridImportDTO.java b/ibs-group/src/main/java/com/ruoyi/group/domain/dto/GridImportDTO.java new file mode 100644 index 0000000..45b43a9 --- /dev/null +++ b/ibs-group/src/main/java/com/ruoyi/group/domain/dto/GridImportDTO.java @@ -0,0 +1,58 @@ +package com.ruoyi.group.domain.dto; + +import com.ruoyi.group.domain.entity.CustGroup; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * 网格导入客群DTO + * + * @author ruoyi + */ +@Data +public class GridImportDTO { + + /** + * 客群信息 + */ + @ApiModelProperty(value = "客群信息", name = "custGroup") + @Valid + @NotNull(message = "客群信息不能为空") + private CustGroup custGroup; + + /** + * 网格类型:0=绩效网格, 1=地理网格, 2=绘制网格 + */ + @ApiModelProperty(value = "网格类型:0=绩效网格, 1=地理网格, 2=绘制网格", name = "gridType") + @NotBlank(message = "网格类型不能为空") + private String gridType; + + /** + * 绩效网格业务类型:retail=零售, corporate=公司(gridType=0时必填) + */ + @ApiModelProperty(value = "绩效网格业务类型:retail=零售, corporate=公司", name = "cmpmBizType") + private String cmpmBizType; + + /** + * 绩效网格客户经理柜员号列表(gridType=0时必填) + */ + @ApiModelProperty(value = "客户经理柜员号列表", name = "userNames") + private List userNames; + + /** + * 地理网格ID列表(gridType=1时必填) + */ + @ApiModelProperty(value = "地理网格ID列表", name = "regionGridIds") + private List regionGridIds; + + /** + * 绘制网格ID列表(gridType=2时必填) + */ + @ApiModelProperty(value = "绘制网格ID列表", name = "drawGridIds") + private List drawGridIds; +} \ No newline at end of file diff --git a/ibs-group/src/main/java/com/ruoyi/group/domain/entity/CustGroup.java b/ibs-group/src/main/java/com/ruoyi/group/domain/entity/CustGroup.java new file mode 100644 index 0000000..c8a9a32 --- /dev/null +++ b/ibs-group/src/main/java/com/ruoyi/group/domain/entity/CustGroup.java @@ -0,0 +1,172 @@ +package com.ruoyi.group.domain.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * 客群实体 + * + * @author ruoyi + */ +@Data +@TableName("ibs_cust_group") +public class CustGroup { + + /** + * 主键ID + */ + @ApiModelProperty(value = "主键ID", name = "id") + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 客群名称 + */ + @ApiModelProperty(value = "客群名称", name = "groupName") + private String groupName; + + /** + * 客群模式:0=静态, 1=动态 + */ + @ApiModelProperty(value = "客群模式:0=静态, 1=动态", name = "groupMode") + private String groupMode; + + /** + * 创建方式:1=模板导入, 2=绩效网格, 3=地理网格, 4=自定义网格 + */ + @ApiModelProperty(value = "创建方式:1=模板导入, 2=绩效网格, 3=地理网格, 4=自定义网格", name = "createMode") + private String createMode; + + /** + * 柜员号 + */ + @ApiModelProperty(value = "柜员号", name = "userName") + private String userName; + + /** + * 柜员名称 + */ + @ApiModelProperty(value = "柜员名称", name = "nickName") + private String nickName; + + /** + * 所属机构ID + */ + @ApiModelProperty(value = "所属机构ID", name = "deptId") + @TableField(fill = FieldFill.INSERT) + private Long deptId; + + /** + * 是否开启共享:0=否, 1=是 + */ + @ApiModelProperty(value = "是否开启共享:0=否, 1=是", name = "shareEnabled") + private Integer shareEnabled; + + /** + * 可见部门ID列表(逗号分隔) + */ + @ApiModelProperty(value = "可见部门ID列表(逗号分隔)", name = "shareDeptIds") + private String shareDeptIds; + + /** + * 共享部门ID列表(非表字段,用于接收前端传参) + */ + @ApiModelProperty(value = "共享部门ID列表", name = "shareDeptIdList") + @TableField(exist = false) + private List shareDeptIdList; + + /** + * 客群状态:0=正常, 1=已禁用 + */ + @ApiModelProperty(value = "客群状态:0=正常, 1=已禁用", name = "groupStatus") + private String groupStatus; + + /** + * 创建状态:0=创建中, 1=创建成功, 2=创建失败 + */ + @ApiModelProperty(value = "创建状态:0=创建中, 1=创建成功, 2=创建失败", name = "createStatus") + private String createStatus; + + /** + * 创建者 + */ + @TableField(fill = FieldFill.INSERT) + private String createBy; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** + * 更新者 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private String updateBy; + + /** + * 更新时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + + /** + * 删除标识:0=正常, 1=删除 + */ + @TableLogic(value = "0", delval = "1") + @TableField("del_flag") + private Integer delFlag; + + /** + * 备注 + */ + @ApiModelProperty(value = "备注", name = "remark") + private String remark; + + /** + * 网格类型:0=绩效网格, 1=地理网格, 2=绘制网格(动态客群使用) + */ + @ApiModelProperty(value = "网格类型", name = "gridType") + private String gridType; + + /** + * 绩效业务类型:retail=零售, corporate=公司(动态客群使用) + */ + @ApiModelProperty(value = "绩效业务类型", name = "cmpmBizType") + private String cmpmBizType; + + /** + * 客户经理列表(逗号分隔,动态客群使用) + */ + @ApiModelProperty(value = "客户经理列表", name = "gridUserNames") + private String gridUserNames; + + /** + * 地理网格ID列表(逗号分隔,动态客群使用) + */ + @ApiModelProperty(value = "地理网格ID列表", name = "regionGridIds") + private String regionGridIds; + + /** + * 绘制网格ID列表(逗号分隔,动态客群使用) + */ + @ApiModelProperty(value = "绘制网格ID列表", name = "drawGridIds") + private String drawGridIds; + + /** + * 有效期截止时间 + */ + @ApiModelProperty(value = "有效期截止时间", name = "validTime") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date validTime; + +} \ No newline at end of file diff --git a/ibs-group/src/main/java/com/ruoyi/group/domain/entity/CustGroupMember.java b/ibs-group/src/main/java/com/ruoyi/group/domain/entity/CustGroupMember.java new file mode 100644 index 0000000..9285dc3 --- /dev/null +++ b/ibs-group/src/main/java/com/ruoyi/group/domain/entity/CustGroupMember.java @@ -0,0 +1,89 @@ +package com.ruoyi.group.domain.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 客群客户关联实体 + * + * @author ruoyi + */ +@Data +@TableName("ibs_cust_group_member") +public class CustGroupMember { + + /** + * 主键ID + */ + @ApiModelProperty(value = "主键ID", name = "id") + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 客群ID + */ + @ApiModelProperty(value = "客群ID", name = "groupId") + private Long groupId; + + /** + * 客户类型:0=个人, 1=商户, 2=企业 + */ + @ApiModelProperty(value = "客户类型:0=个人, 1=商户, 2=企业", name = "custType") + private String custType; + + /** + * 客户号 + */ + @ApiModelProperty(value = "客户号", name = "custId") + private String custId; + + /** + * 客户姓名(冗余) + */ + @ApiModelProperty(value = "客户姓名(冗余)", name = "custName") + private String custName; + + /** + * 客户身份证号 + */ + @ApiModelProperty(value = "客户身份证号", name = "custIdc") + private String custIdc; + + /** + * 统信码(商户/企业有) + */ + @ApiModelProperty(value = "统信码(商户/企业有)", name = "socialCreditCode") + private String socialCreditCode; + + /** + * 创建者 + */ + @TableField(fill = FieldFill.INSERT) + private String createBy; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** + * 删除标识:0=正常, 1=删除 + */ + @TableLogic(value = "0", delval = "1") + @TableField("del_flag") + private Integer delFlag; + + /** + * 手动移除标识:0=否, 1=是(被手动移除的客户不会被定时任务重新导入) + */ + @ApiModelProperty(value = "手动移除标识:0=否, 1=是", name = "manualRemove") + @TableField("manual_remove") + private Integer manualRemove; +} \ No newline at end of file diff --git a/ibs-group/src/main/java/com/ruoyi/group/domain/vo/CustGroupMemberVO.java b/ibs-group/src/main/java/com/ruoyi/group/domain/vo/CustGroupMemberVO.java new file mode 100644 index 0000000..581ac38 --- /dev/null +++ b/ibs-group/src/main/java/com/ruoyi/group/domain/vo/CustGroupMemberVO.java @@ -0,0 +1,66 @@ +package com.ruoyi.group.domain.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 客群客户VO + * + * @author ruoyi + */ +@Data +public class CustGroupMemberVO { + + /** + * 主键ID + */ + @ApiModelProperty(value = "主键ID", name = "id") + private Long id; + + /** + * 客群ID + */ + @ApiModelProperty(value = "客群ID", name = "groupId") + private Long groupId; + + /** + * 客户类型:0=个人, 1=商户, 2=企业 + */ + @ApiModelProperty(value = "客户类型:0=个人, 1=商户, 2=企业", name = "custType") + private String custType; + + /** + * 客户号 + */ + @ApiModelProperty(value = "客户号", name = "custId") + private String custId; + + /** + * 客户姓名 + */ + @ApiModelProperty(value = "客户姓名", name = "custName") + private String custName; + + /** + * 客户身份证号 + */ + @ApiModelProperty(value = "客户身份证号", name = "custIdc") + private String custIdc; + + /** + * 统信码(商户/企业有) + */ + @ApiModelProperty(value = "统信码(商户/企业有)", name = "socialCreditCode") + private String socialCreditCode; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty(value = "创建时间", name = "createTime") + private Date createTime; +} \ No newline at end of file diff --git a/ibs-group/src/main/java/com/ruoyi/group/domain/vo/CustGroupVO.java b/ibs-group/src/main/java/com/ruoyi/group/domain/vo/CustGroupVO.java new file mode 100644 index 0000000..ebd491d --- /dev/null +++ b/ibs-group/src/main/java/com/ruoyi/group/domain/vo/CustGroupVO.java @@ -0,0 +1,122 @@ +package com.ruoyi.group.domain.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * 客群VO + * + * @author ruoyi + */ +@Data +public class CustGroupVO { + + /** + * 主键ID + */ + @ApiModelProperty(value = "主键ID", name = "id") + private Long id; + + /** + * 客群名称 + */ + @ApiModelProperty(value = "客群名称", name = "groupName") + private String groupName; + + /** + * 客群模式:0=静态, 1=动态 + */ + @ApiModelProperty(value = "客群模式:0=静态, 1=动态", name = "groupMode") + private String groupMode; + + /** + * 创建方式:1=模板导入, 2=绩效网格, 3=地理网格, 4=自定义网格 + */ + @ApiModelProperty(value = "创建方式:1=模板导入, 2=绩效网格, 3=地理网格, 4=自定义网格", name = "createMode") + private String createMode; + + /** + * 柜员号 + */ + @ApiModelProperty(value = "柜员号", name = "userName") + private String userName; + + /** + * 柜员名称 + */ + @ApiModelProperty(value = "柜员名称", name = "nickName") + private String nickName; + + /** + * 所属机构ID + */ + @ApiModelProperty(value = "所属机构ID", name = "deptId") + private Long deptId; + + /** + * 是否开启共享:0=否, 1=是 + */ + @ApiModelProperty(value = "是否开启共享:0=否, 1=是", name = "shareEnabled") + private Integer shareEnabled; + + /** + * 可见部门ID列表 + */ + @ApiModelProperty(value = "可见部门ID列表", name = "shareDeptIds") + private List shareDeptIds; + + /** + * 客群状态:0=正常, 1=已禁用 + */ + @ApiModelProperty(value = "客群状态:0=正常, 1=已禁用", name = "groupStatus") + private String groupStatus; + + /** + * 客户数量 + */ + @ApiModelProperty(value = "客户数量", name = "custCount") + private Integer custCount; + + /** + * 创建者 + */ + @ApiModelProperty(value = "创建者", name = "createBy") + private String createBy; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty(value = "创建时间", name = "createTime") + private Date createTime; + + /** + * 更新者 + */ + @ApiModelProperty(value = "更新者", name = "updateBy") + private String updateBy; + + /** + * 更新时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty(value = "更新时间", name = "updateTime") + private Date updateTime; + + /** + * 备注 + */ + @ApiModelProperty(value = "备注", name = "remark") + private String remark; + + /** + * 客户列表 + */ + @ApiModelProperty(value = "客户列表", name = "custList") + private List custList; +} \ No newline at end of file diff --git a/ibs-group/src/main/java/com/ruoyi/group/mapper/CustGroupMapper.java b/ibs-group/src/main/java/com/ruoyi/group/mapper/CustGroupMapper.java new file mode 100644 index 0000000..8a42411 --- /dev/null +++ b/ibs-group/src/main/java/com/ruoyi/group/mapper/CustGroupMapper.java @@ -0,0 +1,34 @@ +package com.ruoyi.group.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.group.domain.dto.CustGroupQueryDTO; +import com.ruoyi.group.domain.entity.CustGroup; +import com.ruoyi.group.domain.vo.CustGroupVO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 客群Mapper接口 + * + * @author ruoyi + */ +public interface CustGroupMapper extends BaseMapper { + + /** + * 查询客群数量(根据部门ID和共享设置) + * + * @param deptId 部门ID + * @param userName 用户名 + * @return 数量 + */ +// Long countByDeptOrUser(@Param("deptId") Long deptId, @Param("userName") Long userName); + + /** + * 查询客群列表 + * + * @param dto 查询条件 + * @return 客群VO列表 + */ + List selectCustGroupList(@Param("dto") CustGroupQueryDTO dto); +} \ No newline at end of file diff --git a/ibs-group/src/main/java/com/ruoyi/group/mapper/CustGroupMemberMapper.java b/ibs-group/src/main/java/com/ruoyi/group/mapper/CustGroupMemberMapper.java new file mode 100644 index 0000000..dfff965 --- /dev/null +++ b/ibs-group/src/main/java/com/ruoyi/group/mapper/CustGroupMemberMapper.java @@ -0,0 +1,21 @@ +package com.ruoyi.group.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.group.domain.entity.CustGroupMember; +import org.apache.ibatis.annotations.Param; + +/** + * 客群客户关联Mapper接口 + * + * @author ruoyi + */ +public interface CustGroupMemberMapper extends BaseMapper { + + /** + * 查询客群客户数量 + * + * @param groupId 客群ID + * @return 数量 + */ + Long countByGroupId(@Param("groupId") Long groupId); +} \ No newline at end of file diff --git a/ibs-group/src/main/java/com/ruoyi/group/service/ICustGroupService.java b/ibs-group/src/main/java/com/ruoyi/group/service/ICustGroupService.java new file mode 100644 index 0000000..1a6debd --- /dev/null +++ b/ibs-group/src/main/java/com/ruoyi/group/service/ICustGroupService.java @@ -0,0 +1,120 @@ +package com.ruoyi.group.service; + +import com.ruoyi.group.domain.dto.CustGroupQueryDTO; +import com.ruoyi.group.domain.dto.GridImportDTO; +import com.ruoyi.group.domain.entity.CustGroup; +import com.ruoyi.group.domain.vo.CustGroupVO; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + * 客群Service接口 + * + * @author ruoyi + */ +public interface ICustGroupService { + + /** + * 查询客群列表 + * + * @param dto 查询条件 + * @return 客群VO列表 + */ + List listCustGroup(CustGroupQueryDTO dto); + + /** + * 异步创建客群(模板导入) + * + * @param custGroup 客群实体 + * @param file Excel文件 + * @return 客群ID + */ + String createCustGroupByTemplate(CustGroup custGroup, MultipartFile file); + + /** + * 异步创建客群(网格导入) + * + * @param gridImportDTO 网格导入条件 + * @return 客群ID + */ + String createCustGroupByGrid(GridImportDTO gridImportDTO); + + /** + * 更新客群(网格导入) + * + * @param gridImportDTO 网格导入条件 + * @return 结果消息 + */ + String updateCustGroupByGrid(GridImportDTO gridImportDTO); + + /** + * 更新客群(模板导入) + * + * @param custGroup 客群实体 + * @param file Excel文件 + * @return 结果消息 + */ + String updateCustGroupByTemplate(CustGroup custGroup, MultipartFile file); + + /** + * 更新客群 + * + * @param custGroup 客群实体 + * @return 结果消息 + */ + String updateCustGroup(CustGroup custGroup); + + /** + * 删除客群 + * + * @param idList 客群ID列表 + * @return 结果消息 + */ + String deleteCustGroup(List idList); + + /** + * 获取客群详情 + * + * @param id 客群ID + * @return 客群VO + */ + CustGroupVO getCustGroup(Long id); + + /** + * 检查客群名称是否存在 + * + * @param groupName 客群名称 + * @return true=存在, false=不存在 + */ + boolean checkGroupNameExist(String groupName); + + /** + * 查询客群创建状态 + * + * @param id 客群ID + * @return 创建状态:0=创建中, 1=创建成功, 2=创建失败 + */ + String getCreateStatus(Long id); + + /** + * 手动移除客群客户 + * + * @param groupId 客群ID + * @param memberIds 客群成员ID列表 + * @return 结果消息 + */ + String removeMembers(Long groupId, List memberIds); + + /** + * 更新动态客群(定时任务调用) + * 根据原始导入条件重新查询并更新客户列表 + */ + void updateDynamicCustGroups(); + + /** + * 检查并禁用过期客群(定时任务调用) + */ + void checkAndDisableExpiredGroups(); + +} \ No newline at end of file diff --git a/ibs-group/src/main/java/com/ruoyi/group/service/impl/CustGroupServiceImpl.java b/ibs-group/src/main/java/com/ruoyi/group/service/impl/CustGroupServiceImpl.java new file mode 100644 index 0000000..aa4df13 --- /dev/null +++ b/ibs-group/src/main/java/com/ruoyi/group/service/impl/CustGroupServiceImpl.java @@ -0,0 +1,817 @@ +package com.ruoyi.group.service.impl; + +import com.alibaba.excel.EasyExcel; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.ruoyi.common.core.domain.entity.SysDept; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.mapper.SysDeptMapper; +import com.ruoyi.ibs.cmpm.domain.entity.GridCmpm; +import com.ruoyi.ibs.cmpm.mapper.GridCmpmMapper; +import com.ruoyi.ibs.draw.domain.entity.DrawGridShapeRelate; +import com.ruoyi.ibs.draw.domain.entity.DrawShapeCust; +import com.ruoyi.ibs.draw.mapper.DrawGridShapeRelateMapper; +import com.ruoyi.ibs.draw.mapper.DrawShapeCustMapper; +import com.ruoyi.ibs.grid.domain.vo.CustVO; +import com.ruoyi.ibs.grid.service.RegionGridListService; +import com.ruoyi.group.domain.dto.CustGroupMemberTemplate; +import com.ruoyi.group.domain.dto.CustGroupQueryDTO; +import com.ruoyi.group.domain.dto.GridImportDTO; +import com.ruoyi.group.domain.entity.CustGroup; +import com.ruoyi.group.domain.entity.CustGroupMember; +import com.ruoyi.group.domain.vo.CustGroupMemberVO; +import com.ruoyi.group.domain.vo.CustGroupVO; +import com.ruoyi.group.mapper.CustGroupMapper; +import com.ruoyi.group.mapper.CustGroupMemberMapper; +import com.ruoyi.group.service.ICustGroupService; +import com.ruoyi.ibs.grid.domain.entity.RegionCustUser; +import com.ruoyi.ibs.grid.domain.entity.RegionGrid; +import com.ruoyi.ibs.grid.mapper.RegionCustUserMapper; +import com.ruoyi.ibs.grid.mapper.RegionGridMapper; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.stream.Collectors; + +/** + * 客群Service实现 + * + * @author ruoyi + */ +@Service +@Slf4j +public class CustGroupServiceImpl implements ICustGroupService { + + @Resource + private CustGroupMapper custGroupMapper; + + @Resource + private CustGroupMemberMapper custGroupMemberMapper; + + @Resource(name = "excelImportExecutor") + private ExecutorService executorService; + + @Resource + private GridCmpmMapper gridCmpmMapper; + + @Resource + private RegionCustUserMapper regionCustUserMapper; + + @Resource + private RegionGridMapper regionGridMapper; + + @Resource + private RegionGridListService regionGridListService; + + @Resource + private DrawShapeCustMapper drawShapeCustMapper; + + @Resource + private DrawGridShapeRelateMapper drawGridShapeRelateMapper; + + @Resource + private SysDeptMapper sysDeptMapper; + + @Override + public List listCustGroup(CustGroupQueryDTO dto) { + return custGroupMapper.selectCustGroupList(dto); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String createCustGroupByTemplate(CustGroup custGroup, MultipartFile file) { + // 检查客群名称是否存在 + if (checkGroupNameExist(custGroup.getGroupName())) { + throw new ServiceException("客群名称已存在"); + } + // 获取当前用户信息 + SysUser user = SecurityUtils.getLoginUser().getUser(); + custGroup.setUserName(user.getUserName()); + custGroup.setNickName(user.getNickName()); + // 设置默认值 + custGroup.setGroupMode("0"); // 静态 + custGroup.setCreateMode("1"); // 模板导入 + if (StringUtils.isEmpty(custGroup.getGroupStatus())) { + custGroup.setGroupStatus("0"); + } + // 设置创建状态为创建中 + custGroup.setCreateStatus("0"); + // 插入客群 + custGroupMapper.insert(custGroup); + // 获取当前用户部门编码(异步线程中无法获取) + String headId = SecurityUtils.getHeadId(); + // 异步导入客户 + executorService.submit(() -> doImportCustGroupByTemplate(custGroup, file, headId)); + return String.valueOf(custGroup.getId()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String createCustGroupByGrid(GridImportDTO gridImportDTO) { + CustGroup custGroup = gridImportDTO.getCustGroup(); + // 检查客群名称是否存在 + if (checkGroupNameExist(custGroup.getGroupName())) { + throw new ServiceException("客群名称已存在"); + } + // 获取当前用户信息 + SysUser user = SecurityUtils.getLoginUser().getUser(); + custGroup.setUserName(user.getUserName()); + custGroup.setNickName(user.getNickName()); + // 默认状态为正常 + if (StringUtils.isEmpty(custGroup.getGroupStatus())) { + custGroup.setGroupStatus("0"); + } + // 设置创建模式和状态 + custGroup.setGroupMode(custGroup.getGroupMode()); + // 根据网格类型设置创建方式:0=模板导入, 2=绩效网格, 3=地理网格, 4=绘制网格 + String gridType = gridImportDTO.getGridType(); + if ("0".equals(gridType)) { + custGroup.setCreateMode("2"); // 绩效网格 + } else if ("1".equals(gridType)) { + custGroup.setCreateMode("3"); // 地理网格 + } else if ("2".equals(gridType)) { + custGroup.setCreateMode("4"); // 绘制网格 + } else { + throw new ServiceException("无效的网格类型"); + } + + // 保存网格导入条件(用于动态客群后续更新) + custGroup.setGridType(gridImportDTO.getGridType()); + if ("0".equals(gridType)) { + custGroup.setCmpmBizType(gridImportDTO.getCmpmBizType()); + if (gridImportDTO.getUserNames() != null && !gridImportDTO.getUserNames().isEmpty()) { + custGroup.setGridUserNames(String.join(",", gridImportDTO.getUserNames())); + } + } else if ("1".equals(gridType)) { + if (gridImportDTO.getRegionGridIds() != null && !gridImportDTO.getRegionGridIds().isEmpty()) { + custGroup.setRegionGridIds(gridImportDTO.getRegionGridIds().stream() + .map(String::valueOf).collect(Collectors.joining(","))); + } + } else if ("2".equals(gridType)) { + if (gridImportDTO.getDrawGridIds() != null && !gridImportDTO.getDrawGridIds().isEmpty()) { + custGroup.setDrawGridIds(gridImportDTO.getDrawGridIds().stream() + .map(String::valueOf).collect(Collectors.joining(","))); + } + } + + // 设置创建状态为创建中 + custGroup.setCreateStatus("0"); + // 插入客群 + custGroupMapper.insert(custGroup); + // 重新设置回DTO(确保异步线程能获取到正确的ID和状态) + gridImportDTO.setCustGroup(custGroup); + // 获取当前用户部门编码(异步线程中无法获取) + String headId = SecurityUtils.getHeadId(); + // 异步导入客户 + executorService.submit(() -> doImportCustGroupByGrid(gridImportDTO, headId)); + return String.valueOf(custGroup.getId()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String updateCustGroup(CustGroup custGroup) { + CustGroup existGroup = custGroupMapper.selectById(custGroup.getId()); + if (existGroup == null) { + throw new ServiceException("客群不存在"); + } + // 检查客群名称是否存在(排除自己) + if (!existGroup.getGroupName().equals(custGroup.getGroupName()) && checkGroupNameExist(custGroup.getGroupName())) { + throw new ServiceException("客群名称已存在"); + } + custGroupMapper.updateById(custGroup); + return "客群更新成功"; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String updateCustGroupByGrid(GridImportDTO gridImportDTO) { + CustGroup custGroup = gridImportDTO.getCustGroup(); + // 检查客群是否存在 + CustGroup existGroup = custGroupMapper.selectById(custGroup.getId()); + if (existGroup == null) { + throw new ServiceException("客群不存在"); + } + // 更新客群基本信息 + if (!existGroup.getGroupName().equals(custGroup.getGroupName()) && checkGroupNameExist(custGroup.getGroupName())) { + throw new ServiceException("客群名称已存在"); + } + // 重新查询数据库,获取最新状态 + CustGroup latestGroup = custGroupMapper.selectById(custGroup.getId()); + latestGroup.setGroupName(custGroup.getGroupName()); + latestGroup.setGroupMode(custGroup.getGroupMode()); + latestGroup.setRemark(custGroup.getRemark()); + latestGroup.setValidTime(custGroup.getValidTime()); + latestGroup.setShareEnabled(custGroup.getShareEnabled()); + latestGroup.setShareDeptIds(custGroup.getShareDeptIds()); + // 更新数据库 + custGroupMapper.updateById(latestGroup); + // 重新设置回DTO(确保异步线程能获取到正确的ID和状态) + gridImportDTO.setCustGroup(latestGroup); + // 获取当前用户部门编码(异步线程中无法获取) + String headId = SecurityUtils.getHeadId(); + // 异步导入客户 + executorService.submit(() -> doImportCustGroupByGrid(gridImportDTO, headId)); + return "客群更新中"; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String updateCustGroupByTemplate(CustGroup custGroup, MultipartFile file) { + // 检查客群是否存在 + CustGroup existGroup = custGroupMapper.selectById(custGroup.getId()); + if (existGroup == null) { + throw new ServiceException("客群不存在"); + } + // 更新客群基本信息 + if (!existGroup.getGroupName().equals(custGroup.getGroupName()) && checkGroupNameExist(custGroup.getGroupName())) { + throw new ServiceException("客群名称已存在"); + } + // 重新查询数据库,获取最新状态 + CustGroup latestGroup = custGroupMapper.selectById(custGroup.getId()); + latestGroup.setGroupName(custGroup.getGroupName()); + latestGroup.setGroupMode(custGroup.getGroupMode()); + latestGroup.setRemark(custGroup.getRemark()); + latestGroup.setValidTime(custGroup.getValidTime()); + latestGroup.setShareEnabled(custGroup.getShareEnabled()); + latestGroup.setShareDeptIds(custGroup.getShareDeptIds()); + // 更新数据库 + custGroupMapper.updateById(latestGroup); + // 获取当前用户部门编码(异步线程中无法获取) + String headId = SecurityUtils.getHeadId(); + // 异步导入客户(使用最新状态的对象) + executorService.submit(() -> doImportCustGroupByTemplate(latestGroup, file, headId)); + return "客群更新中"; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String deleteCustGroup(List idList) { + if (idList == null || idList.isEmpty()) { + throw new ServiceException("请选择要删除的客群"); + } + for (Long id : idList) { + // 删除客群客户关联 + LambdaQueryWrapper memberWrapper = new LambdaQueryWrapper<>(); + memberWrapper.eq(CustGroupMember::getGroupId, id); + custGroupMemberMapper.delete(memberWrapper); + // 删除客群 + custGroupMapper.deleteById(id); + } + return "客群删除成功"; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String removeMembers(Long groupId, List memberIds) { + if (memberIds == null || memberIds.isEmpty()) { + throw new ServiceException("请选择要移除的客户"); + } + // 检查客群是否存在 + CustGroup custGroup = custGroupMapper.selectById(groupId); + if (custGroup == null) { + throw new ServiceException("客群不存在"); + } + // 标记为手动移除(软删除) + for (Long memberId : memberIds) { + CustGroupMember member = custGroupMemberMapper.selectById(memberId); + if (member != null && member.getGroupId().equals(groupId)) { + member.setManualRemove(1); + member.setDelFlag(1); + custGroupMemberMapper.updateById(member); + } + } + return "成功移除 " + memberIds.size() + " 个客户"; + } + + @Override + public CustGroupVO getCustGroup(Long id) { + CustGroup custGroup = custGroupMapper.selectById(id); + if (custGroup == null) { + throw new ServiceException("客群不存在"); + } + CustGroupVO vo = new CustGroupVO(); + vo.setId(custGroup.getId()); + vo.setGroupName(custGroup.getGroupName()); + vo.setGroupMode(custGroup.getGroupMode()); + vo.setCreateMode(custGroup.getCreateMode()); + vo.setUserName(custGroup.getUserName()); + vo.setNickName(custGroup.getNickName()); + vo.setDeptId(custGroup.getDeptId()); + vo.setShareEnabled(custGroup.getShareEnabled()); + vo.setGroupStatus(custGroup.getGroupStatus()); + vo.setRemark(custGroup.getRemark()); + vo.setCreateBy(custGroup.getCreateBy()); + vo.setCreateTime(custGroup.getCreateTime()); + vo.setUpdateBy(custGroup.getUpdateBy()); + vo.setUpdateTime(custGroup.getUpdateTime()); + // 处理共享部门ID列表 + if (StringUtils.isNotEmpty(custGroup.getShareDeptIds())) { + List deptIds = new ArrayList<>(); + for (String idStr : custGroup.getShareDeptIds().split(",")) { + if (StringUtils.isNotEmpty(idStr)) { + deptIds.add(Long.valueOf(idStr)); + } + } + vo.setShareDeptIds(deptIds); + } + // 查询客户列表 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(CustGroupMember::getGroupId, id); + List memberList = custGroupMemberMapper.selectList(wrapper); + List memberVOList = new ArrayList<>(); + for (CustGroupMember member : memberList) { + CustGroupMemberVO memberVO = new CustGroupMemberVO(); + memberVO.setId(member.getId()); + memberVO.setGroupId(member.getGroupId()); + memberVO.setCustType(member.getCustType()); + memberVO.setCustId(member.getCustId()); + memberVO.setCustName(member.getCustName()); + memberVO.setCustIdc(member.getCustIdc()); + memberVO.setSocialCreditCode(member.getSocialCreditCode()); + memberVO.setCreateTime(member.getCreateTime()); + memberVOList.add(memberVO); + } + vo.setCustList(memberVOList); + vo.setCustCount(memberVOList.size()); + return vo; + } + + @Override + public boolean checkGroupNameExist(String groupName) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(CustGroup::getGroupName, groupName); + return custGroupMapper.selectCount(wrapper) > 0; + } + + @Override + public String getCreateStatus(Long id) { + CustGroup custGroup = custGroupMapper.selectById(id); + if (custGroup == null) { + throw new ServiceException("客群不存在"); + } + return custGroup.getCreateStatus(); + } + + @Override + public void updateDynamicCustGroups() { + log.info("开始更新动态客群..."); + + // 查询所有动态客群 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(CustGroup::getGroupMode, "1"); // 1=动态 + wrapper.eq(CustGroup::getGroupStatus, "0"); // 0=正常 + wrapper.eq(CustGroup::getCreateStatus, "1"); // 1=创建成功 + List dynamicGroups = custGroupMapper.selectList(wrapper); + + if (dynamicGroups.isEmpty()) { + log.info("没有需要更新的动态客群"); + return; + } + + log.info("找到 {} 个动态客群需要更新", dynamicGroups.size()); + Date now = new Date(); + + for (CustGroup custGroup : dynamicGroups) { + // 检查有效期,过期的客群跳过更新 + if (custGroup.getValidTime() != null && custGroup.getValidTime().before(now)) { + log.info("动态客群已过期,跳过更新,客群ID:{},客群名称:{},有效期:{}", + custGroup.getId(), custGroup.getGroupName(), custGroup.getValidTime()); + continue; + } + + try { + updateDynamicCustGroup(custGroup); + log.info("动态客群更新成功,客群ID:{},客群名称:{}", custGroup.getId(), custGroup.getGroupName()); + } catch (Exception e) { + log.error("动态客群更新失败,客群ID:{},客群名称:{}", custGroup.getId(), custGroup.getGroupName(), e); + } + } + + log.info("动态客群更新完成,总计:{},成功:{},失败:{}", + dynamicGroups.size(), + dynamicGroups.stream().filter(g -> { + // 假设更新成功的状态设置 + LambdaQueryWrapper w = new LambdaQueryWrapper<>(); + w.eq(CustGroup::getId, g.getId()); + // 这里简单统计,实际可以通过更精确的方式 + return true; + }).count(), + 0); + } + + @Override + public void checkAndDisableExpiredGroups() { + log.info("开始检查并禁用过期客群..."); + + Date now = new Date(); + + // 查询所有正常状态且有过期时间的客群 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(CustGroup::getGroupStatus, "0"); // 0=正常 + wrapper.isNotNull(CustGroup::getValidTime); + wrapper.le(CustGroup::getValidTime, now); // validTime <= now + List expiredGroups = custGroupMapper.selectList(wrapper); + + if (expiredGroups.isEmpty()) { + log.info("没有需要禁用的过期客群"); + return; + } + + log.info("找到 {} 个过期客群需要禁用", expiredGroups.size()); + + for (CustGroup custGroup : expiredGroups) { + try { + custGroup.setGroupStatus("1"); // 1=已禁用 + custGroupMapper.updateById(custGroup); + log.info("客群已禁用,客群ID:{},客群名称:{},有效期:{}", + custGroup.getId(), custGroup.getGroupName(), custGroup.getValidTime()); + } catch (Exception e) { + log.error("禁用过期客群失败,客群ID:{},客群名称:{}", custGroup.getId(), custGroup.getGroupName(), e); + } + } + + log.info("过期客群禁用完成,总计:{}", expiredGroups.size()); + } + + /** + * 更新单个动态客群(增量更新方式) + */ + private void updateDynamicCustGroup(CustGroup custGroup) { + // 从客群部门获取 headId(定时任务中无登录用户上下文) + // headId 是 deptId 的前三位 + String headId = ""; + if (custGroup.getDeptId() != null) { + String deptIdStr = String.valueOf(custGroup.getDeptId()); + headId = deptIdStr.substring(0, 3); + } + + // 查询现有客户(排除手动移除的客户,只查询正常状态的客户) + LambdaQueryWrapper existWrapper = new LambdaQueryWrapper<>(); + existWrapper.eq(CustGroupMember::getGroupId, custGroup.getId()) + .eq(CustGroupMember::getDelFlag, 0) + .eq(CustGroupMember::getManualRemove, 0) + .select(CustGroupMember::getCustId, CustGroupMember::getCustType); + List existMembers = custGroupMemberMapper.selectList(existWrapper); + Set existKeySet = existMembers.stream() + .map(m -> m.getCustId() + "_" + m.getCustType()) + .collect(Collectors.toSet()); + + // 构造 GridImportDTO 复用现有导入方法 + GridImportDTO gridImportDTO = new GridImportDTO(); + gridImportDTO.setCustGroup(custGroup); + gridImportDTO.setGridType(custGroup.getGridType()); + gridImportDTO.setCmpmBizType(custGroup.getCmpmBizType()); + + // 根据网格类型设置参数并查询 + List newMemberList = new ArrayList<>(); + String gridType = custGroup.getGridType(); + if ("0".equals(gridType)) { + // 绩效网格 + if (StringUtils.isNotEmpty(custGroup.getGridUserNames())) { + gridImportDTO.setUserNames(Arrays.asList(custGroup.getGridUserNames().split(","))); + } + newMemberList.addAll(importFromCmpmGrid(custGroup, gridImportDTO, headId)); + } else if ("1".equals(gridType)) { + // 地理网格 + if (StringUtils.isNotEmpty(custGroup.getRegionGridIds())) { + gridImportDTO.setRegionGridIds(Arrays.stream(custGroup.getRegionGridIds().split(",")) + .map(Long::valueOf).collect(Collectors.toList())); + } + newMemberList.addAll(importFromRegionGrid(custGroup, gridImportDTO)); + } else if ("2".equals(gridType)) { + // 绘制网格 + if (StringUtils.isNotEmpty(custGroup.getDrawGridIds())) { + gridImportDTO.setDrawGridIds(Arrays.stream(custGroup.getDrawGridIds().split(",")) + .map(Long::valueOf).collect(Collectors.toList())); + } + newMemberList.addAll(importFromDrawGrid(custGroup, gridImportDTO)); + } + + // 计算差异 + Set newKeySet = newMemberList.stream() + .map(m -> m.getCustId() + "_" + m.getCustType()) + .collect(Collectors.toSet()); + + // 需要删除的客户:存在于旧列表但不存在于新列表 + List toDeleteKeys = new ArrayList<>(existKeySet); + toDeleteKeys.removeAll(newKeySet); + // 需要新增的客户:存在于新列表但不存在于旧列表 + List toAddMembers = newMemberList.stream() + .filter(m -> !existKeySet.contains(m.getCustId() + "_" + m.getCustType())) + .collect(Collectors.toList()); + + // 删除不再存在的客户 + if (!toDeleteKeys.isEmpty()) { + for (String key : toDeleteKeys) { + String[] parts = key.split("_"); + LambdaQueryWrapper deleteWrapper = new LambdaQueryWrapper<>(); + deleteWrapper.eq(CustGroupMember::getGroupId, custGroup.getId()) + .eq(CustGroupMember::getCustId, parts[0]) + .eq(CustGroupMember::getCustType, parts[1]); + custGroupMemberMapper.delete(deleteWrapper); + } + } + + // 插入新增客户 + int addCount = 0; + for (CustGroupMember member : toAddMembers) { + try { + custGroupMemberMapper.insert(member); + addCount++; + } catch (org.springframework.dao.DuplicateKeyException e) { + log.debug("客户已存在,跳过:groupId={}, custId={}", member.getGroupId(), member.getCustId()); + } + } + + log.info("客群客户更新完成,客群ID:{},原数量:{},新数量:{},新增:{},删除:{}", + custGroup.getId(), existMembers.size(), newMemberList.size(), addCount, toDeleteKeys.size()); + } + + /** + * 异步导入客户 + */ + private void doImportCustGroupByTemplate(CustGroup custGroup, MultipartFile file, String headId) { + try { + // 解析Excel + List templateList = EasyExcel.read(file.getInputStream()) + .head(CustGroupMemberTemplate.class) + .sheet() + .doReadSync(); + if (templateList.isEmpty()) { + throw new ServiceException("导入列表为空"); + } + // 校验和去重 + Set uniqueCustIds = new HashSet<>(); + List memberList = new ArrayList<>(); + for (CustGroupMemberTemplate template : templateList) { + // 校验客户类型 + String custType = template.getCustType(); + if (StringUtils.isEmpty(custType)) { + throw new ServiceException("客户类型不能为空"); + } + // 转换客户类型值 + String custTypeValue; + switch (custType) { + case "个人": + custTypeValue = "0"; + break; + case "企业": + custTypeValue = "2"; + break; + case "商户": + custTypeValue = "1"; + break; + default: + throw new ServiceException("客户类型填写错误,只能填写:个人、企业、商户"); + } + // 客户号不能为空 + if (StringUtils.isEmpty(template.getCustId())) { + throw new ServiceException("客户号不能为空"); + } + // 检查重复 + if (!uniqueCustIds.add(template.getCustId())) { + continue; // 跳过重复客户 + } + // 创建客户成员 + CustGroupMember member = new CustGroupMember(); + member.setGroupId(custGroup.getId()); + member.setCustType(custTypeValue); + member.setCustId(template.getCustId()); + member.setCustName(template.getCustName()); + member.setCustIdc(template.getCustIdc()); + member.setSocialCreditCode(template.getSocialCreditCode()); + member.setCreateTime(new Date()); + // 个人客户必须有身份证号 + if ("0".equals(custTypeValue) && StringUtils.isEmpty(member.getCustIdc())) { + throw new ServiceException("个人客户[" + template.getCustId() + "]身份证号不能为空"); + } + // 企业/商户客户必须有统信码 + if (("1".equals(custTypeValue) || "2".equals(custTypeValue)) + && StringUtils.isEmpty(member.getSocialCreditCode())) { + throw new ServiceException("企业/商户客户[" + template.getCustId() + "]统信码不能为空"); + } + memberList.add(member); + } + // 批量插入 + int batchSize = 1000; + int successCount = 0; + int skippedCount = 0; + int restoredCount = 0; + for (int i = 0; i < memberList.size(); i += batchSize) { + int endIndex = Math.min(i + batchSize, memberList.size()); + List batchList = memberList.subList(i, endIndex); + for (CustGroupMember member : batchList) { + try { + custGroupMemberMapper.insert(member); + successCount++; + } catch (DuplicateKeyException e) { + // 客户已存在,检查是否是被手动移除的 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(CustGroupMember::getGroupId, member.getGroupId()) + .eq(CustGroupMember::getCustId, member.getCustId()) + .eq(CustGroupMember::getCustType, member.getCustType()); + CustGroupMember existMember = custGroupMemberMapper.selectOne(queryWrapper); + if (existMember != null && existMember.getManualRemove() != null && existMember.getManualRemove() == 1) { + // 是被手动移除的客户,清除标记并恢复 + existMember.setManualRemove(0); + existMember.setDelFlag(0); + existMember.setCustName(member.getCustName()); + custGroupMemberMapper.updateById(existMember); + restoredCount++; + log.debug("恢复手动移除的客户:groupId={}, custId={}", member.getGroupId(), member.getCustId()); + } else { + // 正常存在的客户,跳过 + skippedCount++; + log.debug("客户已存在,跳过:groupId={}, custId={}", member.getGroupId(), member.getCustId()); + } + } + } + } + log.info("客群客户导入完成(模板),客群ID:{},成功:{},跳过重复:{},恢复:{}", + custGroup.getId(), successCount, skippedCount, restoredCount); + // 更新创建状态为成功 + custGroup.setCreateStatus("1"); + custGroupMapper.updateById(custGroup); + } catch (Exception e) { + // 更新创建状态为失败 + custGroup.setCreateStatus("2"); + custGroupMapper.updateById(custGroup); + log.error("客群客户导入失败,客群ID:{}", custGroup.getId(), e); + throw new ServiceException("客群客户导入失败: " + e.getMessage()); + } + } + + /** + * 异步导入客户(网格方式) + */ + private void doImportCustGroupByGrid(GridImportDTO gridImportDTO, String headId) { + CustGroup custGroup = gridImportDTO.getCustGroup(); + try { + List memberList = new ArrayList<>(); + String gridType = gridImportDTO.getGridType(); + // 根据网格类型查询客户 + if ("0".equals(gridType)) { + // 绩效网格 + memberList.addAll(importFromCmpmGrid(custGroup, gridImportDTO, headId)); + } else if ("1".equals(gridType)) { + // 地理网格 + memberList.addAll(importFromRegionGrid(custGroup, gridImportDTO)); + } else if ("2".equals(gridType)) { + // 绘制网格 + memberList.addAll(importFromDrawGrid(custGroup, gridImportDTO)); + } + if (memberList.isEmpty()) { + throw new ServiceException("未查询到任何客户"); + } + // 批量插入 + int batchSize = 1000; + int successCount = 0; + int skippedCount = 0; + int restoredCount = 0; + for (int i = 0; i < memberList.size(); i += batchSize) { + int endIndex = Math.min(i + batchSize, memberList.size()); + List batchList = memberList.subList(i, endIndex); + for (CustGroupMember member : batchList) { + try { + custGroupMemberMapper.insert(member); + successCount++; + } catch (DuplicateKeyException e) { + // 客户已存在,检查是否是被手动移除的 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(CustGroupMember::getGroupId, member.getGroupId()) + .eq(CustGroupMember::getCustId, member.getCustId()) + .eq(CustGroupMember::getCustType, member.getCustType()); + CustGroupMember existMember = custGroupMemberMapper.selectOne(queryWrapper); + if (existMember != null && existMember.getManualRemove() != null && existMember.getManualRemove() == 1) { + // 是被手动移除的客户,清除标记并恢复 + existMember.setManualRemove(0); + existMember.setDelFlag(0); + existMember.setCustName(member.getCustName()); + custGroupMemberMapper.updateById(existMember); + restoredCount++; + log.debug("恢复手动移除的客户:groupId={}, custId={}", member.getGroupId(), member.getCustId()); + } else { + // 正常存在的客户,跳过 + skippedCount++; + log.debug("客户已存在,跳过:groupId={}, custId={}", member.getGroupId(), member.getCustId()); + } + } + } + } + log.info("客群客户导入完成(网格),客群ID:{},成功:{},跳过重复:{},恢复:{}", + custGroup.getId(), successCount, skippedCount, restoredCount); + // 更新创建状态为成功 + custGroup.setCreateStatus("1"); + custGroupMapper.updateById(custGroup); + } catch (Exception e) { + // 更新创建状态为失败 + custGroup.setCreateStatus("2"); + custGroupMapper.updateById(custGroup); + log.error("客群客户导入失败,客群ID:{}", custGroup.getId(), e); + throw new ServiceException("客群客户导入失败: " + e.getMessage()); + } + } + + /** + * 从绩效网格导入客户 + */ + private List importFromCmpmGrid(CustGroup custGroup, GridImportDTO gridImportDTO, String headId) { + List memberList = new ArrayList<>(); + // 确定要查询的网格类型 + List gridTypes = new ArrayList<>(); + String cmpmBizType = gridImportDTO.getCmpmBizType(); + if ("retail".equals(cmpmBizType)) { + gridTypes.add("retail"); + } else if ("corporate".equals(cmpmBizType)) { + gridTypes.add("corporate"); + gridTypes.add("corporate_account"); + } else { + throw new ServiceException("请选择绩效网格业务类型(零售/公司)"); + } + // 查询客户 + for (String userName : gridImportDTO.getUserNames()) { + for (String gridType : gridTypes) { + List cmpmList = gridCmpmMapper.getGridCmpmByUserName(userName, headId, gridType); + for (GridCmpm cmpm : cmpmList) { + CustGroupMember member = new CustGroupMember(); + member.setGroupId(custGroup.getId()); + member.setCustId(cmpm.getCustId()); + member.setCustName(cmpm.getCustName()); + member.setCustType(cmpm.getCustType()); + member.setCustIdc(cmpm.getCustIdc()); + member.setSocialCreditCode(cmpm.getUsci()); + member.setCreateTime(new Date()); + memberList.add(member); + } + } + } + return memberList; + } + + /** + * 从地理网格导入客户 + */ + private List importFromRegionGrid(CustGroup custGroup, GridImportDTO gridImportDTO) { + List memberList = new ArrayList<>(); + // 查询地理网格获取编码 + if (gridImportDTO.getRegionGridIds() == null || gridImportDTO.getRegionGridIds().isEmpty()) { + throw new ServiceException("请选择地理网格"); + } + List regionGrids = regionGridMapper.selectBatchIds(gridImportDTO.getRegionGridIds()); + // 使用 selectAllCustFromGrid 方法查询所有客户(不限制客户类型) + for (RegionGrid regionGrid : regionGrids) { + List custUsers = regionGridListService.selectAllCustFromGrid(regionGrid); + for (RegionCustUser custUser : custUsers) { + CustGroupMember member = new CustGroupMember(); + member.setGroupId(custGroup.getId()); + member.setCustId(custUser.getCustId()); + member.setCustName(custUser.getCustName()); + member.setCustType(custUser.getCustType()); + member.setCreateTime(new Date()); + memberList.add(member); + } + } + return memberList; + } + + /** + * 从绘制网格导入客户 + */ + private List importFromDrawGrid(CustGroup custGroup, GridImportDTO gridImportDTO) { + List memberList = new ArrayList<>(); + if (gridImportDTO.getDrawGridIds() == null || gridImportDTO.getDrawGridIds().isEmpty()) { + throw new ServiceException("请选择绘制网格"); + } + // 查询绘制网格关联的图形ID + for (Long gridId : gridImportDTO.getDrawGridIds()) { + LambdaQueryWrapper relateWrapper = new LambdaQueryWrapper<>(); + relateWrapper.eq(DrawGridShapeRelate::getGridId, gridId); + List relates = drawGridShapeRelateMapper.selectList(relateWrapper); + for (DrawGridShapeRelate relate : relates) { + // 根据图形ID查询客户 + LambdaQueryWrapper custWrapper = new LambdaQueryWrapper<>(); + custWrapper.eq(DrawShapeCust::getShapeId, relate.getShapeId()); + List shapeCusts = drawShapeCustMapper.selectList(custWrapper); + for (DrawShapeCust shapeCust : shapeCusts) { + CustGroupMember member = new CustGroupMember(); + member.setGroupId(custGroup.getId()); + member.setCustId(shapeCust.getCustId()); + member.setCustName(shapeCust.getCustName()); + member.setCustType(shapeCust.getCustType()); + member.setCreateTime(new Date()); + memberList.add(member); + } + } + } + return memberList; + } +} \ No newline at end of file diff --git a/ibs-group/src/main/resources/mapper/group/CustGroupMapper.xml b/ibs-group/src/main/resources/mapper/group/CustGroupMapper.xml new file mode 100644 index 0000000..e85cb46 --- /dev/null +++ b/ibs-group/src/main/resources/mapper/group/CustGroupMapper.xml @@ -0,0 +1,46 @@ + + + + + + + diff --git a/ibs/src/main/java/com/ruoyi/ibs/cmpm/controller/GridCmpmController.java b/ibs/src/main/java/com/ruoyi/ibs/cmpm/controller/GridCmpmController.java index fe3cf9f..a7b71f7 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/cmpm/controller/GridCmpmController.java +++ b/ibs/src/main/java/com/ruoyi/ibs/cmpm/controller/GridCmpmController.java @@ -97,6 +97,13 @@ public class GridCmpmController extends BaseController { return gridCmpmService.selectCustManagerResult(); } + @GetMapping("/custManager/custLevel/list") + @Log(title = "绩效网格-管户报表星级列表") + @ApiOperation("管户报表星级列表") + public AjaxResult getCustLevelListForManager() { + return success(gridCmpmService.getCustLevelListForManager()); + } + @GetMapping("/custLevel/count") @Log(title = "绩效网格-查询客户分层等级") @ApiOperation("查询客户分层等级") diff --git a/ibs/src/main/java/com/ruoyi/ibs/cmpm/domain/dto/CustManagerDTO.java b/ibs/src/main/java/com/ruoyi/ibs/cmpm/domain/dto/CustManagerDTO.java index be557e0..ed1a85e 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/cmpm/domain/dto/CustManagerDTO.java +++ b/ibs/src/main/java/com/ruoyi/ibs/cmpm/domain/dto/CustManagerDTO.java @@ -18,4 +18,8 @@ public class CustManagerDTO { @ApiModelProperty(value = "网点名",notes = "") private String branchId; + /** 状态类型:current(本月)、last(上月)、rise(上升)、fall(下降) */ + @ApiModelProperty(value = "状态类型", notes = "current(本月)、last(上月)、rise(上升)、fall(下降)") + private String statusType; + } diff --git a/ibs/src/main/java/com/ruoyi/ibs/cmpm/service/GridCmpmService.java b/ibs/src/main/java/com/ruoyi/ibs/cmpm/service/GridCmpmService.java index e71cb46..a399978 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/cmpm/service/GridCmpmService.java +++ b/ibs/src/main/java/com/ruoyi/ibs/cmpm/service/GridCmpmService.java @@ -152,6 +152,13 @@ public class GridCmpmService { return new DwbRetailResultVO(); } + /** + * 获取管户报表星级列表 + */ + public List getCustLevelListForManager() { + return gridCmpmMapper.getCustLevelList(); + } + /** * 每月定时设置统计数据 */ @@ -226,7 +233,6 @@ public class GridCmpmService { // 获取上月各星级客户数 Map historyLevelCountMap = getCustLevelCountMap(custManagerDTO, "last"); Map custLevelCompLm = calculateLevelChanges(historyLevelCountMap, currentLevelCountMap); - vo.setCustLevelCompLm(custLevelCompLm); // 在 vo.setCustLevelCompLm 之前,按顺序重新组织数据 String[] order = {"5星", "4星", "3星", "2星", "1星", "基础", "长尾"}; diff --git a/ibs/src/main/java/com/ruoyi/ibs/grid/service/RegionGridListService.java b/ibs/src/main/java/com/ruoyi/ibs/grid/service/RegionGridListService.java index 03186b5..a1dc384 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/grid/service/RegionGridListService.java +++ b/ibs/src/main/java/com/ruoyi/ibs/grid/service/RegionGridListService.java @@ -35,4 +35,12 @@ public interface RegionGridListService { List getSecGridListByManager(RegionGridListDTO regionGridListDTO); + /** + * 查询网格内所有客户(不限制客户类型,用于导入客群) + * @param regionGrid 地理网格对象 + * @return 网格内所有客户列表 + */ + List selectAllCustFromGrid(RegionGrid regionGrid); + + } diff --git a/ibs/src/main/java/com/ruoyi/ibs/grid/service/impl/RegionGridListServiceImpl.java b/ibs/src/main/java/com/ruoyi/ibs/grid/service/impl/RegionGridListServiceImpl.java index 563345d..50dba4c 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/grid/service/impl/RegionGridListServiceImpl.java +++ b/ibs/src/main/java/com/ruoyi/ibs/grid/service/impl/RegionGridListServiceImpl.java @@ -2,6 +2,7 @@ package com.ruoyi.ibs.grid.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.ruoyi.common.core.domain.entity.SysDept; +import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.bean.BeanUtils; import com.ruoyi.ibs.draw.domain.dto.grid.CustCountDTO; @@ -429,4 +430,25 @@ public class RegionGridListServiceImpl implements RegionGridListService { return secGridListByManager; } + + /** + * 查询网格内所有客户(不限制客户类型,用于导入客群) + * @param regionGrid 地理网格对象 + * @return 网格内所有客户列表 + */ + @Override + public List selectAllCustFromGrid(RegionGrid regionGrid) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + // 根据网格等级判断使用一级还是二级网格ID查询 + if (regionGrid.getGridLevel().equals("1")) { + queryWrapper.eq(RegionCustUser::getTopGridId, regionGrid.getGridId()); + } else if (regionGrid.getGridLevel().equals("2")) { + queryWrapper.eq(RegionCustUser::getSecGridId, regionGrid.getGridId()); + } else { + throw new ServiceException("无效的网格等级: " + regionGrid.getGridLevel()); + } + // 不限制客户类型,查询所有类型的客户 + return regionCustUserMapper.selectList(queryWrapper); + } + } diff --git a/ibs/src/main/resources/mapper/cmpm/GridCmpmMapper.xml b/ibs/src/main/resources/mapper/cmpm/GridCmpmMapper.xml index e58bc86..bc24159 100644 --- a/ibs/src/main/resources/mapper/cmpm/GridCmpmMapper.xml +++ b/ibs/src/main/resources/mapper/cmpm/GridCmpmMapper.xml @@ -142,6 +142,28 @@ and manager_id = #{managerId} and outlet_id = #{outletId} and branch_id = #{branchId} + + + + + and cust_level = #{custLevel} + + + + and cust_level_lm = #{custLevel} + + + + and cust_level = #{custLevel} + and cust_level_comp_lm like '上升%' + + + + and cust_level_lm = #{custLevel} + and cust_level_comp_lm like '下降%' + + + @@ -438,6 +460,7 @@ select distinct cust_level from dwb_retail_cust_level_manager_detail_875 + order by cust_level