669 lines
26 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.designhub.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.designhub.dto.DepartmentCreateRequest;
import com.designhub.dto.DepartmentLimitSettingsRequest;
import com.designhub.dto.DepartmentScoreAdjustRequest;
import com.designhub.dto.DepartmentUpdateRequest;
import com.designhub.entity.Brand;
import com.designhub.entity.Department;
import com.designhub.entity.DeptBrandRelation;
import com.designhub.entity.User;
import com.designhub.exception.BusinessException;
import com.designhub.mapper.BrandMapper;
import com.designhub.mapper.DepartmentMapper;
import com.designhub.mapper.DeptBrandRelationMapper;
import com.designhub.mapper.UserMapper;
import com.designhub.vo.DepartmentOverviewVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 部门管理Service
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DepartmentService {
private final DepartmentMapper departmentMapper;
private final TeamScorePoolService teamScorePoolService;
private final UserMapper userMapper;
private final DeptBrandRelationMapper deptBrandRelationMapper;
private final BrandMapper brandMapper;
/**
* 获取所有部门列表
*/
public List<Department> getAllDepartments() {
return departmentMapper.selectList(
new LambdaQueryWrapper<Department>()
.eq(Department::getStatus, 1)
.orderByAsc(Department::getSortOrder)
);
}
/**
* 获取部门树形结构
*/
public List<Department> getDepartmentTree() {
List<Department> allDepts = departmentMapper.selectDeptTree();
return buildTree(allDepts, 0L);
}
/**
* 根据类型获取部门列表
*/
public List<Department> getDepartmentsByType(String deptType) {
return departmentMapper.selectList(
new LambdaQueryWrapper<Department>()
.eq(Department::getDeptType, deptType)
.eq(Department::getStatus, 1)
.orderByAsc(Department::getSortOrder)
);
}
/**
* 根据ID获取部门
*/
public Department getDepartmentById(Long id) {
return departmentMapper.selectById(id);
}
/**
* 部门概览列表
*/
public List<DepartmentOverviewVO> getDepartmentOverviewList() {
List<Department> departments = getAllDepartments();
Map<Long, Long> memberCountMap = loadMemberCountMap();
Map<Long, String> leaderNameMap = loadLeaderNames(departments);
return departments.stream()
.map(dept -> buildOverviewVO(dept, memberCountMap, leaderNameMap))
.collect(Collectors.toList());
}
/**
* 获取单个部门概览
*/
public DepartmentOverviewVO getDepartmentOverview(Long deptId) {
Department department = getDepartmentById(deptId);
if (department == null) {
throw new BusinessException("部门不存在");
}
Map<Long, Long> memberCountMap = loadMemberCountMap();
Map<Long, String> leaderNameMap = loadLeaderNames(Collections.singletonList(department));
return buildOverviewVO(department, memberCountMap, leaderNameMap);
}
/**
* 创建部门
*/
@Transactional(rollbackFor = Exception.class)
public DepartmentOverviewVO createDepartment(DepartmentCreateRequest request) {
validateDeptType(request.getDeptType());
// 验证负责人ID是否有效
if (request.getLeaderId() != null && request.getLeaderId() > 0) {
User leader = userMapper.selectById(request.getLeaderId());
if (leader == null) {
throw new BusinessException("指定的负责人不存在");
}
}
Department department = new Department();
department.setDeptName(request.getDeptName());
department.setDeptCode(request.getDeptCode());
department.setDeptType(request.getDeptType());
department.setParentId(request.getParentId() != null ? request.getParentId() : 0L);
department.setLeaderId(request.getLeaderId());
department.setSortOrder(request.getSortOrder());
department.setStatus(request.getStatus());
department.setAllowedDemandTypes(joinAllowedTypes(request.getAllowedDemandTypes()));
departmentMapper.insert(department);
// 初始化积分池(需求部门和一级需求部门都需要积分池)
teamScorePoolService.upsertPool(department.getId(), department.getDeptName());
if ("demand".equals(request.getDeptType()) || "primary_demand".equals(request.getDeptType())) {
if (request.getInitialScoreBalance() != null) {
teamScorePoolService.setBalance(department.getId(), request.getInitialScoreBalance());
department.setScoreBalance(request.getInitialScoreBalance());
}
teamScorePoolService.updateLimitConfig(
department.getId(),
defaultLimitEnabled(request.getLimitEnabled()),
defaultLimitPeriod(request.getLimitPeriod()),
request.getPeriodLimit(),
request.getAlertThreshold()
);
department.setLimitEnabled(defaultLimitEnabled(request.getLimitEnabled()));
department.setLimitPeriod(defaultLimitPeriod(request.getLimitPeriod()));
department.setPeriodLimit(request.getPeriodLimit());
department.setAlertThreshold(request.getAlertThreshold());
department.setOverLimitAction(
StringUtils.hasText(request.getOverLimitAction()) ? request.getOverLimitAction() : "alert"
);
}
return getDepartmentOverview(department.getId());
}
/**
* 更新部门
*/
@Transactional(rollbackFor = Exception.class)
public boolean updateDepartment(Long id, DepartmentUpdateRequest request) {
Department existDept = getDepartmentById(id);
if (existDept == null) {
throw new BusinessException("部门不存在");
}
if (StringUtils.hasText(request.getDeptName())) {
existDept.setDeptName(request.getDeptName());
}
if (StringUtils.hasText(request.getDeptCode())) {
existDept.setDeptCode(request.getDeptCode());
}
if (StringUtils.hasText(request.getDeptType())) {
validateDeptType(request.getDeptType());
existDept.setDeptType(request.getDeptType());
}
// 处理parentId:允许设置为0来清除(设置为null)
if (request.getParentId() != null) {
Long parentId = request.getParentId();
existDept.setParentId(parentId == 0 ? null : parentId);
}
// 处理leaderId:前端总是发送leaderId字段
// null表示清除负责人,非null数字表示设置负责人
// 由于无法区分字段不存在和字段为null,前端必须总是发送此字段
// 如果前端不发送字段,这里getLeaderId()也会返回null,但不会更新(保持原值)
// 为了支持清除,我们检查:如果leaderId字段在请求中存在(通过检查其他必填字段),则处理
// 简化方案:总是检查leaderId,如果为null且当前有值,则清除;如果为数字,则设置
// 但由于无法判断字段是否存在,我们采用:如果leaderId不为null,则更新
if (request.getLeaderId() != null) {
Long leaderId = request.getLeaderId();
// 验证负责人ID是否有效(0表示清除,大于0需要验证)
if (leaderId > 0) {
User leader = userMapper.selectById(leaderId);
if (leader == null) {
throw new BusinessException("指定的负责人不存在");
}
}
existDept.setLeaderId(leaderId == 0 ? null : leaderId);
} else if (request.getDeptName() != null) {
// 如果其他字段被更新(说明这是完整的更新请求),且leaderId为null,则清除负责人
// 这是一个启发式判断:如果请求包含deptName(通常是必填字段),则认为leaderId=null是明确的清除操作
existDept.setLeaderId(null);
}
// 否则(leaderId为null且没有其他字段更新),不处理leaderId,保持原值
if (request.getSortOrder() != null) {
existDept.setSortOrder(request.getSortOrder());
}
if (request.getStatus() != null) {
existDept.setStatus(request.getStatus());
}
if (request.getAllowedDemandTypes() != null) {
existDept.setAllowedDemandTypes(joinAllowedTypes(request.getAllowedDemandTypes()));
}
if (request.getLimitEnabled() != null) {
existDept.setLimitEnabled(request.getLimitEnabled());
}
if (request.getLimitPeriod() != null) {
existDept.setLimitPeriod(request.getLimitPeriod());
}
if (request.getPeriodLimit() != null) {
existDept.setPeriodLimit(request.getPeriodLimit());
}
if (request.getAlertThreshold() != null) {
existDept.setAlertThreshold(request.getAlertThreshold());
}
if (request.getOverLimitAction() != null) {
existDept.setOverLimitAction(request.getOverLimitAction());
}
departmentMapper.updateById(existDept);
// 同步积分池(需求部门和一级需求部门都需要积分池)
teamScorePoolService.upsertPool(existDept.getId(), existDept.getDeptName());
if (("demand".equals(existDept.getDeptType()) || "primary_demand".equals(existDept.getDeptType())) &&
(request.getLimitEnabled() != null || request.getLimitPeriod() != null ||
request.getPeriodLimit() != null || request.getAlertThreshold() != null)) {
teamScorePoolService.updateLimitConfig(
existDept.getId(),
request.getLimitEnabled(),
request.getLimitPeriod(),
request.getPeriodLimit(),
request.getAlertThreshold()
);
}
return true;
}
/**
* 更新部门限制配置
*/
@Transactional(rollbackFor = Exception.class)
public boolean updateLimitSettings(Long deptId, DepartmentLimitSettingsRequest request) {
Department department = getDepartmentById(deptId);
if (department == null) {
throw new BusinessException("部门不存在");
}
if (!"demand".equals(department.getDeptType()) && !"primary_demand".equals(department.getDeptType())) {
throw new BusinessException("只有需求部门或一级需求部门需要配置积分限制");
}
department.setLimitEnabled(request.getLimitEnabled());
department.setLimitPeriod(request.getLimitPeriod());
department.setPeriodLimit(request.getPeriodLimit());
department.setAlertThreshold(request.getAlertThreshold());
department.setOverLimitAction(request.getOverLimitAction());
departmentMapper.updateById(department);
teamScorePoolService.updateLimitConfig(
deptId,
request.getLimitEnabled(),
request.getLimitPeriod(),
request.getPeriodLimit(),
request.getAlertThreshold()
);
return true;
}
/**
* 删除部门(逻辑删除)
*/
@Transactional(rollbackFor = Exception.class)
public boolean deleteDepartment(Long id) {
long childCount = departmentMapper.selectCount(
new LambdaQueryWrapper<Department>()
.eq(Department::getParentId, id)
);
if (childCount > 0) {
throw new BusinessException("该部门下还有子部门,无法删除");
}
return departmentMapper.deleteById(id) > 0;
}
/**
* 设置部门负责人/主管
*/
@Transactional(rollbackFor = Exception.class)
public boolean setDepartmentHead(Long deptId, Long userId, String type) {
Department department = getDepartmentById(deptId);
if (department == null) {
throw new BusinessException("部门不存在");
}
if ("head".equals(type)) {
if (!"demand".equals(department.getDeptType()) && !"primary_demand".equals(department.getDeptType())) {
throw new BusinessException("只有需求部门或一级需求部门可以设置负责人");
}
department.setLeaderId(userId);
} else if ("supervisor".equals(type)) {
if (!"receiving".equals(department.getDeptType())) {
throw new BusinessException("只有接单部门可以设置主管");
}
department.setSupervisorId(userId);
} else {
throw new BusinessException("类型参数错误");
}
return departmentMapper.updateById(department) > 0;
}
/**
* 调整部门积分
*/
@Transactional(rollbackFor = Exception.class)
public void adjustScore(Long deptId, DepartmentScoreAdjustRequest request) {
Department department = getDepartmentById(deptId);
if (department == null) {
throw new BusinessException("部门不存在");
}
if (!"demand".equals(department.getDeptType()) && !"primary_demand".equals(department.getDeptType())) {
throw new BusinessException("只有需求部门或一级需求部门可以调整积分");
}
if (request.getAmount() == null || request.getAmount() <= 0) {
throw new BusinessException("调整积分必须大于0");
}
String action = request.getAction().toLowerCase();
switch (action) {
case "recharge" -> teamScorePoolService.rechargeScore(deptId, request.getAmount());
case "deduct" -> teamScorePoolService.deductScore(deptId, request.getAmount());
default -> throw new BusinessException("不支持的积分操作类型");
}
}
private void validateDeptType(String deptType) {
if (!"demand".equals(deptType) && !"receiving".equals(deptType) && !"primary_demand".equals(deptType)) {
throw new BusinessException("部门类型必须是 demand、receiving 或 primary_demand");
}
}
// ==================== 品牌关联管理 ====================
/**
* 根据品牌ID获取负责的需求部门
*/
public Department getDemandDeptByBrandId(Long brandId) {
if (brandId == null) {
return null;
}
Long deptId = deptBrandRelationMapper.getDeptIdByBrandId(brandId);
if (deptId == null) {
return null;
}
return departmentMapper.selectById(deptId);
}
/**
* 设置部门负责的品牌
*/
@Transactional(rollbackFor = Exception.class)
public void setDeptResponsibleBrands(Long deptId, List<Long> brandIds) {
Department department = getDepartmentById(deptId);
if (department == null) {
throw new BusinessException("部门不存在");
}
if (!"demand".equals(department.getDeptType())) {
throw new BusinessException("只有需求部门可以设置负责品牌");
}
// 检查品牌是否已被其他部门负责
if (!CollectionUtils.isEmpty(brandIds)) {
for (Long brandId : brandIds) {
Long existingDeptId = deptBrandRelationMapper.getDeptIdByBrandId(brandId);
if (existingDeptId != null && !existingDeptId.equals(deptId)) {
Brand brand = brandMapper.selectById(brandId);
Department existingDept = departmentMapper.selectById(existingDeptId);
throw new BusinessException("品牌 [" + (brand != null ? brand.getName() : brandId) +
"] 已被部门 [" + (existingDept != null ? existingDept.getDeptName() : existingDeptId) + "] 负责");
}
}
}
// 删除原有关联
deptBrandRelationMapper.deleteByDeptId(deptId);
// 批量插入新关联
if (!CollectionUtils.isEmpty(brandIds)) {
deptBrandRelationMapper.batchInsert(deptId, brandIds);
}
log.info("设置部门 {} 负责品牌: {}", deptId, brandIds);
}
/**
* 获取部门负责的品牌列表
*/
public List<Brand> getDeptResponsibleBrands(Long deptId) {
List<Long> brandIds = deptBrandRelationMapper.getBrandIdsByDeptId(deptId);
if (CollectionUtils.isEmpty(brandIds)) {
return Collections.emptyList();
}
return brandMapper.selectBatchIds(brandIds);
}
/**
* 获取部门负责的品牌ID列表
*/
public List<Long> getDeptResponsibleBrandIds(Long deptId) {
return deptBrandRelationMapper.getBrandIdsByDeptId(deptId);
}
/**
* 获取所有品牌-部门关联关系
*/
public List<DeptBrandRelation> getAllBrandDeptRelations() {
return deptBrandRelationMapper.getAllRelationsWithNames();
}
/**
* 检查部门是否为一级需求部门
*/
public boolean isPrimaryDemandDept(Long deptId) {
if (deptId == null) {
return false;
}
Department dept = getDepartmentById(deptId);
return dept != null && "primary_demand".equals(dept.getDeptType());
}
/**
* 检查部门是否可以发起任务(需求部门或一级需求部门)
*/
public boolean canPublishTask(Long deptId) {
if (deptId == null) {
return false;
}
Department dept = getDepartmentById(deptId);
if (dept == null) {
return false;
}
String deptType = dept.getDeptType();
return "demand".equals(deptType) || "primary_demand".equals(deptType);
}
/**
* 获取所有一级需求部门
*/
public List<Department> getPrimaryDemandDepartments() {
return departmentMapper.selectList(
new LambdaQueryWrapper<Department>()
.eq(Department::getDeptType, "primary_demand")
.eq(Department::getStatus, 1)
.orderByAsc(Department::getSortOrder)
);
}
/**
* 获取所有需求部门的ID列表(只包括 demand 类型,不包括 primary_demand)
* 用于跨部门成员分配功能
* 注意:一级需求部门(primary_demand)的成员不应该出现在下拉列表中
*/
public List<Long> getAllDemandDeptIds() {
List<Department> demandDepts = departmentMapper.selectList(
new LambdaQueryWrapper<Department>()
.eq(Department::getDeptType, "demand") // 只查询 demand 类型,不包括 primary_demand
.eq(Department::getStatus, 1)
.eq(Department::getDeleted, 0)
);
List<Long> allDeptIds = new java.util.ArrayList<>();
for (Department dept : demandDepts) {
allDeptIds.add(dept.getId());
// 递归获取所有子部门ID
allDeptIds.addAll(getAllChildDeptIds(dept.getId()));
}
return allDeptIds;
}
private Integer defaultLimitEnabled(Integer limitEnabled) {
return limitEnabled != null ? limitEnabled : 0;
}
private String defaultLimitPeriod(String limitPeriod) {
return StringUtils.hasText(limitPeriod) ? limitPeriod : "MONTHLY";
}
private String joinAllowedTypes(List<String> allowedTypes) {
if (CollectionUtils.isEmpty(allowedTypes)) {
return null;
}
return allowedTypes.stream()
.filter(StringUtils::hasText)
.collect(Collectors.joining(","));
}
private List<String> splitAllowedTypes(String allowedTypes) {
if (allowedTypes == null || allowedTypes.trim().isEmpty()) {
return Collections.emptyList();
}
return Arrays.stream(allowedTypes.split(","))
.map(String::trim)
.filter(StringUtils::hasText)
.collect(Collectors.toList());
}
private Map<Long, Long> loadMemberCountMap() {
// 只统计启用且未删除的用户
return userMapper.selectList(
new LambdaQueryWrapper<User>()
.eq(User::getStatus, 1)
.eq(User::getDeleted, 0)
).stream()
.filter(user -> user.getDeptId() != null)
.collect(Collectors.groupingBy(
User::getDeptId,
Collectors.counting()
));
}
private Map<Long, String> loadLeaderNames(List<Department> departments) {
List<Long> leaderIds = departments.stream()
.map(Department::getLeaderId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (leaderIds.isEmpty()) {
return Collections.emptyMap();
}
return userMapper.selectBatchIds(leaderIds).stream()
.collect(Collectors.toMap(
User::getId,
user -> StringUtils.hasText(user.getRealName()) ? user.getRealName() : user.getUsername()
));
}
private DepartmentOverviewVO buildOverviewVO(Department department,
Map<Long, Long> memberCountMap,
Map<Long, String> leaderNameMap) {
DepartmentOverviewVO vo = new DepartmentOverviewVO();
vo.setId(department.getId());
vo.setDeptName(department.getDeptName());
vo.setDeptCode(department.getDeptCode());
vo.setDeptType(department.getDeptType());
vo.setParentId(department.getParentId());
vo.setLeaderId(department.getLeaderId());
if (department.getLeaderId() != null && leaderNameMap != null) {
vo.setLeaderName(leaderNameMap.get(department.getLeaderId()));
}
vo.setDescription(department.getDescription());
vo.setSortOrder(department.getSortOrder());
vo.setStatus(department.getStatus());
vo.setAllowedDemandTypes(splitAllowedTypes(department.getAllowedDemandTypes()));
var pool = teamScorePoolService.getByDeptId(department.getId());
if (pool != null) {
vo.setScoreBalance(pool.getBalance());
vo.setTotalRecharge(pool.getTotalRecharge());
vo.setTotalConsume(pool.getTotalConsume());
vo.setTotalEarn(pool.getTotalEarn());
vo.setLimitEnabled(pool.getLimitEnabled());
vo.setLimitPeriod(pool.getLimitPeriod());
vo.setPeriodLimit(pool.getPeriodLimit());
vo.setPeriodUsed(pool.getPeriodUsed());
vo.setAlertThreshold(pool.getAlertThreshold());
vo.setLimitAlert(teamScorePoolService.isAlert(department.getId()));
} else {
vo.setScoreBalance(department.getScoreBalance());
vo.setLimitEnabled(department.getLimitEnabled());
vo.setLimitPeriod(department.getLimitPeriod());
vo.setPeriodLimit(department.getPeriodLimit());
vo.setAlertThreshold(department.getAlertThreshold());
vo.setLimitAlert(false);
}
vo.setOverLimitAction(department.getOverLimitAction());
if (memberCountMap != null && memberCountMap.containsKey(department.getId())) {
vo.setMemberCount(memberCountMap.get(department.getId()).intValue());
} else {
vo.setMemberCount(0);
}
return vo;
}
/**
* 构建树形结构
*/
private List<Department> buildTree(List<Department> allDepts, Long parentId) {
List<Department> children = allDepts.stream()
.filter(dept -> {
Long deptParentId = dept.getParentId();
if (deptParentId == null) {
return parentId == null || parentId == 0L;
}
return deptParentId.equals(parentId);
})
.collect(Collectors.toList());
for (Department child : children) {
buildTree(allDepts, child.getId());
}
return children;
}
/**
* 获取部门的所有子部门ID(递归)
*/
public List<Long> getAllChildDeptIds(Long deptId) {
List<Long> result = new ArrayList<>();
if (deptId == null) {
return result;
}
// 查找直接子部门(只查询未删除且启用的部门)
List<Department> children = departmentMapper.selectList(
new LambdaQueryWrapper<Department>()
.eq(Department::getParentId, deptId)
.eq(Department::getDeleted, 0)
.eq(Department::getStatus, 1)
);
// 递归获取所有子部门
for (Department child : children) {
result.add(child.getId());
result.addAll(getAllChildDeptIds(child.getId()));
}
log.info("获取部门{}的所有子部门ID: {}", deptId, result);
return result;
}
/**
* 检查用户是否为任何部门的负责人
*/
public boolean isUserDepartmentLeader(Long userId) {
if (userId == null) {
return false;
}
// 查询是否有部门的 leader_id 等于该用户ID
Long count = departmentMapper.selectCount(
new LambdaQueryWrapper<Department>()
.eq(Department::getLeaderId, userId)
.eq(Department::getStatus, 1)
);
return count != null && count > 0;
}
}