| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753 |
- package com.malk.service.workhours;
- import com.alibaba.fastjson.JSON;
- import com.malk.server.aliwork.YDConf;
- import com.malk.server.aliwork.YDParam;
- import com.malk.server.common.McException;
- import com.malk.server.dingtalk.DDR_New;
- import com.malk.server.workhours.MonthClosingCheckRequest;
- import com.malk.server.workhours.MonthClosingConf;
- import com.malk.server.workhours.MonthClosingStatusResult;
- import com.malk.server.workhours.MonthClosingValidationResult;
- import com.malk.server.workhours.WHConf;
- import com.malk.service.aliwork.YDClient;
- import org.apache.commons.lang3.StringUtils;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import java.time.Instant;
- import java.time.YearMonth;
- import java.time.ZoneId;
- import java.time.format.DateTimeFormatter;
- import java.time.format.DateTimeParseException;
- import java.time.format.ResolverStyle;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.HashMap;
- import java.util.LinkedHashSet;
- import java.util.List;
- import java.util.Locale;
- import java.util.Map;
- import java.util.Set;
- import java.util.concurrent.ConcurrentHashMap;
- import java.util.concurrent.ConcurrentMap;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.FutureTask;
- import java.util.function.LongSupplier;
- /**
- * 月度封账统一校验服务。
- *
- * <p>TimeCard、补填、变更与封账流程均调用本服务,避免各业务维护不同口径。</p>
- * <p>prd 2026-10-23:页面准入状态改为宜搭原生查询;本服务仍承担后端保存、
- * 提交、审批补发及封账校验,不能随旧页面查询接口作废而停用。</p>
- */
- @Service
- public class MonthClosingService {
- private static final int PAGE_SIZE = 100;
- private static final int MAX_PAGES = 1000;
- private static final String APPROVAL_MONTH_FIELD = "textField_mmd2lv0y";
- private static final String SUMMARY_MONTH_FIELD = "textField_mmbffvda";
- private static final String SUMMARY_STATUS_IN_APPROVAL = "审批中";
- private static final String REPAIR_SUMMARY_STATUS_PARTIAL = "部分成功";
- private static final String WRITE_BACK_SUCCESS = "全部成功";
- private static final String STATUS_CLOSED = "封账";
- private static final String STATUS_REOPENED = "启封";
- private static final long CLOSING_STATUS_CACHE_MILLIS = 5 * 60 * 1000L;
- private static final ZoneId BUSINESS_ZONE = ZoneId.of("Asia/Shanghai");
- private static final DateTimeFormatter MONTH_FORMATTER =
- DateTimeFormatter.ofPattern("uuuuMM").withResolverStyle(ResolverStyle.STRICT);
- private final ConcurrentMap<String, FutureTask<MonthClosingStatusResult>> closingStatusRequests =
- new ConcurrentHashMap<>();
- private final ConcurrentMap<String, CachedClosingStatus> closingStatusCache =
- new ConcurrentHashMap<>();
- private LongSupplier currentTimeMillis = System::currentTimeMillis;
- @Autowired
- private YDClient ydClient;
- @Autowired
- private WHConf whConf;
- @Autowired
- private MonthClosingConf closingConf;
- /**
- * 查询目标月份是否已封账。
- *
- * @param request 月份请求
- * @return 已审批封账与进行中封账数量
- */
- public MonthClosingStatusResult status(MonthClosingCheckRequest request) {
- String monthText = validateRequest(request);
- return queryClosingStatusCached(monthText);
- }
- /**
- * 一次读取全部封账流程,预热实际封账月份、当前月和上月的五分钟缓存。
- *
- * @deprecated prd 2026-10-23:旧页面查询预热功能作废,对应定时任务已关闭。
- * 仅保留历史方法兼容,不再自动调用;业务按需缓存及并发合并保持原样。
- * @return 本次写入缓存的月份数量
- */
- @Deprecated
- public int warmUpClosingStatusCache() {
- List<Map> processes = queryProcesses(closingConf.getFormUuid(), null, null, null);
- Set<String> months = new LinkedHashSet<>();
- for (Map item : processes) {
- String monthText = monthFromDateValue(
- extractFormData(item).get(closingConf.getMonthDateField()));
- if (StringUtils.isNotBlank(monthText)) {
- months.add(monthText);
- }
- }
- YearMonth currentMonth = YearMonth.now(BUSINESS_ZONE);
- months.add(currentMonth.format(MONTH_FORMATTER));
- months.add(currentMonth.minusMonths(1).format(MONTH_FORMATTER));
- long expiresAt = currentTimeMillis.getAsLong() + CLOSING_STATUS_CACHE_MILLIS;
- for (String monthText : months) {
- closingStatusCache.put(monthText, new CachedClosingStatus(
- summarizeClosingStatus(
- monthText, filterClosingMonth(processes, monthText)), expiresAt));
- }
- return months.size();
- }
- /**
- * 查询目标月份是否已封账;同月查询结果在五分钟内由后端复用。
- *
- * @param monthText 月份,格式 yyyyMM
- * @return true 表示最新已审批动作是封账
- */
- public boolean isClosed(String monthText) {
- MonthClosingCheckRequest request = MonthClosingCheckRequest.builder()
- .monthText(monthText)
- .build();
- return status(request).isClosed();
- }
- /**
- * 正常工时写入锁:封账流程中与已审批封账均禁止新增或追加审批。
- *
- * @param monthText 工时所属月份,格式 yyyyMM
- * @return true 表示当月已经锁定
- */
- public boolean isLocked(String monthText) {
- MonthClosingStatusResult result = status(MonthClosingCheckRequest.builder()
- .monthText(monthText).build());
- return result.isClosed() || result.getRunningClosingCount() > 0;
- }
- /**
- * 校验目标月份是否允许发起封账。
- *
- * @param request 月份请求
- * @return 封账许可、阻塞数量及提示
- */
- public MonthClosingValidationResult validate(MonthClosingCheckRequest request) {
- String monthText = validateRequest(request);
- String requestedStatus = validateClosingStatus(request.getClosingStatus());
- MonthClosingStatusResult closingStatus = queryClosingStatusCached(monthText);
- if (STATUS_REOPENED.equals(requestedStatus)) {
- return validateReopening(monthText, closingStatus);
- }
- return validateClosing(monthText, closingStatus);
- }
- private MonthClosingValidationResult validateClosing(
- String monthText,
- MonthClosingStatusResult closingStatus) {
- if (closingStatus.isClosed() || closingStatus.getRunningClosingCount() > 0) {
- return buildValidationResult(monthText, STATUS_CLOSED, closingStatus, 0, 0, 0, 0);
- }
- List<Map> projectRunning = normalApprovalProcesses(queryProcesses(
- whConf.getFormUuidApproval(), APPROVAL_MONTH_FIELD, monthText, "RUNNING"));
- List<Map> otherRunning = normalApprovalProcesses(queryProcesses(
- whConf.getFormUuidOtherApproval(), APPROVAL_MONTH_FIELD, monthText, "RUNNING"));
- List<Map> projectCompleted = normalApprovalProcesses(queryProcesses(
- whConf.getFormUuidApproval(), APPROVAL_MONTH_FIELD, monthText, "COMPLETED"));
- List<Map> otherCompleted = normalApprovalProcesses(queryProcesses(
- whConf.getFormUuidOtherApproval(), APPROVAL_MONTH_FIELD, monthText, "COMPLETED"));
- projectRunning.addAll(adjustmentApprovalProcesses(whConf.getFormUuidApproval(),
- whConf.getApprovalRepairSubmitDateField(), monthText, "RUNNING"));
- otherRunning.addAll(adjustmentApprovalProcesses(whConf.getFormUuidOtherApproval(),
- whConf.getOtherApprovalRepairSubmitDateField(), monthText, "RUNNING"));
- projectCompleted.addAll(adjustmentApprovalProcesses(whConf.getFormUuidApproval(),
- whConf.getApprovalRepairSubmitDateField(), monthText, "COMPLETED"));
- otherCompleted.addAll(adjustmentApprovalProcesses(whConf.getFormUuidOtherApproval(),
- whConf.getOtherApprovalRepairSubmitDateField(), monthText, "COMPLETED"));
- int writeBackPendingCount = countWriteBackPending(
- projectCompleted, whConf.getApprovalSyncStatusField())
- + countWriteBackPending(
- otherCompleted, whConf.getOtherApprovalSyncStatusField());
- int summaryPendingCount = querySummaryPendingCount(monthText)
- + queryRepairSummaryPendingCount(monthText);
- return buildValidationResult(
- monthText,
- STATUS_CLOSED,
- closingStatus,
- projectRunning.size(),
- otherRunning.size(),
- writeBackPendingCount,
- summaryPendingCount);
- }
- /** 正常填报沿用填报月份口径;历史空类型按正常填报处理。 */
- private List<Map> normalApprovalProcesses(List<Map> processes) {
- List<Map> result = new ArrayList<>();
- String fillTypeField = whConf.getApprovalFillTypeField();
- for (Map item : processes) {
- Map data = extractFormData(item);
- String fillType = text(data.get(fillTypeField));
- if (StringUtils.isBlank(fillTypeField)
- || (!"补填".equals(fillType) && !"变更".equals(fillType))) {
- result.add(item);
- }
- }
- return result;
- }
- /** 补填和变更审批按提交日期归属封账月份,目标月份只用于定位被调整的工时。 */
- private List<Map> adjustmentApprovalProcesses(String formUuid, String submitDateField,
- String monthText, String instanceStatus) {
- McException.assertAccessException(StringUtils.isAnyBlank(
- whConf.getApprovalFillTypeField(), submitDateField), "补填/变更审批查询配置不完整");
- List<Map> result = new ArrayList<>();
- for (Map item : queryProcesses(formUuid, null, null, instanceStatus)) {
- Map data = extractFormData(item);
- String fillType = text(data.get(whConf.getApprovalFillTypeField()));
- if (("补填".equals(fillType) || "变更".equals(fillType))
- && monthText.equals(monthFromDateValue(data.get(submitDateField)))) {
- result.add(item);
- }
- }
- return result;
- }
- private MonthClosingValidationResult validateReopening(
- String monthText,
- MonthClosingStatusResult closingStatus) {
- return buildValidationResult(monthText, STATUS_REOPENED, closingStatus, 0, 0, 0, 0);
- }
- private static MonthClosingValidationResult buildValidationResult(
- String monthText,
- String requestedStatus,
- MonthClosingStatusResult closingStatus,
- int projectRunningCount,
- int otherRunningCount,
- int writeBackPendingCount,
- int summaryPendingCount) {
- boolean requestedClosing = STATUS_CLOSED.equals(requestedStatus);
- boolean stateAllowed = requestedClosing ? !closingStatus.isClosed() : closingStatus.isClosed();
- boolean allowed = stateAllowed
- && closingStatus.getRunningClosingCount() == 0
- && projectRunningCount == 0
- && otherRunningCount == 0
- && writeBackPendingCount == 0
- && summaryPendingCount == 0;
- return MonthClosingValidationResult.builder()
- .monthText(monthText)
- .allowed(allowed)
- .requestedStatus(requestedStatus)
- .currentStatus(closingStatus.getCurrentStatus())
- .alreadyClosed(closingStatus.isClosed())
- .closingRunningCount(closingStatus.getRunningClosingCount())
- .projectRunningCount(projectRunningCount)
- .otherRunningCount(otherRunningCount)
- .writeBackPendingCount(writeBackPendingCount)
- .summaryPendingCount(summaryPendingCount)
- .message(buildMessage(
- monthText,
- requestedStatus,
- closingStatus,
- projectRunningCount,
- otherRunningCount,
- writeBackPendingCount,
- summaryPendingCount))
- .build();
- }
- private MonthClosingStatusResult queryClosingStatusCached(String monthText) {
- MonthClosingStatusResult cached = getCachedClosingStatus(monthText);
- if (cached != null) {
- return cached;
- }
- return queryClosingStatusCoalesced(monthText);
- }
- private MonthClosingStatusResult queryClosingStatusCoalesced(String monthText) {
- // fixme 缓存未命中时,同月并发请求共享一次宜搭查询。
- FutureTask<MonthClosingStatusResult> newRequest = new FutureTask<>(() -> {
- MonthClosingStatusResult cached = getCachedClosingStatus(monthText);
- if (cached != null) {
- return cached;
- }
- MonthClosingStatusResult result = queryClosingStatus(monthText);
- closingStatusCache.put(monthText, new CachedClosingStatus(
- result, currentTimeMillis.getAsLong() + CLOSING_STATUS_CACHE_MILLIS));
- return result;
- });
- FutureTask<MonthClosingStatusResult> activeRequest =
- closingStatusRequests.putIfAbsent(monthText, newRequest);
- boolean ownsRequest = activeRequest == null;
- if (ownsRequest) {
- activeRequest = newRequest;
- activeRequest.run();
- }
- try {
- return activeRequest.get();
- } catch (InterruptedException ex) {
- Thread.currentThread().interrupt();
- throw new IllegalStateException("月度封账状态查询被中断", ex);
- } catch (ExecutionException ex) {
- Throwable cause = ex.getCause();
- if (cause instanceof RuntimeException) {
- throw (RuntimeException) cause;
- }
- if (cause instanceof Error) {
- throw (Error) cause;
- }
- throw new IllegalStateException("月度封账状态查询失败", cause);
- } finally {
- if (ownsRequest) {
- closingStatusRequests.remove(monthText, newRequest);
- }
- }
- }
- private MonthClosingStatusResult getCachedClosingStatus(String monthText) {
- while (true) {
- CachedClosingStatus cached = closingStatusCache.get(monthText);
- if (cached == null) {
- return null;
- }
- if (currentTimeMillis.getAsLong() < cached.expiresAtMillis) {
- return cached.result;
- }
- if (closingStatusCache.remove(monthText, cached)) {
- return null;
- }
- }
- }
- private static final class CachedClosingStatus {
- private final MonthClosingStatusResult result;
- private final long expiresAtMillis;
- private CachedClosingStatus(MonthClosingStatusResult result, long expiresAtMillis) {
- this.result = result;
- this.expiresAtMillis = expiresAtMillis;
- }
- }
- private MonthClosingStatusResult queryClosingStatus(String monthText) {
- List<Long> monthRange = closingMonthRange(monthText);
- List<Map> processes = filterClosingMonth(
- queryProcesses(closingConf.getFormUuid(), closingConf.getMonthDateField(),
- monthRange, null), monthText);
- return summarizeClosingStatus(monthText, processes);
- }
- private MonthClosingStatusResult summarizeClosingStatus(String monthText, List<Map> processes) {
- int approvedClosingCount = 0;
- int approvedReopeningCount = 0;
- int runningClosingCount = 0;
- String currentStatus = "";
- Long latestTime = null;
- for (Map item : processes) {
- String instanceStatus = processInstanceStatus(item);
- if ("RUNNING".equals(instanceStatus)) {
- runningClosingCount++;
- continue;
- }
- if (!"COMPLETED".equals(instanceStatus)) {
- continue;
- }
- if (!isApproved(item)) {
- continue;
- }
- String itemStatus = text(extractFormData(item).get(closingConf.getStatusField()));
- if (STATUS_CLOSED.equals(itemStatus)) {
- approvedClosingCount++;
- } else if (STATUS_REOPENED.equals(itemStatus)) {
- approvedReopeningCount++;
- } else {
- continue;
- }
- Long itemTime = processEventTime(item);
- if (StringUtils.isBlank(currentStatus)
- || (latestTime == null && itemTime != null)
- || (latestTime != null && itemTime != null && itemTime > latestTime)) {
- currentStatus = itemStatus;
- latestTime = itemTime;
- }
- }
- return MonthClosingStatusResult.builder()
- .monthText(monthText)
- .closed(STATUS_CLOSED.equals(currentStatus))
- .currentStatus(currentStatus)
- .approvedClosingCount(approvedClosingCount)
- .approvedReopeningCount(approvedReopeningCount)
- .runningClosingCount(runningClosingCount)
- .build();
- }
- private List<Map> filterClosingMonth(List<Map> processes, String monthText) {
- List<Map> result = new ArrayList<>();
- for (Map item : processes) {
- Object monthValue = extractFormData(item).get(closingConf.getMonthDateField());
- if (monthText.equals(monthFromDateValue(monthValue))) {
- result.add(item);
- }
- }
- return result;
- }
- private int countWriteBackPending(List<Map> completed, String syncStatusField) {
- int pending = 0;
- for (Map item : completed) {
- if (!isApproved(item)) {
- continue;
- }
- Map formData = extractFormData(item);
- String syncStatus = text(formData.get(syncStatusField));
- if (!WRITE_BACK_SUCCESS.equals(syncStatus)) {
- pending++;
- }
- }
- return pending;
- }
- private int querySummaryPendingCount(String monthText) {
- Map<String, Object> search = new HashMap<>();
- search.put(SUMMARY_MONTH_FIELD, monthText);
- search.put(whConf.getSummaryApprovalStatusField(), SUMMARY_STATUS_IN_APPROVAL);
- List<Map> rows = queryForms(whConf.getFormUuidWorkHoursSummary(), search);
- int count = 0;
- for (Map item : rows) {
- Map formData = extractFormData(item);
- if (monthText.equals(text(formData.get(SUMMARY_MONTH_FIELD)))
- && SUMMARY_STATUS_IN_APPROVAL.equals(
- text(formData.get(whConf.getSummaryApprovalStatusField())))) {
- count++;
- }
- }
- return count;
- }
- private int queryRepairSummaryPendingCount(String monthText) {
- McException.assertAccessException(StringUtils.isAnyBlank(
- whConf.getFormUuidRepairWorkHoursSummary(),
- whConf.getRepairSummarySubmitDateField(),
- whConf.getRepairSummarySyncStatusField()), "补填汇总查询配置不完整");
- int count = 0;
- // fixme 补填提交日期是复制表新增字段;完整分页后本地匹配月份,避免查询索引漏掉待回写记录。
- for (Map item : queryForms(whConf.getFormUuidRepairWorkHoursSummary(), new HashMap<>())) {
- Map data = extractFormData(item);
- String status = text(data.get(whConf.getRepairSummarySyncStatusField()));
- if (monthText.equals(monthFromDateValue(data.get(whConf.getRepairSummarySubmitDateField())))
- && (SUMMARY_STATUS_IN_APPROVAL.equals(status)
- || REPAIR_SUMMARY_STATUS_PARTIAL.equals(status))) {
- count++;
- }
- }
- return count;
- }
- private List<Map> queryProcesses(
- String formUuid,
- String monthField,
- Object monthValue,
- String instanceStatus) {
- McException.assertAccessException(
- StringUtils.isBlank(formUuid),
- "月度封账流程查询配置不完整");
- Map<String, Object> search = new HashMap<>();
- if (StringUtils.isNotBlank(monthField) && monthValue != null) {
- search.put(monthField, monthValue);
- }
- List<Map> result = new ArrayList<>();
- int page = 1;
- while (page <= MAX_PAGES) {
- DDR_New response = ydClient.queryData(YDParam.builder()
- .appType(whConf.getYidaAppType())
- .systemToken(whConf.getYidaSystemToken())
- .formUuid(formUuid)
- .searchFieldJson(search.isEmpty() ? null : JSON.toJSONString(search))
- .instanceStatus(instanceStatus)
- .pageNumber(page)
- .pageSize(PAGE_SIZE)
- .build(), YDConf.FORM_QUERY.retrieve_search_process);
- List<Map> data = responseData(response);
- result.addAll(data);
- if (!hasNextPage(page, response.getTotalCount(), data.size(), result.size())) {
- break;
- }
- page++;
- }
- McException.assertAccessException(page > MAX_PAGES, "月度封账流程查询超过分页上限");
- return result;
- }
- private List<Map> queryForms(String formUuid, Map<String, Object> search) {
- McException.assertAccessException(StringUtils.isBlank(formUuid), "工时汇总表配置为空");
- List<Map> result = new ArrayList<>();
- int page = 1;
- while (page <= MAX_PAGES) {
- DDR_New response = ydClient.queryData(YDParam.builder()
- .appType(whConf.getYidaAppType())
- .systemToken(whConf.getYidaSystemToken())
- .formUuid(formUuid)
- .searchFieldJson(JSON.toJSONString(search))
- .currentPage(page)
- .pageSize(PAGE_SIZE)
- .build(), YDConf.FORM_QUERY.retrieve_search_form);
- List<Map> data = responseData(response);
- result.addAll(data);
- if (!hasNextPage(page, response.getTotalCount(), data.size(), result.size())) {
- break;
- }
- page++;
- }
- McException.assertAccessException(page > MAX_PAGES, "工时汇总查询超过分页上限");
- return result;
- }
- private static boolean hasNextPage(
- int page,
- long totalCount,
- int returnedSize,
- int collectedSize) {
- if (returnedSize < PAGE_SIZE) {
- return false;
- }
- return totalCount <= 0 || collectedSize < totalCount || (long) page * PAGE_SIZE < totalCount;
- }
- @SuppressWarnings("unchecked")
- private static List<Map> responseData(DDR_New response) {
- McException.assertAccessException(response == null, "宜搭查询未返回结果");
- Object data = response.getData();
- if (data == null) {
- return new ArrayList<>();
- }
- McException.assertAccessException(!(data instanceof List), "宜搭查询返回数据格式异常");
- return (List<Map>) data;
- }
- @SuppressWarnings("unchecked")
- static Map extractFormData(Map item) {
- if (item == null) {
- return new HashMap();
- }
- Object formData = item.get("formData");
- if (formData instanceof Map) {
- return (Map) formData;
- }
- Object data = item.get("data");
- if (!(data instanceof Map)) {
- return new HashMap();
- }
- Map dataMap = (Map) data;
- Object nested = dataMap.get("formData");
- return nested instanceof Map ? (Map) nested : dataMap;
- }
- static String validateMonthText(String monthText) {
- String value = StringUtils.trimToEmpty(monthText);
- McException.assertParamException(!value.matches("\\d{6}"), "月份必须为yyyyMM格式");
- try {
- YearMonth.parse(value, MONTH_FORMATTER);
- } catch (DateTimeParseException ex) {
- throw McException.builder()
- .code("validated_param")
- .message("月份必须为有效的yyyyMM格式")
- .build();
- }
- return value;
- }
- static List<Long> closingMonthRange(String monthText) {
- YearMonth month = YearMonth.parse(validateMonthText(monthText), MONTH_FORMATTER);
- long start = month.atDay(1).atStartOfDay(BUSINESS_ZONE).toInstant().toEpochMilli();
- long end = month.plusMonths(1).atDay(1).atStartOfDay(BUSINESS_ZONE)
- .toInstant().toEpochMilli() - 1L;
- return Arrays.asList(start, end);
- }
- static String monthFromDateValue(Object value) {
- if (value == null) {
- return "";
- }
- if (value instanceof Number) {
- return monthFromEpoch(((Number) value).longValue());
- }
- String raw = text(value);
- if (raw.matches("\\d{6}")) {
- try {
- return validateMonthText(raw);
- } catch (McException ignored) {
- return "";
- }
- }
- if (raw.matches("\\d{8}")) {
- return normalizeYearMonth(raw.substring(0, 4), raw.substring(4, 6));
- }
- if (raw.matches("\\d{10}|\\d{13}")) {
- try {
- return monthFromEpoch(Long.parseLong(raw));
- } catch (NumberFormatException ignored) {
- return "";
- }
- }
- try {
- return YearMonth.from(Instant.parse(raw).atZone(BUSINESS_ZONE)).format(MONTH_FORMATTER);
- } catch (DateTimeParseException ignored) {
- // 普通日期字符串继续按 yyyy-MM 前缀处理。
- }
- if (raw.matches("^\\d{4}[-/]\\d{2}.*")) {
- return normalizeYearMonth(raw.substring(0, 4), raw.substring(5, 7));
- }
- return "";
- }
- private static String monthFromEpoch(long rawTimestamp) {
- String digits = String.valueOf(Math.abs(rawTimestamp));
- if (digits.length() == 8) {
- return normalizeYearMonth(digits.substring(0, 4), digits.substring(4, 6));
- }
- long milliseconds = digits.length() <= 10 ? rawTimestamp * 1000L : rawTimestamp;
- try {
- return YearMonth.from(Instant.ofEpochMilli(milliseconds).atZone(BUSINESS_ZONE))
- .format(MONTH_FORMATTER);
- } catch (RuntimeException ignored) {
- return "";
- }
- }
- private static String normalizeYearMonth(String year, String month) {
- try {
- return YearMonth.of(Integer.parseInt(year), Integer.parseInt(month))
- .format(MONTH_FORMATTER);
- } catch (RuntimeException ignored) {
- return "";
- }
- }
- private static Long processEventTime(Map item) {
- if (item == null) {
- return null;
- }
- String[] fields = {
- "modifiedTimeGMT", "gmtModified", "finishTimeGMT", "finishTime",
- "modifiedTime", "createTimeGMT", "createTime"
- };
- for (String field : fields) {
- Long parsed = parseTimestamp(item.get(field));
- if (parsed != null) {
- return parsed;
- }
- }
- return null;
- }
- private static Long parseTimestamp(Object value) {
- if (value instanceof Number) {
- long number = ((Number) value).longValue();
- return String.valueOf(Math.abs(number)).length() <= 10 ? number * 1000L : number;
- }
- String raw = text(value);
- if (raw.matches("\\d{10}|\\d{13}")) {
- try {
- long number = Long.parseLong(raw);
- return raw.length() == 10 ? number * 1000L : number;
- } catch (NumberFormatException ignored) {
- return null;
- }
- }
- try {
- return Instant.parse(raw).toEpochMilli();
- } catch (DateTimeParseException ignored) {
- return null;
- }
- }
- private static String validateClosingStatus(String closingStatus) {
- String value = text(closingStatus);
- McException.assertParamException(
- !STATUS_CLOSED.equals(value) && !STATUS_REOPENED.equals(value),
- "封账状态必须为封账或启封");
- return value;
- }
- private String validateRequest(MonthClosingCheckRequest request) {
- McException.assertParamException(request == null, "请求不能为空");
- McException.assertAccessException(
- StringUtils.isAnyBlank(
- whConf.getYidaAppType(),
- whConf.getYidaSystemToken(),
- closingConf.getFormUuid(),
- closingConf.getMonthDateField(),
- closingConf.getStatusField()),
- "月度封账配置不完整");
- return validateMonthText(request.getMonthText());
- }
- private static boolean isApproved(Map item) {
- return "agree".equals(text(item.get("approvedResult")).toLowerCase(Locale.ROOT));
- }
- private static String processInstanceStatus(Map item) {
- String status = text(item == null ? null : item.get("instanceStatus"));
- if (StringUtils.isBlank(status)) {
- status = text(item == null ? null : item.get("processInstanceStatus"));
- }
- return status.toUpperCase(Locale.ROOT);
- }
- private static String buildMessage(
- String monthText,
- String requestedStatus,
- MonthClosingStatusResult closingStatus,
- int projectRunningCount,
- int otherRunningCount,
- int writeBackPendingCount,
- int summaryPendingCount) {
- if (closingStatus.getRunningClosingCount() > 0) {
- return monthText + " 已有封账或启封流程审批中,不可重复提交";
- }
- if (STATUS_REOPENED.equals(requestedStatus)) {
- return closingStatus.isClosed()
- ? monthText + " 当前已封账,可以提交启封"
- : monthText + " 当前未封账,不可重复启封";
- }
- if (closingStatus.isClosed()) {
- return monthText + " 已完成封账,不可重复提交";
- }
- if (projectRunningCount > 0 || otherRunningCount > 0) {
- return monthText + " 仍有工时审批单处理中:项目工时 "
- + projectRunningCount + " 张,其他工时 " + otherRunningCount + " 张";
- }
- if (writeBackPendingCount > 0) {
- return monthText + " 有 " + writeBackPendingCount + " 张审批单尚未成功回写工时汇总";
- }
- if (summaryPendingCount > 0) {
- return monthText + " 仍有 " + summaryPendingCount + " 条工时汇总尚未完成审批回写";
- }
- return monthText + " 工时审批已完成,可以提交封账";
- }
- private static String text(Object value) {
- return value == null ? "" : String.valueOf(value).trim();
- }
- }
|