From 9d4cbff7464ebfd7803c2ce53ded49d0f73fe011 Mon Sep 17 00:00:00 2001
From: longz <longzhaoz@qq.com>
Date: 星期一, 29 六月 2026 20:33:55 +0800
Subject: [PATCH] 初步调整fix
---
backend/src/main/java/com/sinata/appointment/service/ApprovalEngineService.java | 417 ++++++++++++++++++++++++++++------------------------------
1 files changed, 202 insertions(+), 215 deletions(-)
diff --git a/backend/src/main/java/com/sinata/appointment/service/ApprovalEngineService.java b/backend/src/main/java/com/sinata/appointment/service/ApprovalEngineService.java
index e2c629f..47ab6cc 100644
--- a/backend/src/main/java/com/sinata/appointment/service/ApprovalEngineService.java
+++ b/backend/src/main/java/com/sinata/appointment/service/ApprovalEngineService.java
@@ -17,6 +17,7 @@
import com.sinata.appointment.entity.MpPassLocalUnitMember;
import com.sinata.appointment.entity.MpStaffGroupMember;
import com.sinata.appointment.entity.MpUser;
+import com.sinata.appointment.entity.SysDept;
import com.sinata.appointment.exception.BizException;
import com.sinata.appointment.mapper.ApprovalInstanceMapper;
import com.sinata.appointment.mapper.ApprovalTaskMapper;
@@ -25,6 +26,7 @@
import com.sinata.appointment.mapper.MpPassLocalUnitMemberMapper;
import com.sinata.appointment.mapper.MpStaffGroupMemberMapper;
import com.sinata.appointment.mapper.MpUserMapper;
+import com.sinata.appointment.mapper.SysDeptMapper;
import com.sinata.appointment.constants.BizErrorCode;
import java.time.LocalDateTime;
import java.util.ArrayList;
@@ -48,11 +50,13 @@
private static final TypeReference<List<Long>> LONG_LIST_TYPE = new TypeReference<>() {};
private static final String AUTO_APPROVE_COMMENT = "前序节点已审批,系统自动同意";
+ private static final String LOCAL_UNIT_GROUP_TOKEN = ":UNIT:";
private final BizApprovalNodeConfigMapper bizApprovalNodeConfigMapper;
private final ApprovalInstanceMapper approvalInstanceMapper;
private final ApprovalTaskMapper approvalTaskMapper;
private final MpUserMapper mpUserMapper;
+ private final SysDeptMapper sysDeptMapper;
private final MpPassLocalUnitMapper mpPassLocalUnitMapper;
private final MpPassLocalUnitMemberMapper mpPassLocalUnitMemberMapper;
private final MpStaffGroupMemberMapper mpStaffGroupMemberMapper;
@@ -118,15 +122,15 @@
}
if (ApprovalNodeCode.LEVEL2_DEPT_LEADER.equals(nodeCode)) {
Long deptId = resolveVisitedDeptId(order);
- return listEmployeeIdsByDeptAndLeaderFlag(deptId, true);
+ return getDeptLeaderIds(deptId);
}
if (ApprovalNodeCode.LEVEL4_VISIT_DEPT.equals(nodeCode)) {
Long competentDeptId = getVisitCompetentDeptId();
- return listEmployeeIdsByDeptAndLeaderFlag(competentDeptId, false);
+ return listEmployeeIdsByDeptWithLeaderFallback(competentDeptId);
}
if (ApprovalNodeCode.LEVEL5_VISIT_DEPT_LEADER.equals(nodeCode)) {
Long competentDeptId = getVisitCompetentDeptId();
- return listEmployeeIdsByDeptAndLeaderFlag(competentDeptId, true);
+ return getDeptLeaderIds(competentDeptId);
}
if (ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER.equals(nodeCode)) {
return List.of();
@@ -161,7 +165,59 @@
}
}
- private List<Long> listEmployeeIdsByDeptAndLeaderFlag(Long deptId, boolean leaderFlag) {
+ private List<Long> getDeptLeaderIds(Long deptId) {
+ if (deptId == null || deptId <= 0) {
+ return List.of();
+ }
+ SysDept dept = sysDeptMapper.selectById(deptId);
+ if (dept == null || dept.getDelFlag() != null && dept.getDelFlag() == 1) {
+ return List.of();
+ }
+ List<Long> leaderIds = parseDeptLeaderIds(dept);
+ if (leaderIds.isEmpty()) {
+ return List.of();
+ }
+ List<MpUser> leaders =
+ mpUserMapper.selectList(
+ new LambdaQueryWrapper<MpUser>()
+ .eq(MpUser::getDelFlag, 0)
+ .eq(MpUser::getStatus, 1)
+ .eq(MpUser::getUserType, MiniUserType.EMPLOYEE)
+ .in(MpUser::getId, leaderIds));
+ Map<Long, MpUser> leaderMap = new LinkedHashMap<>();
+ for (MpUser leader : leaders) {
+ leaderMap.put(leader.getId(), leader);
+ }
+ List<Long> result = new ArrayList<>();
+ for (Long id : leaderIds) {
+ if (leaderMap.containsKey(id)) {
+ result.add(id);
+ }
+ }
+ return result;
+ }
+
+ private List<Long> parseDeptLeaderIds(SysDept dept) {
+ List<Long> ids = new ArrayList<>();
+ Set<Long> seen = new LinkedHashSet<>();
+ if (StringUtils.hasText(dept.getLeaderIds())) {
+ for (String part : dept.getLeaderIds().split(",")) {
+ if (!StringUtils.hasText(part)) {
+ continue;
+ }
+ try {
+ Long id = Long.parseLong(part.trim());
+ if (id > 0 && seen.add(id)) {
+ ids.add(id);
+ }
+ } catch (NumberFormatException ignored) {
+ }
+ }
+ }
+ return ids;
+ }
+
+ private List<Long> listAllActiveEmployeeIdsByDept(Long deptId) {
if (deptId == null || deptId <= 0) {
return List.of();
}
@@ -171,9 +227,12 @@
.eq(MpUser::getDelFlag, 0)
.eq(MpUser::getStatus, 1)
.eq(MpUser::getUserType, MiniUserType.EMPLOYEE)
- .eq(MpUser::getDeptId, deptId)
- .eq(MpUser::getDeptLeaderFlag, leaderFlag));
- return users.stream().map(MpUser::getId).filter(Objects::nonNull).toList();
+ .eq(MpUser::getDeptId, deptId));
+ return users.stream().map(MpUser::getId).filter(Objects::nonNull).distinct().toList();
+ }
+
+ private List<Long> listEmployeeIdsByDeptWithLeaderFallback(Long deptId) {
+ return listAllActiveEmployeeIdsByDept(deptId);
}
@Transactional(rollbackFor = Exception.class)
@@ -252,7 +311,7 @@
inst.setStatus(ApprovalInstanceStatus.REJECTED);
inst.setFinishedAt(LocalDateTime.now());
approvalInstanceMapper.updateById(inst);
- List<ApprovalTask> pending =
+ List<ApprovalTask> remainingTasks =
approvalTaskMapper.selectList(
new LambdaQueryWrapper<ApprovalTask>()
.eq(ApprovalTask::getInstanceId, inst.getId())
@@ -260,9 +319,9 @@
ApprovalTask::getTaskStatus,
ApprovalTaskStatus.PENDING,
ApprovalTaskStatus.WAITING));
- for (ApprovalTask t : pending) {
+ for (ApprovalTask t : remainingTasks) {
t.setTaskStatus(ApprovalTaskStatus.REJECTED);
- t.setComment(reason);
+ t.setComment(null);
approvalTaskMapper.updateById(t);
}
}
@@ -380,94 +439,16 @@
}
private int createTasksForLocalUnitNode(Long instanceId, AppointmentOrder order, String initialStatus) {
- List<Long> localUnitIds = parseLongJsonArray(order.getPassLocalUnitIds());
- if (localUnitIds.isEmpty()) {
- return 0;
- }
- List<MpPassLocalUnit> units =
- mpPassLocalUnitMapper.selectList(
- new LambdaQueryWrapper<MpPassLocalUnit>()
- .eq(MpPassLocalUnit::getDelFlag, 0)
- .in(MpPassLocalUnit::getId, localUnitIds));
- if (units.isEmpty()) {
- return 0;
- }
- Map<Long, MpPassLocalUnit> unitMap =
- units.stream().collect(java.util.stream.Collectors.toMap(MpPassLocalUnit::getId, x -> x, (a, b) -> a));
-
- LinkedHashSet<Long> deptIds = new LinkedHashSet<>();
- LinkedHashSet<Long> groupIds = new LinkedHashSet<>();
- LinkedHashMap<String, List<Long>> tempGroups = new LinkedHashMap<>();
-
- for (Long unitId : localUnitIds) {
- MpPassLocalUnit unit = unitMap.get(unitId);
- if (unit == null || unit.getApproverType() == null) {
- continue;
- }
- if (unit.getApproverType() == LocalUnitApproverType.DEPT) {
- for (Long deptId : parseLongJsonArray(unit.getApproverDeptIds())) {
- if (deptId != null && deptId > 0) {
- deptIds.add(deptId);
- }
- }
- continue;
- }
- if (unit.getApproverType() == LocalUnitApproverType.STAFF_GROUP) {
- Long gid = unit.getApproverGroupId();
- if (gid != null && gid > 0) {
- groupIds.add(gid);
- }
- continue;
- }
- if (unit.getApproverType() == LocalUnitApproverType.STAFF) {
- List<MpPassLocalUnitMember> members =
- mpPassLocalUnitMemberMapper.selectList(
- new LambdaQueryWrapper<MpPassLocalUnitMember>()
- .eq(MpPassLocalUnitMember::getDelFlag, 0)
- .eq(MpPassLocalUnitMember::getUnitId, unitId)
- .orderByAsc(MpPassLocalUnitMember::getId));
- LinkedHashSet<Long> mids = new LinkedHashSet<>();
- for (MpPassLocalUnitMember m : members) {
- if (m.getMpUserId() != null && m.getMpUserId() > 0) {
- mids.add(m.getMpUserId());
- }
- }
- if (mids.isEmpty()) {
- continue;
- }
- List<Long> ordered = mids.stream().toList();
- String sign = buildTempGroupSign(ordered);
- tempGroups.putIfAbsent(sign, ordered);
- }
- }
-
+ List<LocalUnitApprovalGroup> groups = resolveLocalUnitApprovalGroups(order, false);
int total = 0;
- for (Long deptId : deptIds) {
- List<Long> assignees = listEmployeeIdsByDeptAndLeaderFlag(deptId, true);
- String key = ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER + ":DEPT:" + deptId;
- total += createTasksWithGroupKey(instanceId, ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER, key, assignees, initialStatus);
- }
- for (Long groupId : groupIds) {
- List<MpStaffGroupMember> members =
- mpStaffGroupMemberMapper.selectList(
- new LambdaQueryWrapper<MpStaffGroupMember>()
- .eq(MpStaffGroupMember::getDelFlag, 0)
- .eq(MpStaffGroupMember::getGroupId, groupId)
- .orderByAsc(MpStaffGroupMember::getId));
- LinkedHashSet<Long> mids = new LinkedHashSet<>();
- for (MpStaffGroupMember m : members) {
- if (m.getMpUserId() != null && m.getMpUserId() > 0) {
- mids.add(m.getMpUserId());
- }
- }
- List<Long> assignees = filterActiveEmployeeIds(mids.stream().toList());
- String key = ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER + ":GROUP:" + groupId;
- total += createTasksWithGroupKey(instanceId, ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER, key, assignees, initialStatus);
- }
- for (Map.Entry<String, List<Long>> e : tempGroups.entrySet()) {
- List<Long> assignees = filterActiveEmployeeIds(e.getValue());
- String key = ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER + ":TMP:" + e.getKey();
- total += createTasksWithGroupKey(instanceId, ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER, key, assignees, initialStatus);
+ for (LocalUnitApprovalGroup group : groups) {
+ total +=
+ createTasksWithGroupKey(
+ instanceId,
+ ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER,
+ buildLocalUnitGroupKey(group.unitId()),
+ group.assigneeIds(),
+ initialStatus);
}
return total;
}
@@ -493,122 +474,15 @@
"节点无审批人: " + resolveNodeName(ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER));
}
- List<MpPassLocalUnit> units =
- mpPassLocalUnitMapper.selectList(
- new LambdaQueryWrapper<MpPassLocalUnit>()
- .eq(MpPassLocalUnit::getDelFlag, 0)
- .in(MpPassLocalUnit::getId, localUnitIds));
- if (units.size() != localUnitIds.size()) {
- throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位配置缺失,无法生成审批人");
- }
- Map<Long, MpPassLocalUnit> unitMap =
- units.stream()
- .collect(
- java.util.stream.Collectors.toMap(
- MpPassLocalUnit::getId, x -> x, (a, b) -> a));
-
- LinkedHashSet<Long> deptIds = new LinkedHashSet<>();
- LinkedHashSet<Long> groupIds = new LinkedHashSet<>();
- LinkedHashMap<String, List<Long>> tempGroups = new LinkedHashMap<>();
-
- for (Long unitId : localUnitIds) {
- MpPassLocalUnit unit = unitMap.get(unitId);
- if (unit == null || unit.getApproverType() == null) {
- throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批类型缺失: " + unitId);
- }
- if (unit.getApproverType() == LocalUnitApproverType.DEPT) {
- List<Long> ids = parseLongJsonArray(unit.getApproverDeptIds());
- if (ids.isEmpty()) {
- throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批部门未配置: " + unitId);
- }
- deptIds.addAll(ids);
- continue;
- }
- if (unit.getApproverType() == LocalUnitApproverType.STAFF_GROUP) {
- Long gid = unit.getApproverGroupId();
- if (gid == null || gid <= 0) {
- throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批分组未配置: " + unitId);
- }
- groupIds.add(gid);
- continue;
- }
- if (unit.getApproverType() == LocalUnitApproverType.STAFF) {
- List<MpPassLocalUnitMember> members =
- mpPassLocalUnitMemberMapper.selectList(
- new LambdaQueryWrapper<MpPassLocalUnitMember>()
- .eq(MpPassLocalUnitMember::getDelFlag, 0)
- .eq(MpPassLocalUnitMember::getUnitId, unitId)
- .orderByAsc(MpPassLocalUnitMember::getId));
- LinkedHashSet<Long> mids = new LinkedHashSet<>();
- for (MpPassLocalUnitMember m : members) {
- if (m.getMpUserId() != null && m.getMpUserId() > 0) {
- mids.add(m.getMpUserId());
- }
- }
- if (mids.isEmpty()) {
- throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批人员未配置: " + unitId);
- }
- List<Long> ordered = mids.stream().toList();
- String sign = buildTempGroupSign(ordered);
- tempGroups.putIfAbsent(sign, ordered);
- continue;
- }
- throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批类型无效: " + unitId);
- }
-
+ List<LocalUnitApprovalGroup> groups = resolveLocalUnitApprovalGroups(order, true);
int total = 0;
- for (Long deptId : deptIds) {
- List<Long> assignees = listEmployeeIdsByDeptAndLeaderFlag(deptId, true);
- if (assignees.isEmpty()) {
- throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "审批部门无负责人: " + deptId);
- }
- String key = ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER + ":DEPT:" + deptId;
+ for (LocalUnitApprovalGroup group : groups) {
total +=
createTasksWithGroupKey(
instanceId,
ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER,
- key,
- assignees,
- initialStatus);
- }
- for (Long groupId : groupIds) {
- List<MpStaffGroupMember> members =
- mpStaffGroupMemberMapper.selectList(
- new LambdaQueryWrapper<MpStaffGroupMember>()
- .eq(MpStaffGroupMember::getDelFlag, 0)
- .eq(MpStaffGroupMember::getGroupId, groupId)
- .orderByAsc(MpStaffGroupMember::getId));
- LinkedHashSet<Long> mids = new LinkedHashSet<>();
- for (MpStaffGroupMember m : members) {
- if (m.getMpUserId() != null && m.getMpUserId() > 0) {
- mids.add(m.getMpUserId());
- }
- }
- List<Long> assignees = filterActiveEmployeeIds(mids.stream().toList());
- if (assignees.isEmpty()) {
- throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "审批分组无有效成员: " + groupId);
- }
- String key = ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER + ":GROUP:" + groupId;
- total +=
- createTasksWithGroupKey(
- instanceId,
- ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER,
- key,
- assignees,
- initialStatus);
- }
- for (Map.Entry<String, List<Long>> e : tempGroups.entrySet()) {
- List<Long> assignees = filterActiveEmployeeIds(e.getValue());
- if (assignees.isEmpty()) {
- throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "临时审批组无有效成员");
- }
- String key = ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER + ":TMP:" + e.getKey();
- total +=
- createTasksWithGroupKey(
- instanceId,
- ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER,
- key,
- assignees,
+ buildLocalUnitGroupKey(group.unitId()),
+ group.assigneeIds(),
initialStatus);
}
if (total <= 0) {
@@ -617,6 +491,125 @@
"节点无审批人: " + resolveNodeName(ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER));
}
return total;
+ }
+
+ private List<LocalUnitApprovalGroup> resolveLocalUnitApprovalGroups(
+ AppointmentOrder order, boolean strict) {
+ List<Long> localUnitIds = parseLongJsonArray(order.getPassLocalUnitIds());
+ if (localUnitIds.isEmpty()) {
+ return List.of();
+ }
+ List<MpPassLocalUnit> units =
+ mpPassLocalUnitMapper.selectList(
+ new LambdaQueryWrapper<MpPassLocalUnit>()
+ .eq(MpPassLocalUnit::getDelFlag, 0)
+ .in(MpPassLocalUnit::getId, localUnitIds));
+ Map<Long, MpPassLocalUnit> unitMap =
+ units.stream()
+ .collect(
+ java.util.stream.Collectors.toMap(
+ MpPassLocalUnit::getId, x -> x, (a, b) -> a));
+ if (strict && unitMap.size() != localUnitIds.size()) {
+ throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位配置缺失,无法生成审批人");
+ }
+
+ List<LocalUnitApprovalGroup> groups = new ArrayList<>();
+ for (Long unitId : localUnitIds) {
+ MpPassLocalUnit unit = unitMap.get(unitId);
+ if (unit == null) {
+ if (strict) {
+ throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位配置缺失,无法生成审批人");
+ }
+ continue;
+ }
+ List<Long> assignees = resolveLocalUnitAssignees(unitId, unit, strict);
+ if (assignees.isEmpty()) {
+ continue;
+ }
+ groups.add(new LocalUnitApprovalGroup(unitId, assignees));
+ }
+ return groups;
+ }
+
+ private List<Long> resolveLocalUnitAssignees(Long unitId, MpPassLocalUnit unit, boolean strict) {
+ Integer approverType = unit.getApproverType();
+ if (approverType == null) {
+ if (strict) {
+ throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批类型缺失: " + unitId);
+ }
+ return List.of();
+ }
+ if (approverType == LocalUnitApproverType.DEPT) {
+ List<Long> deptIds = parseLongJsonArray(unit.getApproverDeptIds());
+ if (deptIds.isEmpty()) {
+ if (strict) {
+ throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批部门未配置: " + unitId);
+ }
+ return List.of();
+ }
+ LinkedHashSet<Long> assignees = new LinkedHashSet<>();
+ for (Long deptId : deptIds) {
+ List<Long> deptApprovers = listAllActiveEmployeeIdsByDept(deptId);
+ if (strict && deptApprovers.isEmpty()) {
+ throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "审批部门无可用审批人: " + deptId);
+ }
+ assignees.addAll(deptApprovers);
+ }
+ return assignees.stream().toList();
+ }
+ if (approverType == LocalUnitApproverType.STAFF_GROUP) {
+ Long groupId = unit.getApproverGroupId();
+ if (groupId == null || groupId <= 0) {
+ if (strict) {
+ throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批分组未配置: " + unitId);
+ }
+ return List.of();
+ }
+ List<MpStaffGroupMember> members =
+ mpStaffGroupMemberMapper.selectList(
+ new LambdaQueryWrapper<MpStaffGroupMember>()
+ .eq(MpStaffGroupMember::getDelFlag, 0)
+ .eq(MpStaffGroupMember::getGroupId, groupId)
+ .orderByAsc(MpStaffGroupMember::getId));
+ LinkedHashSet<Long> mids = new LinkedHashSet<>();
+ for (MpStaffGroupMember member : members) {
+ if (member.getMpUserId() != null && member.getMpUserId() > 0) {
+ mids.add(member.getMpUserId());
+ }
+ }
+ List<Long> assignees = filterActiveEmployeeIds(mids.stream().toList());
+ if (strict && assignees.isEmpty()) {
+ throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "审批分组无有效成员: " + groupId);
+ }
+ return assignees;
+ }
+ if (approverType == LocalUnitApproverType.STAFF) {
+ List<MpPassLocalUnitMember> members =
+ mpPassLocalUnitMemberMapper.selectList(
+ new LambdaQueryWrapper<MpPassLocalUnitMember>()
+ .eq(MpPassLocalUnitMember::getDelFlag, 0)
+ .eq(MpPassLocalUnitMember::getUnitId, unitId)
+ .orderByAsc(MpPassLocalUnitMember::getId));
+ LinkedHashSet<Long> mids = new LinkedHashSet<>();
+ for (MpPassLocalUnitMember member : members) {
+ if (member.getMpUserId() != null && member.getMpUserId() > 0) {
+ mids.add(member.getMpUserId());
+ }
+ }
+ List<Long> assignees = filterActiveEmployeeIds(mids.stream().toList());
+ if (strict && assignees.isEmpty()) {
+ throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批人员未配置: " + unitId);
+ }
+ return assignees;
+ }
+ if (strict) {
+ throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批类型无效: " + unitId);
+ }
+ return List.of();
+ }
+
+ private String buildLocalUnitGroupKey(Long unitId) {
+ return ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER + LOCAL_UNIT_GROUP_TOKEN + unitId;
}
private String resolveNodeName(String nodeCode) {
@@ -743,11 +736,5 @@
return out;
}
- private String buildTempGroupSign(List<Long> memberIds) {
- String joined = memberIds.stream().map(String::valueOf).collect(java.util.stream.Collectors.joining("-"));
- if (joined.length() <= 40) {
- return joined;
- }
- return Integer.toHexString(joined.hashCode());
- }
+ private record LocalUnitApprovalGroup(Long unitId, List<Long> assigneeIds) {}
}
--
Gitblit v1.7.1