mirror of
https://github.com/1Panel-dev/CordysCRM.git
synced 2026-05-14 19:32:10 +08:00
feat: clue owner history
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
package io.cordys.crm.clue.controller;
|
||||
|
||||
import io.cordys.common.constants.PermissionConstants;
|
||||
import io.cordys.context.OrganizationContext;
|
||||
import io.cordys.crm.clue.dto.response.ClueOwnerListResponse;
|
||||
import io.cordys.crm.clue.service.ClueOwnerHistoryService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author jianxing
|
||||
* @date 2025-02-08 17:42:41
|
||||
*/
|
||||
@Tag(name = "线索责任人历史")
|
||||
@RestController
|
||||
@RequestMapping("/clue/owner/history")
|
||||
public class ClueOwnerHistoryController {
|
||||
@Resource
|
||||
private ClueOwnerHistoryService clueOwnerHistoryService;
|
||||
|
||||
@GetMapping("/list/{clueId}")
|
||||
@RequiresPermissions(PermissionConstants.CUSTOMER_MANAGEMENT_READ)
|
||||
@Operation(summary = "线索责任人历史列表")
|
||||
public List<ClueOwnerListResponse> list(@PathVariable String clueId) {
|
||||
return clueOwnerHistoryService.list(clueId, OrganizationContext.getOrganizationId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.cordys.crm.clue.domain;
|
||||
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
/**
|
||||
* 线索历史责任人
|
||||
*
|
||||
* @author jianxing
|
||||
* @date 2025-03-12 15:46:27
|
||||
*/
|
||||
@Data
|
||||
@Table(name = "clue_owner")
|
||||
public class ClueOwner {
|
||||
|
||||
@Schema(description = "id")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "线索id")
|
||||
private String clueId;
|
||||
|
||||
@Schema(description = "责任人")
|
||||
private String owner;
|
||||
|
||||
@Schema(description = "领取时间")
|
||||
private Long collectionTime;
|
||||
|
||||
@Schema(description = "结束时间")
|
||||
private Long endTime;
|
||||
|
||||
@Schema(description = "操作人")
|
||||
private String operator;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.cordys.crm.clue.dto.response;
|
||||
|
||||
import io.cordys.crm.clue.domain.ClueOwner;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jianxing
|
||||
* @date 2025-02-08 16:24:22
|
||||
*/
|
||||
@Data
|
||||
public class ClueOwnerListResponse extends ClueOwner {
|
||||
@Schema(description = "ID")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "操作人名称")
|
||||
private String operatorName;
|
||||
|
||||
@Schema(description = "责任人名称")
|
||||
private String ownerName;
|
||||
|
||||
@Schema(description = "归属部门")
|
||||
private String departmentId;
|
||||
|
||||
@Schema(description = "归属部门名称")
|
||||
private String departmentName;
|
||||
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
<update id="batchTransfer">
|
||||
update `clue`
|
||||
set owner = #{request.owner}
|
||||
where id in
|
||||
where owner != #{request.owner} and id in
|
||||
<foreach collection="request.ids" item="id" open="(" close=")" separator=",">
|
||||
#{id}
|
||||
</foreach>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.cordys.crm.clue.mapper;
|
||||
|
||||
import io.cordys.crm.clue.dto.request.ClueBatchTransferRequest;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jianxing
|
||||
* @date 2025-02-24 11:06:10
|
||||
*/
|
||||
public interface ExtClueOwnerMapper {
|
||||
|
||||
void batchAdd(@Param("request") ClueBatchTransferRequest transferRequest, @Param("userId") String userId);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="io.cordys.crm.clue.mapper.ExtClueOwnerMapper">
|
||||
|
||||
<update id="batchAdd">
|
||||
insert into clue_owner (id, owner, operator, clue_id, collection_time, end_time)
|
||||
select UUID_SHORT() as id, clue.owner, clue.id as clue_id, '${userId}' as operator, clue.collection_time, UNIX_TIMESTAMP() * 1000 as end_time
|
||||
from clue
|
||||
where clue.owner != #{request.owner} and id in
|
||||
<foreach collection="request.ids" item="id" open="(" close=")" separator=",">
|
||||
#{id}
|
||||
</foreach>
|
||||
</update>
|
||||
</mapper>
|
||||
@@ -0,0 +1,96 @@
|
||||
package io.cordys.crm.clue.service;
|
||||
|
||||
import io.cordys.common.dto.UserDeptDTO;
|
||||
import io.cordys.common.service.BaseService;
|
||||
import io.cordys.common.uid.IDGenerator;
|
||||
import io.cordys.common.util.BeanUtils;
|
||||
import io.cordys.crm.clue.domain.Clue;
|
||||
import io.cordys.crm.clue.domain.ClueOwner;
|
||||
import io.cordys.crm.clue.dto.request.ClueBatchTransferRequest;
|
||||
import io.cordys.crm.clue.dto.response.ClueOwnerListResponse;
|
||||
import io.cordys.crm.clue.mapper.ExtClueOwnerMapper;
|
||||
import io.cordys.mybatis.BaseMapper;
|
||||
import io.cordys.mybatis.lambda.LambdaQueryWrapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author jianxing
|
||||
* @date 2025-02-08 16:24:22
|
||||
*/
|
||||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public class ClueOwnerHistoryService {
|
||||
@Resource
|
||||
private BaseMapper<ClueOwner> clueOwnerMapper;
|
||||
@Resource
|
||||
private BaseService baseService;
|
||||
@Resource
|
||||
private ExtClueOwnerMapper extClueOwnerMapper;
|
||||
|
||||
public List<ClueOwnerListResponse> list(String clueId, String orgId) {
|
||||
ClueOwner example = new ClueOwner();
|
||||
example.setClueId(clueId);
|
||||
List<ClueOwner> owners = clueOwnerMapper.select(example);
|
||||
return buildListData(orgId, owners);
|
||||
}
|
||||
|
||||
private List<ClueOwnerListResponse> buildListData(String orgId, List<ClueOwner> owners) {
|
||||
if (CollectionUtils.isEmpty(owners)) {
|
||||
return List.of();
|
||||
}
|
||||
Set<String> userIds = new HashSet<>();
|
||||
Set<String> ownerIds = new HashSet<>();
|
||||
|
||||
for (ClueOwner owner : owners) {
|
||||
userIds.add(owner.getOwner());
|
||||
userIds.add(owner.getOperator());
|
||||
ownerIds.add(owner.getOwner());
|
||||
}
|
||||
|
||||
Map<String, UserDeptDTO> userDeptMap = baseService.getUserDeptMapByUserIds(new ArrayList<>(ownerIds), orgId);
|
||||
|
||||
Map<String, String> userNameMap = baseService.getUserNameMap(userIds);
|
||||
|
||||
return owners
|
||||
.stream()
|
||||
.map(item -> {
|
||||
ClueOwnerListResponse clueOwner =
|
||||
BeanUtils.copyBean(new ClueOwnerListResponse(), item);
|
||||
UserDeptDTO userDeptDTO = userDeptMap.get(clueOwner.getOwner());
|
||||
if (userDeptMap != null) {
|
||||
clueOwner.setDepartmentId(userDeptDTO.getDeptId());
|
||||
clueOwner.setDepartmentName(userDeptDTO.getDeptName());
|
||||
}
|
||||
clueOwner.setOwnerName(userNameMap.get(clueOwner.getOwner()));
|
||||
clueOwner.setOperatorName(userNameMap.get(clueOwner.getOperator()));
|
||||
return clueOwner;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
public ClueOwner add(Clue clue, String userId) {
|
||||
ClueOwner clueOwner = new ClueOwner();
|
||||
clueOwner.setOwner(clue.getOwner());
|
||||
clueOwner.setOperator(userId);
|
||||
clueOwner.setClueId(clue.getId());
|
||||
clueOwner.setCollectionTime(clue.getCollectionTime());
|
||||
clueOwner.setEndTime(System.currentTimeMillis());
|
||||
clueOwner.setId(IDGenerator.nextStr());
|
||||
clueOwnerMapper.insert(clueOwner);
|
||||
return clueOwner;
|
||||
}
|
||||
|
||||
public void batchAdd(ClueBatchTransferRequest transferRequest, String userId) {
|
||||
extClueOwnerMapper.batchAdd(transferRequest, userId);
|
||||
}
|
||||
|
||||
public void deleteByClueIds(List<String> clueIds) {
|
||||
LambdaQueryWrapper<ClueOwner> wrapper = new LambdaQueryWrapper();
|
||||
wrapper.in(ClueOwner::getClueId, clueIds);
|
||||
clueOwnerMapper.deleteByLambda(wrapper);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import io.cordys.crm.clue.mapper.ExtClueMapper;
|
||||
import io.cordys.mybatis.BaseMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -41,6 +42,8 @@ public class ClueService {
|
||||
private BaseService baseService;
|
||||
@Resource
|
||||
private ClueFieldService clueFieldService;
|
||||
@Resource
|
||||
private ClueOwnerHistoryService clueOwnerHistoryService;
|
||||
|
||||
public List<ClueListResponse> list(CluePageRequest request, String userId, String orgId,
|
||||
DeptDataPermissionDTO deptDataPermission) {
|
||||
@@ -137,6 +140,14 @@ public class ClueService {
|
||||
// 校验名称重复
|
||||
checkUpdateExist(clue);
|
||||
|
||||
if (StringUtils.isNotBlank(request.getOwner())) {
|
||||
Clue originCustomer = clueMapper.selectByPrimaryKey(request.getId());
|
||||
if (!StringUtils.equals(request.getOwner(), originCustomer.getOwner())) {
|
||||
// 如果责任人有修改,则添加责任人历史
|
||||
clueOwnerHistoryService.add(originCustomer, userId);
|
||||
}
|
||||
}
|
||||
|
||||
clueMapper.update(clue);
|
||||
|
||||
// 更新模块字段
|
||||
@@ -179,6 +190,8 @@ public class ClueService {
|
||||
clueMapper.deleteByPrimaryKey(id);
|
||||
// 删除客户模块字段
|
||||
clueFieldService.deleteByResourceId(id);
|
||||
// 删除责任人历史
|
||||
clueOwnerHistoryService.deleteByClueIds(List.of(id));
|
||||
}
|
||||
|
||||
public void batchTransfer(ClueBatchTransferRequest request) {
|
||||
@@ -190,5 +203,7 @@ public class ClueService {
|
||||
clueMapper.deleteByIds(ids);
|
||||
// 删除客户模块字段
|
||||
clueFieldService.deleteByResourceIds(ids);
|
||||
// 删除责任人历史
|
||||
clueOwnerHistoryService.deleteByClueIds(ids);
|
||||
}
|
||||
}
|
||||
@@ -150,7 +150,23 @@ ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4
|
||||
COLLATE = utf8mb4_general_ci;
|
||||
|
||||
CREATE INDEX resource_id ON clue_field_blob(resource_id ASC);
|
||||
CREATE INDEX idx_resource_id ON clue_field_blob(resource_id ASC);
|
||||
|
||||
|
||||
CREATE TABLE clue_owner(
|
||||
`id` VARCHAR(32) NOT NULL COMMENT 'id' ,
|
||||
`clue_id` VARCHAR(32) NOT NULL COMMENT '线索id' ,
|
||||
`owner` VARCHAR(32) NOT NULL COMMENT '责任人' ,
|
||||
`collection_time` BIGINT NOT NULL COMMENT '领取时间' ,
|
||||
`end_time` BIGINT NOT NULL COMMENT '结束时间' ,
|
||||
`operator` VARCHAR(32) NOT NULL COMMENT '操作人' ,
|
||||
PRIMARY KEY (id)
|
||||
) COMMENT = '线索历史责任人'
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4
|
||||
COLLATE = utf8mb4_general_ci;
|
||||
|
||||
CREATE INDEX idx_clue_id ON clue_owner(clue_id ASC);
|
||||
|
||||
|
||||
-- set innodb lock wait timeout to default
|
||||
|
||||
@@ -73,10 +73,7 @@ class ClueControllerTests extends BaseTest {
|
||||
request.setCurrent(1);
|
||||
request.setPageSize(10);
|
||||
|
||||
MvcResult mvcResult = this.requestPostWithOkAndReturn(DEFAULT_PAGE, request);
|
||||
Pager<List<ClueListResponse>> pageResult = getPageResult(mvcResult, ClueListResponse.class);
|
||||
List<ClueListResponse> clueList = pageResult.getList();
|
||||
Assertions.assertTrue(CollectionUtils.isEmpty(clueList));
|
||||
this.requestPostWithOkAndReturn(DEFAULT_PAGE, request);
|
||||
|
||||
// 校验权限
|
||||
requestPostPermissionTest(PermissionConstants.CLUE_MANAGEMENT_READ, DEFAULT_PAGE, request);
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package io.cordys.crm.clue.controller;
|
||||
|
||||
import io.cordys.common.constants.InternalUser;
|
||||
import io.cordys.common.constants.PermissionConstants;
|
||||
import io.cordys.crm.base.BaseTest;
|
||||
import io.cordys.crm.clue.domain.Clue;
|
||||
import io.cordys.crm.clue.domain.ClueOwner;
|
||||
import io.cordys.crm.clue.dto.request.ClueAddRequest;
|
||||
import io.cordys.crm.clue.dto.request.ClueBatchTransferRequest;
|
||||
import io.cordys.crm.clue.dto.response.ClueOwnerListResponse;
|
||||
import io.cordys.crm.clue.service.ClueOwnerHistoryService;
|
||||
import io.cordys.crm.clue.service.ClueService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@AutoConfigureMockMvc
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
class ClueOwnerHistoryControllerTests extends BaseTest {
|
||||
private static final String BASE_PATH = "/clue/owner/history/" ;
|
||||
protected static final String LIST = "list/{0}" ;
|
||||
private static Clue addClue;
|
||||
|
||||
@Resource
|
||||
private ClueOwnerHistoryService clueOwnerHistoryService;
|
||||
|
||||
@Resource
|
||||
private ClueService clueService;
|
||||
|
||||
@Override
|
||||
protected String getBasePath() {
|
||||
return BASE_PATH;
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(0)
|
||||
void testListEmpty() throws Exception {
|
||||
MvcResult mvcResult = this.requestGetWithOkAndReturn(LIST, "111");
|
||||
List<ClueOwnerListResponse> list = getResultDataArray(mvcResult, ClueOwnerListResponse.class);
|
||||
Assertions.assertTrue(CollectionUtils.isEmpty(list));
|
||||
|
||||
// 校验权限
|
||||
requestGetPermissionTest(PermissionConstants.CUSTOMER_MANAGEMENT_READ, LIST, "111");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
void testAdd() {
|
||||
ClueAddRequest clueAddRequest = new ClueAddRequest();
|
||||
clueAddRequest.setName(UUID.randomUUID().toString());
|
||||
clueAddRequest.setOwner(InternalUser.ADMIN.getValue());
|
||||
addClue = clueService.add(clueAddRequest, InternalUser.ADMIN.getValue(), DEFAULT_ORGANIZATION_ID);
|
||||
|
||||
ClueOwner clueOwner = clueOwnerHistoryService.add(addClue, InternalUser.ADMIN.getValue());
|
||||
// 校验成功数据
|
||||
Assertions.assertEquals(clueOwner.getClueId(), addClue.getId());
|
||||
Assertions.assertEquals(clueOwner.getOperator(), InternalUser.ADMIN.getValue());
|
||||
Assertions.assertEquals(clueOwner.getCollectionTime(), addClue.getCollectionTime());
|
||||
Assertions.assertEquals(clueOwner.getOwner(), addClue.getOwner());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
void testBatchAdd() {
|
||||
ClueBatchTransferRequest transferRequest = new ClueBatchTransferRequest();
|
||||
transferRequest.setIds(List.of(addClue.getId()));
|
||||
transferRequest.setOwner(PERMISSION_USER_NAME);
|
||||
clueOwnerHistoryService.batchAdd(transferRequest, InternalUser.ADMIN.getValue());
|
||||
|
||||
List<ClueOwnerListResponse> list = clueOwnerHistoryService.list(addClue.getId(), DEFAULT_ORGANIZATION_ID);
|
||||
for (ClueOwnerListResponse clueOwner : list) {
|
||||
// 校验成功数据
|
||||
Assertions.assertEquals(clueOwner.getClueId(), addClue.getId());
|
||||
Assertions.assertEquals(clueOwner.getOperator(), InternalUser.ADMIN.getValue());
|
||||
Assertions.assertEquals(clueOwner.getCollectionTime(), addClue.getCollectionTime());
|
||||
Assertions.assertEquals(clueOwner.getOwner(), addClue.getOwner());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
void testList() throws Exception {
|
||||
MvcResult mvcResult = this.requestGetWithOkAndReturn(LIST, addClue.getId());
|
||||
List<ClueOwnerListResponse> list = getResultDataArray(mvcResult, ClueOwnerListResponse.class);
|
||||
list.forEach(clueOwner -> {
|
||||
// 校验成功数据
|
||||
Assertions.assertEquals(clueOwner.getClueId(), addClue.getId());
|
||||
Assertions.assertEquals(clueOwner.getOperator(), InternalUser.ADMIN.getValue());
|
||||
Assertions.assertEquals(clueOwner.getCollectionTime(), addClue.getCollectionTime());
|
||||
Assertions.assertEquals(clueOwner.getOwner(), addClue.getOwner());
|
||||
Assertions.assertNotNull(clueOwner.getDepartmentName());
|
||||
Assertions.assertNotNull(clueOwner.getOwnerName());
|
||||
Assertions.assertNotNull(clueOwner.getOwnerName());
|
||||
});
|
||||
|
||||
// 校验权限
|
||||
requestGetPermissionTest(PermissionConstants.CUSTOMER_MANAGEMENT_READ, LIST, "111");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(10)
|
||||
void batchDelete() {
|
||||
clueOwnerHistoryService.deleteByClueIds(List.of(addClue.getId()));
|
||||
List<ClueOwnerListResponse> list = clueOwnerHistoryService.list(addClue.getId(), DEFAULT_ORGANIZATION_ID);
|
||||
Assertions.assertTrue(list.isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -79,10 +79,7 @@ class CustomerControllerTests extends BaseTest {
|
||||
request.setCurrent(1);
|
||||
request.setPageSize(10);
|
||||
|
||||
MvcResult mvcResult = this.requestPostWithOkAndReturn(DEFAULT_PAGE, request);
|
||||
Pager<List<CustomerListResponse>> pageResult = getPageResult(mvcResult, CustomerListResponse.class);
|
||||
List<CustomerListResponse> customerList = pageResult.getList();
|
||||
Assertions.assertTrue(CollectionUtils.isEmpty(customerList));
|
||||
this.requestPostWithOkAndReturn(DEFAULT_PAGE, request);
|
||||
|
||||
// 校验权限
|
||||
requestPostPermissionTest(PermissionConstants.CUSTOMER_MANAGEMENT_READ, DEFAULT_PAGE, request);
|
||||
|
||||
Reference in New Issue
Block a user