package com.sinata.appointment.service;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.sinata.appointment.constants.BizErrorCode;
|
import com.sinata.appointment.constants.MiniUserType;
|
import com.sinata.appointment.dto.StaffGroupAppendMembersRequest;
|
import com.sinata.appointment.dto.StaffGroupCreateRequest;
|
import com.sinata.appointment.dto.StaffGroupDetailVo;
|
import com.sinata.appointment.dto.StaffGroupListVo;
|
import com.sinata.appointment.dto.StaffGroupMemberVo;
|
import com.sinata.appointment.dto.StaffGroupUpdateRequest;
|
import com.sinata.appointment.entity.MpStaffGroup;
|
import com.sinata.appointment.entity.MpStaffGroupMember;
|
import com.sinata.appointment.entity.MpUser;
|
import com.sinata.appointment.exception.BizException;
|
import com.sinata.appointment.mapper.MpStaffGroupMapper;
|
import com.sinata.appointment.mapper.MpStaffGroupMemberMapper;
|
import com.sinata.appointment.mapper.MpUserMapper;
|
import com.sinata.appointment.mapper.SysDeptMapper;
|
import com.sinata.appointment.util.MobileMaskUtil;
|
import com.sinata.appointment.util.UuidAssert;
|
import java.util.ArrayList;
|
import java.util.HashMap;
|
import java.util.HashSet;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Set;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.util.StringUtils;
|
|
@Service
|
public class StaffGroupManageApplicationService {
|
|
private final MpStaffGroupMapper mpStaffGroupMapper;
|
private final MpStaffGroupMemberMapper mpStaffGroupMemberMapper;
|
private final MpUserMapper mpUserMapper;
|
private final SysDeptMapper sysDeptMapper;
|
|
public StaffGroupManageApplicationService(
|
MpStaffGroupMapper mpStaffGroupMapper,
|
MpStaffGroupMemberMapper mpStaffGroupMemberMapper,
|
MpUserMapper mpUserMapper,
|
SysDeptMapper sysDeptMapper) {
|
this.mpStaffGroupMapper = mpStaffGroupMapper;
|
this.mpStaffGroupMemberMapper = mpStaffGroupMemberMapper;
|
this.mpUserMapper = mpUserMapper;
|
this.sysDeptMapper = sysDeptMapper;
|
}
|
|
public IPage<StaffGroupListVo> page(
|
long page, long size, String groupName, String leaderMobile) {
|
LambdaQueryWrapper<MpStaffGroup> q =
|
new LambdaQueryWrapper<MpStaffGroup>()
|
.eq(MpStaffGroup::getDelFlag, 0)
|
.like(StringUtils.hasText(groupName), MpStaffGroup::getGroupName, groupName)
|
.orderByDesc(MpStaffGroup::getCreateTime);
|
|
if (StringUtils.hasText(leaderMobile)) {
|
String kw = leaderMobile.trim();
|
List<MpUser> leaders =
|
mpUserMapper.selectList(
|
new LambdaQueryWrapper<MpUser>()
|
.eq(MpUser::getDelFlag, 0)
|
.eq(MpUser::getUserType, MiniUserType.EMPLOYEE)
|
.like(MpUser::getMobile, kw));
|
if (leaders.isEmpty()) {
|
Page<StaffGroupListVo> empty = new Page<>(page, size, 0);
|
empty.setRecords(List.of());
|
return empty;
|
}
|
List<Long> leaderIds = leaders.stream().map(MpUser::getId).toList();
|
q.in(MpStaffGroup::getLeaderMpUserId, leaderIds);
|
}
|
|
Page<MpStaffGroup> p = mpStaffGroupMapper.selectPage(new Page<>(page, size), q);
|
|
// preload leaders and counts
|
Set<Long> leaderIds = new HashSet<>();
|
Set<Long> groupIds = new HashSet<>();
|
for (MpStaffGroup g : p.getRecords()) {
|
groupIds.add(g.getId());
|
if (g.getLeaderMpUserId() != null) {
|
leaderIds.add(g.getLeaderMpUserId());
|
}
|
}
|
Map<Long, MpUser> leaderMap = mpUsersByIds(leaderIds);
|
Map<Long, Integer> countMap = memberCountByGroupIds(groupIds);
|
|
return p.convert(
|
g -> {
|
StaffGroupListVo vo = new StaffGroupListVo();
|
vo.setId(g.getId());
|
vo.setUuid(g.getUuid());
|
vo.setGroupName(g.getGroupName());
|
MpUser leader = g.getLeaderMpUserId() != null ? leaderMap.get(g.getLeaderMpUserId()) : null;
|
vo.setLeaderName(leader != null ? leader.getNickName() : null);
|
vo.setLeaderMobileMasked(leader != null ? MobileMaskUtil.mask(leader.getMobile()) : null);
|
vo.setMemberCount(countMap.getOrDefault(g.getId(), 0));
|
vo.setCreateTime(g.getCreateTime());
|
return vo;
|
});
|
}
|
|
public StaffGroupDetailVo detail(Long id, String uuid) {
|
MpStaffGroup g = loadActiveWithUuid(id, uuid);
|
StaffGroupDetailVo vo = new StaffGroupDetailVo();
|
vo.setId(g.getId());
|
vo.setUuid(g.getUuid());
|
vo.setGroupName(g.getGroupName());
|
vo.setLeaderMpUserId(g.getLeaderMpUserId());
|
vo.setCreateTime(g.getCreateTime());
|
|
// members
|
List<MpStaffGroupMember> rels =
|
mpStaffGroupMemberMapper.selectList(
|
new LambdaQueryWrapper<MpStaffGroupMember>()
|
.eq(MpStaffGroupMember::getDelFlag, 0)
|
.eq(MpStaffGroupMember::getGroupId, g.getId())
|
.orderByAsc(MpStaffGroupMember::getId));
|
Set<Long> memberIds = new HashSet<>();
|
for (MpStaffGroupMember r : rels) {
|
memberIds.add(r.getMpUserId());
|
}
|
Map<Long, MpUser> memberMap = mpUsersByIds(memberIds);
|
Map<Long, String> deptNames = deptNamesByMemberMap(memberMap);
|
List<StaffGroupMemberVo> members = new ArrayList<>();
|
for (MpStaffGroupMember r : rels) {
|
MpUser u = memberMap.get(r.getMpUserId());
|
if (u == null) {
|
continue;
|
}
|
StaffGroupMemberVo m = new StaffGroupMemberVo();
|
m.setId(u.getId());
|
m.setUuid(u.getUuid());
|
m.setRealName(u.getNickName());
|
m.setStaffNo(u.getStaffNo());
|
m.setMobileMasked(MobileMaskUtil.mask(u.getMobile()));
|
m.setDeptId(u.getDeptId());
|
m.setDeptName(u.getDeptId() != null ? deptNames.get(u.getDeptId()) : null);
|
members.add(m);
|
}
|
vo.setMembers(members);
|
vo.setMemberCount(members.size());
|
|
// leader display
|
if (g.getLeaderMpUserId() != null) {
|
MpUser leader = memberMap.get(g.getLeaderMpUserId());
|
if (leader == null) {
|
leader = mpUserMapper.selectById(g.getLeaderMpUserId());
|
}
|
if (leader != null) {
|
vo.setLeaderName(leader.getNickName());
|
vo.setLeaderMobile(leader.getMobile());
|
vo.setLeaderMobileMasked(MobileMaskUtil.mask(leader.getMobile()));
|
}
|
}
|
return vo;
|
}
|
|
@Transactional(rollbackFor = Exception.class)
|
public Map<String, Object> create(StaffGroupCreateRequest req) {
|
String name = req.getGroupName().trim();
|
List<Long> memberIds = dedup(req.getMemberIds());
|
Long leaderId = req.getLeaderMpUserId();
|
|
if (memberIds.isEmpty()) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "关联人员不能为空");
|
}
|
if (leaderId == null) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "负责人不能为空");
|
}
|
assertLeaderHasUsableMobile(leaderId);
|
validateMembers(memberIds);
|
if (!memberIds.contains(leaderId)) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "负责人必须在关联人员中");
|
}
|
|
MpStaffGroup g = new MpStaffGroup();
|
g.setGroupName(name);
|
g.setLeaderMpUserId(leaderId);
|
mpStaffGroupMapper.insert(g);
|
|
saveMembers(g.getId(), memberIds);
|
MpStaffGroup fresh = mpStaffGroupMapper.selectById(g.getId());
|
return Map.of("id", fresh.getId(), "uuid", fresh.getUuid());
|
}
|
|
@Transactional(rollbackFor = Exception.class)
|
public void update(Long id, StaffGroupUpdateRequest req) {
|
MpStaffGroup g = loadActiveWithUuid(id, req.getUuid());
|
String name = req.getGroupName().trim();
|
List<Long> memberIds = dedup(req.getMemberIds());
|
Long leaderId = req.getLeaderMpUserId();
|
|
if (memberIds.isEmpty()) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "关联人员不能为空");
|
}
|
if (leaderId == null) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "负责人不能为空");
|
}
|
assertLeaderHasUsableMobile(leaderId);
|
validateMembers(memberIds);
|
if (!memberIds.contains(leaderId)) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "负责人必须在关联人员中");
|
}
|
|
g.setGroupName(name);
|
g.setLeaderMpUserId(leaderId);
|
mpStaffGroupMapper.updateById(g);
|
|
deleteMembersPhysically(g.getId());
|
saveMembers(g.getId(), memberIds);
|
}
|
|
@Transactional(rollbackFor = Exception.class)
|
public void delete(Long id, String uuid) {
|
MpStaffGroup g = loadActiveWithUuid(id, uuid);
|
int affected = mpStaffGroupMapper.deleteById(g.getId());
|
if (affected <= 0) {
|
throw new BizException(BizErrorCode.RESOURCE_VERIFY_FAILED, "删除失败,请刷新后重试");
|
}
|
deleteMembersPhysically(g.getId());
|
}
|
|
@Transactional(rollbackFor = Exception.class)
|
public void appendMembers(Long id, StaffGroupAppendMembersRequest req) {
|
MpStaffGroup g = loadActiveWithUuid(id, req.getUuid());
|
List<Long> ids = dedup(req.getMemberIds());
|
if (ids.isEmpty()) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "请至少选择一名成员");
|
}
|
validateMembers(ids);
|
Set<Long> existing = existingMemberMpUserIds(g.getId());
|
int added = 0;
|
for (Long mid : ids) {
|
if (existing.contains(mid)) {
|
continue;
|
}
|
MpStaffGroupMember rel = new MpStaffGroupMember();
|
rel.setGroupId(g.getId());
|
rel.setMpUserId(mid);
|
mpStaffGroupMemberMapper.insert(rel);
|
existing.add(mid);
|
added++;
|
}
|
if (added == 0) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "所选成员均已在分组中");
|
}
|
}
|
|
@Transactional(rollbackFor = Exception.class)
|
public void removeMember(Long groupId, String uuid, Long mpUserId) {
|
MpStaffGroup g = loadActiveWithUuid(groupId, uuid);
|
if (mpUserId == null) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "成员无效");
|
}
|
if (g.getLeaderMpUserId() != null && g.getLeaderMpUserId().equals(mpUserId)) {
|
throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "不能移除负责人");
|
}
|
MpStaffGroupMember one =
|
mpStaffGroupMemberMapper.selectOne(
|
new LambdaQueryWrapper<MpStaffGroupMember>()
|
.eq(MpStaffGroupMember::getGroupId, g.getId())
|
.eq(MpStaffGroupMember::getMpUserId, mpUserId)
|
.eq(MpStaffGroupMember::getDelFlag, 0));
|
if (one == null) {
|
throw new BizException(BizErrorCode.RESOURCE_VERIFY_FAILED, "该成员不在分组中");
|
}
|
mpStaffGroupMemberMapper.deletePhysicalByGroupAndUser(g.getId(), mpUserId);
|
}
|
|
private Set<Long> existingMemberMpUserIds(Long groupId) {
|
List<MpStaffGroupMember> rels =
|
mpStaffGroupMemberMapper.selectList(
|
new LambdaQueryWrapper<MpStaffGroupMember>()
|
.eq(MpStaffGroupMember::getGroupId, groupId)
|
.eq(MpStaffGroupMember::getDelFlag, 0));
|
Set<Long> set = new HashSet<>();
|
for (MpStaffGroupMember r : rels) {
|
if (r.getMpUserId() != null) {
|
set.add(r.getMpUserId());
|
}
|
}
|
return set;
|
}
|
|
private void deleteMembersPhysically(Long groupId) {
|
mpStaffGroupMemberMapper.deletePhysicalByGroupId(groupId);
|
}
|
|
private void assertLeaderHasUsableMobile(Long leaderMpUserId) {
|
MpUser u = mpUserMapper.selectById(leaderMpUserId);
|
if (u == null || (u.getDelFlag() != null && u.getDelFlag() == 1)) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "负责人不存在");
|
}
|
if (!MiniUserType.EMPLOYEE.equals(u.getUserType())) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "负责人须为员工");
|
}
|
if (u.getStatus() != null && u.getStatus() == 0) {
|
throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "负责人已禁用");
|
}
|
if (!StringUtils.hasText(u.getMobile())) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "负责人联系电话不能为空");
|
}
|
}
|
|
private MpStaffGroup loadActiveWithUuid(Long id, String uuid) {
|
MpStaffGroup g = mpStaffGroupMapper.selectById(id);
|
if (g == null || (g.getDelFlag() != null && g.getDelFlag() == 1)) {
|
throw new BizException(BizErrorCode.RESOURCE_VERIFY_FAILED, "资源不存在或无权访问");
|
}
|
UuidAssert.match(g, uuid);
|
return g;
|
}
|
|
private Map<Long, MpUser> mpUsersByIds(Set<Long> ids) {
|
Map<Long, MpUser> map = new HashMap<>();
|
if (ids == null || ids.isEmpty()) {
|
return map;
|
}
|
List<MpUser> users =
|
mpUserMapper.selectList(
|
new LambdaQueryWrapper<MpUser>()
|
.eq(MpUser::getDelFlag, 0)
|
.in(MpUser::getId, ids));
|
for (MpUser u : users) {
|
map.put(u.getId(), u);
|
}
|
return map;
|
}
|
|
private Map<Long, Integer> memberCountByGroupIds(Set<Long> groupIds) {
|
Map<Long, Integer> map = new HashMap<>();
|
if (groupIds == null || groupIds.isEmpty()) {
|
return map;
|
}
|
List<MpStaffGroupMember> rels =
|
mpStaffGroupMemberMapper.selectList(
|
new LambdaQueryWrapper<MpStaffGroupMember>()
|
.eq(MpStaffGroupMember::getDelFlag, 0)
|
.in(MpStaffGroupMember::getGroupId, groupIds));
|
for (MpStaffGroupMember r : rels) {
|
map.put(r.getGroupId(), map.getOrDefault(r.getGroupId(), 0) + 1);
|
}
|
return map;
|
}
|
|
private Map<Long, String> deptNamesByMemberMap(Map<Long, MpUser> memberMap) {
|
Set<Long> deptIds = new HashSet<>();
|
for (MpUser u : memberMap.values()) {
|
if (u.getDeptId() != null) {
|
deptIds.add(u.getDeptId());
|
}
|
}
|
if (deptIds.isEmpty()) {
|
return new HashMap<>();
|
}
|
Map<Long, String> out = new HashMap<>();
|
List<com.sinata.appointment.entity.SysDept> depts =
|
sysDeptMapper.selectBatchIds(deptIds);
|
for (com.sinata.appointment.entity.SysDept d : depts) {
|
out.put(d.getId(), d.getDeptName());
|
}
|
return out;
|
}
|
|
private static List<Long> dedup(List<Long> ids) {
|
if (ids == null || ids.isEmpty()) {
|
return List.of();
|
}
|
Set<Long> set = new java.util.LinkedHashSet<>(ids);
|
return new java.util.ArrayList<>(set);
|
}
|
|
private void validateMembers(List<Long> memberIds) {
|
if (memberIds == null) {
|
return;
|
}
|
for (Long id : memberIds) {
|
MpUser u = mpUserMapper.selectById(id);
|
if (u == null || (u.getDelFlag() != null && u.getDelFlag() == 1)) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "关联人员不存在: " + id);
|
}
|
if (!MiniUserType.EMPLOYEE.equals(u.getUserType())) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "仅允许绑定员工: " + id);
|
}
|
if (u.getStatus() != null && u.getStatus() == 0) {
|
throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "员工已禁用: " + id);
|
}
|
}
|
}
|
|
private void saveMembers(Long groupId, List<Long> memberIds) {
|
if (memberIds == null || memberIds.isEmpty()) {
|
return;
|
}
|
for (Long mid : memberIds) {
|
MpStaffGroupMember rel = new MpStaffGroupMember();
|
rel.setGroupId(groupId);
|
rel.setMpUserId(mid);
|
mpStaffGroupMemberMapper.insert(rel);
|
}
|
}
|
}
|