|
|
@@ -60,7 +60,7 @@ public class ReSubmitApprovalService {
|
|
|
/**
|
|
|
* 批量历史补偿的目标分片大小;以员工整组为边界,允许单个员工整组略超目标值。
|
|
|
*/
|
|
|
- private static final int TARGET_APPROVAL_SUBTABLE_ROWS = 100;
|
|
|
+ private static final int TARGET_APPROVAL_SUBTABLE_ROWS = 300;
|
|
|
|
|
|
/**
|
|
|
* 兜底部门主管 userId(Raymond,前端 v2.42 保持一致)
|
|
|
@@ -261,11 +261,13 @@ public class ReSubmitApprovalService {
|
|
|
// 3a) 项目工时:每个 PM 一张单
|
|
|
for (ManagerGroup mg : cr.managerGroups.values()) {
|
|
|
try {
|
|
|
- String action = processManagerApproval(mg, monthText, uid);
|
|
|
- if ("created".equals(action)) {
|
|
|
- billableCreated++;
|
|
|
- } else if ("updated".equals(action)) {
|
|
|
- billableUpdated++;
|
|
|
+ List<String> actions = processManagerApproval(mg, monthText, uid);
|
|
|
+ for (String action : actions) {
|
|
|
+ if ("created".equals(action)) {
|
|
|
+ billableCreated++;
|
|
|
+ } else if ("updated".equals(action)) {
|
|
|
+ billableUpdated++;
|
|
|
+ }
|
|
|
}
|
|
|
} catch (Exception ex) {
|
|
|
failed++;
|
|
|
@@ -275,11 +277,14 @@ public class ReSubmitApprovalService {
|
|
|
// 3b) 其他工时:一人一张单,approver 用该员工部门主管(记录中带),否则兜底 Raymond
|
|
|
if (!cr.otherRows.isEmpty()) {
|
|
|
try {
|
|
|
- String action = processOtherApproval(cr.otherRows, cr.deptManagerId, monthText, uid);
|
|
|
- if ("created".equals(action)) {
|
|
|
- otherCreated++;
|
|
|
- } else if ("updated".equals(action)) {
|
|
|
- otherUpdated++;
|
|
|
+ List<String> actions = processOtherApproval(
|
|
|
+ cr.otherRows, cr.deptManagerId, monthText, uid);
|
|
|
+ for (String action : actions) {
|
|
|
+ if ("created".equals(action)) {
|
|
|
+ otherCreated++;
|
|
|
+ } else if ("updated".equals(action)) {
|
|
|
+ otherUpdated++;
|
|
|
+ }
|
|
|
}
|
|
|
} catch (Exception ex) {
|
|
|
failed++;
|
|
|
@@ -331,14 +336,70 @@ public class ReSubmitApprovalService {
|
|
|
|
|
|
Map<String, Object> formData = JSON.parseObject(
|
|
|
request.getFormDataJson(), Map.class);
|
|
|
- startProcessInstance(
|
|
|
- request.getFormUuid(),
|
|
|
- request.getProcessCode(),
|
|
|
- formData,
|
|
|
- request.getDeptId());
|
|
|
+ List<Map<String, Object>> shards = billable
|
|
|
+ ? splitApprovalStartFormData(formData)
|
|
|
+ : splitOtherApprovalStartFormData(formData);
|
|
|
+ for (Map<String, Object> shard : shards) {
|
|
|
+ startProcessInstance(
|
|
|
+ request.getFormUuid(),
|
|
|
+ request.getProcessCode(),
|
|
|
+ shard,
|
|
|
+ request.getDeptId());
|
|
|
+ }
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+ private List<Map<String, Object>> splitApprovalStartFormData(Map<String, Object> formData) {
|
|
|
+ List<Map> billableRows = listOrEmpty(formData, A_BIL_TABLE);
|
|
|
+ List<Map> nonBillableRows = listOrEmpty(formData, A_NON_TABLE);
|
|
|
+ List<RowShard> rowShards = splitRowsByHardLimit(billableRows, nonBillableRows);
|
|
|
+ List<Map<String, Object>> result = new ArrayList<>();
|
|
|
+ for (RowShard rowShard : rowShards) {
|
|
|
+ Map<String, Object> shard = new HashMap<>(formData);
|
|
|
+ shard.remove(A_BIL_TABLE);
|
|
|
+ shard.remove(A_NON_TABLE);
|
|
|
+ putRowsIfPresent(shard, A_BIL_TABLE, rowShard.firstRows);
|
|
|
+ putRowsIfPresent(shard, A_NON_TABLE, rowShard.secondRows);
|
|
|
+ double billableHours = sumHours(rowShard.firstRows, A_BIL_HOURS);
|
|
|
+ double nonBillableHours = sumHours(rowShard.secondRows, A_NON_HOURS);
|
|
|
+ shard.put(A_PROJECT_HOURS, billableHours);
|
|
|
+ shard.put(A_NON_PROJECT_HOURS, nonBillableHours);
|
|
|
+ shard.put(A_TOTAL_HOURS, billableHours + nonBillableHours);
|
|
|
+ result.add(shard);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Map<String, Object>> splitOtherApprovalStartFormData(Map<String, Object> formData) {
|
|
|
+ List<Map> rows = listOrEmpty(formData, O_SUB_TABLE);
|
|
|
+ List<RowShard> rowShards = splitRowsByHardLimit(rows, Collections.emptyList());
|
|
|
+ List<Map<String, Object>> result = new ArrayList<>();
|
|
|
+ for (RowShard rowShard : rowShards) {
|
|
|
+ Map<String, Object> shard = new HashMap<>(formData);
|
|
|
+ shard.remove(O_SUB_TABLE);
|
|
|
+ putRowsIfPresent(shard, O_SUB_TABLE, rowShard.firstRows);
|
|
|
+ shard.put(O_TOTAL_HOURS, sumHours(rowShard.firstRows, O_SUB_HOURS));
|
|
|
+ result.add(shard);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void putRowsIfPresent(Map<String, Object> formData,
|
|
|
+ String tableFieldId,
|
|
|
+ List<Map> rows) {
|
|
|
+ if (!rows.isEmpty()) {
|
|
|
+ formData.put(tableFieldId, rows);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private double sumHours(List<Map> rows, String hoursFieldId) {
|
|
|
+ double total = 0;
|
|
|
+ for (Map row : rows) {
|
|
|
+ total += num(row, hoursFieldId);
|
|
|
+ }
|
|
|
+ return total;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* collectApprovalData + collectOtherApprovalData 的等价:
|
|
|
* 遍历该员工该月每天记录,按项目工时 / 其他工时反构造 delta 行,按 PM 分组
|
|
|
@@ -654,7 +715,19 @@ public class ReSubmitApprovalService {
|
|
|
* processManagerApproval 等价 — queryProcess(RUNNING) 有则 update / 无则 create。
|
|
|
* COMPLETED 覆盖已在 collectApprovalData 前过滤,避免历史已完成单被重复发起。
|
|
|
*/
|
|
|
- private String processManagerApproval(ManagerGroup mg, String monthText, String submitterUid) {
|
|
|
+ private List<String> processManagerApproval(ManagerGroup mg,
|
|
|
+ String monthText,
|
|
|
+ String submitterUid) {
|
|
|
+ List<String> actions = new ArrayList<>();
|
|
|
+ for (ManagerGroup shard : splitManagerGroupByHardLimit(mg)) {
|
|
|
+ actions.add(processManagerApprovalShard(shard, monthText, submitterUid));
|
|
|
+ }
|
|
|
+ return actions;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String processManagerApprovalShard(ManagerGroup mg,
|
|
|
+ String monthText,
|
|
|
+ String submitterUid) {
|
|
|
Map<String, Object> searchFieldJson = new HashMap<>();
|
|
|
searchFieldJson.put(A_MONTH_TEXT, monthText);
|
|
|
// fixme employeeField 查询用数组: 单值 [uid]
|
|
|
@@ -671,8 +744,9 @@ public class ReSubmitApprovalService {
|
|
|
if (!managerApprovalHasCapacity(finstId, mg, submitterUid, monthText, existingFormData)) {
|
|
|
continue;
|
|
|
}
|
|
|
- updateApprovalInstance(finstId, mg, monthText, submitterUid, existingFormData);
|
|
|
- return "updated";
|
|
|
+ if (updateApprovalInstance(finstId, mg, monthText, submitterUid, existingFormData)) {
|
|
|
+ return "updated";
|
|
|
+ }
|
|
|
}
|
|
|
// fixme: 单张审批子表达到 500 行后必须新建分片,继续覆盖会丢失第 501 行后的明细。
|
|
|
createApprovalInstance(mg, monthText, submitterUid);
|
|
|
@@ -706,8 +780,9 @@ public class ReSubmitApprovalService {
|
|
|
existBil, A_BIL_SUBMITTER_UID, A_BIL_DAY_TEXT, A_BIL_PROJECT_CODE);
|
|
|
int existingNonBillableCount = countUniqueRows(
|
|
|
existNon, A_NON_SUBMITTER_UID, A_NON_DAY_TEXT, A_NON_PROJECT_CODE);
|
|
|
- return canAppendToApprovalShard(existingBillableCount, billable.rows.size())
|
|
|
- && canAppendToApprovalShard(existingNonBillableCount, nonBillable.rows.size());
|
|
|
+ int existingCount = existingBillableCount + existingNonBillableCount;
|
|
|
+ int mergedCount = billable.rows.size() + nonBillable.rows.size();
|
|
|
+ return canAppendToApprovalShard(existingCount, mergedCount);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -715,9 +790,9 @@ public class ReSubmitApprovalService {
|
|
|
*/
|
|
|
private void createApprovalInstance(ManagerGroup mg, String monthText, String submitterUid) {
|
|
|
McException.assertAccessException(
|
|
|
- !hasSubtableCapacity(mg.billableRows.size())
|
|
|
- || !hasSubtableCapacity(mg.nonBillableRows.size()),
|
|
|
- "单个员工的审批明细超过宜搭子表 500 行限制");
|
|
|
+ !hasSubtableCapacity(
|
|
|
+ mg.billableRows.size() + mg.nonBillableRows.size()),
|
|
|
+ "单张工时审批明细不能超过 500 行");
|
|
|
Map<String, Object> formData = new HashMap<>();
|
|
|
formData.put(A_MONTH_DATE, monthToTs(monthText));
|
|
|
formData.put(A_MONTH_TEXT, monthText);
|
|
|
@@ -755,8 +830,9 @@ public class ReSubmitApprovalService {
|
|
|
/**
|
|
|
* 累计更新已有 RUNNING 工时审批单(按唯一键 userId + dayText + projectCode 去重合并)
|
|
|
*/
|
|
|
- private void updateApprovalInstance(String formInstanceId, ManagerGroup mg,
|
|
|
- String monthText, String submitterUid, Map existingFormData) {
|
|
|
+ private boolean updateApprovalInstance(String formInstanceId, ManagerGroup mg,
|
|
|
+ String monthText, String submitterUid,
|
|
|
+ Map existingFormData) {
|
|
|
List<Map> existBil = resolveFullRows(
|
|
|
formInstanceId,
|
|
|
whConf.getFormUuidApproval(),
|
|
|
@@ -775,9 +851,12 @@ public class ReSubmitApprovalService {
|
|
|
A_NON_SUBMITTER_UID, A_NON_DAY_TEXT, A_NON_PROJECT_CODE,
|
|
|
(br) -> buildNonBillableApprovalRow(br, submitterUid));
|
|
|
|
|
|
+ if (!hasSubtableCapacity(mrBil.rows.size() + mrNon.rows.size())) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
if (mrBil.addedHours == 0 && mrNon.addedHours == 0) {
|
|
|
log.info("[补发起] 已存在等效明细,跳过更新 formInstanceId={}", formInstanceId);
|
|
|
- return;
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
double existProject = num(existingFormData, A_PROJECT_HOURS);
|
|
|
@@ -833,13 +912,28 @@ public class ReSubmitApprovalService {
|
|
|
.build(), YDConf.FORM_OPERATION.update);
|
|
|
log.info("[补发起] 累计更新工时审批单 formInstanceId={} pm={} addedBil={} addedNon={}",
|
|
|
formInstanceId, mg.managerId, mrBil.addedHours, mrNon.addedHours);
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 其他工时审批处理(与 processManagerApproval 同构,但 approver 为员工的部门主管)
|
|
|
*/
|
|
|
- private String processOtherApproval(List<OtherRow> otherRows, String deptManagerId,
|
|
|
- String monthText, String submitterUid) {
|
|
|
+ private List<String> processOtherApproval(List<OtherRow> otherRows,
|
|
|
+ String deptManagerId,
|
|
|
+ String monthText,
|
|
|
+ String submitterUid) {
|
|
|
+ List<String> actions = new ArrayList<>();
|
|
|
+ for (List<OtherRow> shard : splitListByHardLimit(otherRows)) {
|
|
|
+ actions.add(processOtherApprovalShard(
|
|
|
+ shard, deptManagerId, monthText, submitterUid));
|
|
|
+ }
|
|
|
+ return actions;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String processOtherApprovalShard(List<OtherRow> otherRows,
|
|
|
+ String deptManagerId,
|
|
|
+ String monthText,
|
|
|
+ String submitterUid) {
|
|
|
Map<String, Object> searchFieldJson = new HashMap<>();
|
|
|
searchFieldJson.put(O_MONTH_TEXT, monthText);
|
|
|
searchFieldJson.put(O_DEPT_MANAGER, Collections.singletonList(deptManagerId));
|
|
|
@@ -854,8 +948,10 @@ public class ReSubmitApprovalService {
|
|
|
if (!otherApprovalHasCapacity(finstId, otherRows, submitterUid, existingFormData)) {
|
|
|
continue;
|
|
|
}
|
|
|
- updateOtherApprovalInstance(finstId, otherRows, monthText, submitterUid, existingFormData);
|
|
|
- return "updated";
|
|
|
+ if (updateOtherApprovalInstance(
|
|
|
+ finstId, otherRows, monthText, submitterUid, existingFormData)) {
|
|
|
+ return "updated";
|
|
|
+ }
|
|
|
}
|
|
|
createOtherApprovalInstance(otherRows, deptManagerId, monthText, submitterUid);
|
|
|
return "created";
|
|
|
@@ -914,8 +1010,11 @@ public class ReSubmitApprovalService {
|
|
|
submitterUid, deptManagerId, monthText, total, sub.size());
|
|
|
}
|
|
|
|
|
|
- private void updateOtherApprovalInstance(String formInstanceId, List<OtherRow> otherRows,
|
|
|
- String monthText, String submitterUid, Map existingFormData) {
|
|
|
+ private boolean updateOtherApprovalInstance(String formInstanceId,
|
|
|
+ List<OtherRow> otherRows,
|
|
|
+ String monthText,
|
|
|
+ String submitterUid,
|
|
|
+ Map existingFormData) {
|
|
|
List<Map> existRows = resolveFullRows(
|
|
|
formInstanceId,
|
|
|
whConf.getFormUuidOtherApproval(),
|
|
|
@@ -943,9 +1042,12 @@ public class ReSubmitApprovalService {
|
|
|
merged.add(buildOtherApprovalRow(or, submitterUid));
|
|
|
addedHours += or.hours;
|
|
|
}
|
|
|
+ if (!hasSubtableCapacity(merged.size())) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
if (addedHours == 0) {
|
|
|
log.info("[补发起] 其他工时无新增明细 formInstanceId={}", formInstanceId);
|
|
|
- return;
|
|
|
+ return true;
|
|
|
}
|
|
|
double existTotal = num(existingFormData, O_TOTAL_HOURS);
|
|
|
Map<String, Object> updateData = new HashMap<>();
|
|
|
@@ -982,6 +1084,7 @@ public class ReSubmitApprovalService {
|
|
|
.useLatestVersion(true)
|
|
|
.build(), YDConf.FORM_OPERATION.update);
|
|
|
log.info("[补发起] 累计更新其他工时审批单 formInstanceId={} addedHours={}", formInstanceId, addedHours);
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
|
@@ -1038,6 +1141,83 @@ public class ReSubmitApprovalService {
|
|
|
double addedHours = 0;
|
|
|
}
|
|
|
|
|
|
+ private static class RowShard {
|
|
|
+ List<Map> firstRows = new ArrayList<>();
|
|
|
+ List<Map> secondRows = new ArrayList<>();
|
|
|
+
|
|
|
+ int size() {
|
|
|
+ return firstRows.size() + secondRows.size();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<RowShard> splitRowsByHardLimit(List<Map> firstRows, List<Map> secondRows) {
|
|
|
+ List<RowShard> shards = new ArrayList<>();
|
|
|
+ shards.add(new RowShard());
|
|
|
+ for (Map row : firstRows) {
|
|
|
+ appendRowToShard(shards, row, true);
|
|
|
+ }
|
|
|
+ for (Map row : secondRows) {
|
|
|
+ appendRowToShard(shards, row, false);
|
|
|
+ }
|
|
|
+ return shards;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void appendRowToShard(List<RowShard> shards, Map row, boolean firstTable) {
|
|
|
+ RowShard current = shards.get(shards.size() - 1);
|
|
|
+ if (current.size() >= MAX_APPROVAL_SUBTABLE_ROWS) {
|
|
|
+ current = new RowShard();
|
|
|
+ shards.add(current);
|
|
|
+ }
|
|
|
+ (firstTable ? current.firstRows : current.secondRows).add(row);
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<ManagerGroup> splitManagerGroupByHardLimit(ManagerGroup source) {
|
|
|
+ if (source.billableRows.size() + source.nonBillableRows.size()
|
|
|
+ <= MAX_APPROVAL_SUBTABLE_ROWS) {
|
|
|
+ return Collections.singletonList(source);
|
|
|
+ }
|
|
|
+ List<ManagerGroup> shards = new ArrayList<>();
|
|
|
+ shards.add(newManagerGroup(source.managerId));
|
|
|
+ for (BillRow row : source.billableRows) {
|
|
|
+ appendManagerRow(shards, row, true);
|
|
|
+ }
|
|
|
+ for (BillRow row : source.nonBillableRows) {
|
|
|
+ appendManagerRow(shards, row, false);
|
|
|
+ }
|
|
|
+ return shards;
|
|
|
+ }
|
|
|
+
|
|
|
+ private ManagerGroup newManagerGroup(String managerId) {
|
|
|
+ ManagerGroup group = new ManagerGroup();
|
|
|
+ group.managerId = managerId;
|
|
|
+ return group;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void appendManagerRow(List<ManagerGroup> shards, BillRow row, boolean billable) {
|
|
|
+ ManagerGroup current = shards.get(shards.size() - 1);
|
|
|
+ if (current.billableRows.size() + current.nonBillableRows.size()
|
|
|
+ >= MAX_APPROVAL_SUBTABLE_ROWS) {
|
|
|
+ current = newManagerGroup(current.managerId);
|
|
|
+ shards.add(current);
|
|
|
+ }
|
|
|
+ if (billable) {
|
|
|
+ current.billableRows.add(row);
|
|
|
+ current.billableTotal += row.hours;
|
|
|
+ } else {
|
|
|
+ current.nonBillableRows.add(row);
|
|
|
+ current.nonBillableTotal += row.hours;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private <T> List<List<T>> splitListByHardLimit(List<T> rows) {
|
|
|
+ List<List<T>> shards = new ArrayList<>();
|
|
|
+ for (int from = 0; from < rows.size(); from += MAX_APPROVAL_SUBTABLE_ROWS) {
|
|
|
+ int to = Math.min(from + MAX_APPROVAL_SUBTABLE_ROWS, rows.size());
|
|
|
+ shards.add(new ArrayList<>(rows.subList(from, to)));
|
|
|
+ }
|
|
|
+ return shards;
|
|
|
+ }
|
|
|
+
|
|
|
static boolean hasSubtableCapacity(int rowCount) {
|
|
|
return rowCount <= MAX_APPROVAL_SUBTABLE_ROWS;
|
|
|
}
|