|
|
@@ -52,6 +52,16 @@ public class ReSubmitApprovalService {
|
|
|
*/
|
|
|
private static final String APPROVAL_OPINION_AGREE = "同意";
|
|
|
|
|
|
+ /**
|
|
|
+ * 宜搭单个子表组件可稳定查询和覆盖更新的最大行数。
|
|
|
+ */
|
|
|
+ private static final int MAX_APPROVAL_SUBTABLE_ROWS = 500;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量历史补偿的目标分片大小;以员工整组为边界,允许单个员工整组略超目标值。
|
|
|
+ */
|
|
|
+ private static final int TARGET_APPROVAL_SUBTABLE_ROWS = 100;
|
|
|
+
|
|
|
/**
|
|
|
* 兜底部门主管 userId(Raymond,前端 v2.42 保持一致)
|
|
|
*/
|
|
|
@@ -651,31 +661,63 @@ public class ReSubmitApprovalService {
|
|
|
searchFieldJson.put(A_MANAGER, Collections.singletonList(mg.managerId));
|
|
|
|
|
|
List<Map> running = queryProcessRunning(whConf.getFormUuidApproval(), searchFieldJson);
|
|
|
- if (running.isEmpty()) {
|
|
|
- createApprovalInstance(mg, monthText, submitterUid);
|
|
|
- return "created";
|
|
|
- }
|
|
|
- // 取第一条 RUNNING
|
|
|
- Map first = running.get(0);
|
|
|
- String pid = str(first, "processInstanceId");
|
|
|
- String finstId = firstNonBlank(str(first, "formInstanceId"), pid);
|
|
|
- Map existingFormData = (Map) first.get("formData");
|
|
|
- // formData 未直接返回时,回查详情
|
|
|
- if (existingFormData == null || existingFormData.isEmpty()) {
|
|
|
- existingFormData = ydClient.queryData(YDParam.builder()
|
|
|
- .appType(whConf.getYidaAppType())
|
|
|
- .systemToken(whConf.getYidaSystemToken())
|
|
|
- .formInstanceId(finstId)
|
|
|
- .build(), YDConf.FORM_QUERY.retrieve_id).getFormData();
|
|
|
+ // 优先追加到最新分片,避免每名员工都先扫描已满 500 行的旧审批单。
|
|
|
+ running.sort(Comparator.comparingLong(
|
|
|
+ (Map item) -> numLong(item, "gmtModified")).reversed());
|
|
|
+ for (Map item : running) {
|
|
|
+ String pid = str(item, "processInstanceId");
|
|
|
+ String finstId = firstNonBlank(str(item, "formInstanceId"), pid);
|
|
|
+ Map existingFormData = processFormData(item, finstId);
|
|
|
+ if (!managerApprovalHasCapacity(finstId, mg, submitterUid, monthText, existingFormData)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ updateApprovalInstance(finstId, mg, monthText, submitterUid, existingFormData);
|
|
|
+ return "updated";
|
|
|
}
|
|
|
- updateApprovalInstance(finstId, mg, monthText, submitterUid, existingFormData);
|
|
|
- return "updated";
|
|
|
+ // fixme: 单张审批子表达到 500 行后必须新建分片,继续覆盖会丢失第 501 行后的明细。
|
|
|
+ createApprovalInstance(mg, monthText, submitterUid);
|
|
|
+ return "created";
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean managerApprovalHasCapacity(String formInstanceId,
|
|
|
+ ManagerGroup mg,
|
|
|
+ String submitterUid,
|
|
|
+ String monthText,
|
|
|
+ Map existingFormData) {
|
|
|
+ List<Map> existBil = resolveFullRows(
|
|
|
+ formInstanceId,
|
|
|
+ whConf.getFormUuidApproval(),
|
|
|
+ A_BIL_TABLE,
|
|
|
+ (List<Map>) (existingFormData == null ? null : existingFormData.get(A_BIL_TABLE)));
|
|
|
+ List<Map> existNon = resolveFullRows(
|
|
|
+ formInstanceId,
|
|
|
+ whConf.getFormUuidApproval(),
|
|
|
+ A_NON_TABLE,
|
|
|
+ (List<Map>) (existingFormData == null ? null : existingFormData.get(A_NON_TABLE)));
|
|
|
+ MergeResult billable = mergeSubRows(
|
|
|
+ existBil, mg.billableRows, submitterUid, monthText,
|
|
|
+ A_BIL_SUBMITTER_UID, A_BIL_DAY_TEXT, A_BIL_PROJECT_CODE,
|
|
|
+ br -> buildBillableApprovalRow(br, submitterUid));
|
|
|
+ MergeResult nonBillable = mergeSubRows(
|
|
|
+ existNon, mg.nonBillableRows, submitterUid, monthText,
|
|
|
+ A_NON_SUBMITTER_UID, A_NON_DAY_TEXT, A_NON_PROJECT_CODE,
|
|
|
+ br -> buildNonBillableApprovalRow(br, submitterUid));
|
|
|
+ int existingBillableCount = countUniqueRows(
|
|
|
+ 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());
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 创建新的工时审批实例(startProcessInstance)
|
|
|
*/
|
|
|
private void createApprovalInstance(ManagerGroup mg, String monthText, String submitterUid) {
|
|
|
+ McException.assertAccessException(
|
|
|
+ !hasSubtableCapacity(mg.billableRows.size())
|
|
|
+ || !hasSubtableCapacity(mg.nonBillableRows.size()),
|
|
|
+ "单个员工的审批明细超过宜搭子表 500 行限制");
|
|
|
Map<String, Object> formData = new HashMap<>();
|
|
|
formData.put(A_MONTH_DATE, monthToTs(monthText));
|
|
|
formData.put(A_MONTH_TEXT, monthText);
|
|
|
@@ -803,27 +845,49 @@ public class ReSubmitApprovalService {
|
|
|
searchFieldJson.put(O_DEPT_MANAGER, Collections.singletonList(deptManagerId));
|
|
|
|
|
|
List<Map> running = queryProcessRunning(whConf.getFormUuidOtherApproval(), searchFieldJson);
|
|
|
- if (running.isEmpty()) {
|
|
|
- createOtherApprovalInstance(otherRows, deptManagerId, monthText, submitterUid);
|
|
|
- return "created";
|
|
|
+ running.sort(Comparator.comparingLong(
|
|
|
+ (Map item) -> numLong(item, "gmtModified")).reversed());
|
|
|
+ for (Map item : running) {
|
|
|
+ String pid = str(item, "processInstanceId");
|
|
|
+ String finstId = firstNonBlank(str(item, "formInstanceId"), pid);
|
|
|
+ Map existingFormData = processFormData(item, finstId);
|
|
|
+ if (!otherApprovalHasCapacity(finstId, otherRows, submitterUid, existingFormData)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ updateOtherApprovalInstance(finstId, otherRows, monthText, submitterUid, existingFormData);
|
|
|
+ return "updated";
|
|
|
}
|
|
|
- Map first = running.get(0);
|
|
|
- String pid = str(first, "processInstanceId");
|
|
|
- String finstId = firstNonBlank(str(first, "formInstanceId"), pid);
|
|
|
- Map existingFormData = (Map) first.get("formData");
|
|
|
- if (existingFormData == null || existingFormData.isEmpty()) {
|
|
|
- existingFormData = ydClient.queryData(YDParam.builder()
|
|
|
- .appType(whConf.getYidaAppType())
|
|
|
- .systemToken(whConf.getYidaSystemToken())
|
|
|
- .formInstanceId(finstId)
|
|
|
- .build(), YDConf.FORM_QUERY.retrieve_id).getFormData();
|
|
|
+ createOtherApprovalInstance(otherRows, deptManagerId, monthText, submitterUid);
|
|
|
+ return "created";
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean otherApprovalHasCapacity(String formInstanceId,
|
|
|
+ List<OtherRow> otherRows,
|
|
|
+ String submitterUid,
|
|
|
+ Map existingFormData) {
|
|
|
+ List<Map> existing = resolveFullRows(
|
|
|
+ formInstanceId,
|
|
|
+ whConf.getFormUuidOtherApproval(),
|
|
|
+ O_SUB_TABLE,
|
|
|
+ (List<Map>) (existingFormData == null ? null : existingFormData.get(O_SUB_TABLE)));
|
|
|
+ Set<String> keys = new HashSet<>();
|
|
|
+ for (Map row : existing) {
|
|
|
+ keys.add(str(row, O_SUB_SUBMITTER_UID) + "|"
|
|
|
+ + str(row, O_SUB_DAY_TEXT) + "|"
|
|
|
+ + str(row, O_SUB_ACTIVITY));
|
|
|
}
|
|
|
- updateOtherApprovalInstance(finstId, otherRows, monthText, submitterUid, existingFormData);
|
|
|
- return "updated";
|
|
|
+ int existingCount = keys.size();
|
|
|
+ for (OtherRow row : otherRows) {
|
|
|
+ keys.add(submitterUid + "|" + row.dayText + "|" + row.activity);
|
|
|
+ }
|
|
|
+ return canAppendToApprovalShard(existingCount, keys.size());
|
|
|
}
|
|
|
|
|
|
private void createOtherApprovalInstance(List<OtherRow> otherRows, String deptManagerId,
|
|
|
String monthText, String submitterUid) {
|
|
|
+ McException.assertAccessException(
|
|
|
+ !hasSubtableCapacity(otherRows.size()),
|
|
|
+ "单个员工的其他工时审批明细超过宜搭子表 500 行限制");
|
|
|
double total = 0;
|
|
|
List<Map<String, Object>> sub = new ArrayList<>();
|
|
|
for (OtherRow or : otherRows) {
|
|
|
@@ -974,6 +1038,33 @@ public class ReSubmitApprovalService {
|
|
|
double addedHours = 0;
|
|
|
}
|
|
|
|
|
|
+ static boolean hasSubtableCapacity(int rowCount) {
|
|
|
+ return rowCount <= MAX_APPROVAL_SUBTABLE_ROWS;
|
|
|
+ }
|
|
|
+
|
|
|
+ static boolean canAppendToApprovalShard(int existingCount, int mergedCount) {
|
|
|
+ if (!hasSubtableCapacity(mergedCount)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ // 已有等效明细允许幂等命中;空分片允许承载一个员工的完整明细组。
|
|
|
+ return mergedCount == existingCount
|
|
|
+ || existingCount == 0
|
|
|
+ || mergedCount <= TARGET_APPROVAL_SUBTABLE_ROWS;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int countUniqueRows(List<Map> rows,
|
|
|
+ String uidField,
|
|
|
+ String dayField,
|
|
|
+ String keyField) {
|
|
|
+ Set<String> keys = new HashSet<>();
|
|
|
+ for (Map row : rows) {
|
|
|
+ keys.add(str(row, uidField) + "|"
|
|
|
+ + str(row, dayField) + "|"
|
|
|
+ + str(row, keyField));
|
|
|
+ }
|
|
|
+ return keys.size();
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* mergeApprovalSubRows 等价:唯一键 uid + dayText + projectCode/activity,已存在跳过
|
|
|
* 保留原行时用 rebuildRow 修正 EmployeeField 主键(_id 后缀 → 主键)
|