|
|
@@ -74,6 +74,7 @@ public class BizDataService {
|
|
|
// ---------- 区划 ----------
|
|
|
private final Object regionListLock = new Object();
|
|
|
private volatile List<RegionVO> regionListCache;
|
|
|
+ private volatile Map<String, Map<String, Object>> regionRowByKey;
|
|
|
private volatile Map<String, String> regionDisplayIndex;
|
|
|
private volatile long regionListCacheExpireAt;
|
|
|
private static final long REGION_LIST_CACHE_TTL_MS = 300_000L;
|
|
|
@@ -120,6 +121,7 @@ public class BizDataService {
|
|
|
props.getDms().getColumns().getRegion().getModelId(),
|
|
|
200);
|
|
|
List<RegionVO> all = new ArrayList<RegionVO>();
|
|
|
+ Map<String, Map<String, Object>> idx = new HashMap<String, Map<String, Object>>();
|
|
|
java.util.Set<String> seen = new java.util.HashSet<String>();
|
|
|
if (rows != null) {
|
|
|
for (Map<String, Object> row : rows) {
|
|
|
@@ -129,9 +131,17 @@ public class BizDataService {
|
|
|
continue;
|
|
|
}
|
|
|
all.add(vo);
|
|
|
+ if (StringUtils.hasText(key)) {
|
|
|
+ idx.put(key, row);
|
|
|
+ }
|
|
|
+ String dmsId = DmsRowUtils.str(row, "id");
|
|
|
+ if (StringUtils.hasText(dmsId)) {
|
|
|
+ idx.put(dmsId, row);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
regionListCache = all;
|
|
|
+ regionRowByKey = idx;
|
|
|
regionDisplayIndex = null;
|
|
|
regionListCacheExpireAt = System.currentTimeMillis() + REGION_LIST_CACHE_TTL_MS;
|
|
|
}
|
|
|
@@ -267,7 +277,7 @@ public class BizDataService {
|
|
|
private volatile List<PersonnelVO> personnelListCache;
|
|
|
private volatile Map<String, Map<String, Object>> personnelRowByKey;
|
|
|
private volatile long personnelListCacheExpireAt;
|
|
|
- private static final long PERSONNEL_LIST_CACHE_TTL_MS = 300_000L;
|
|
|
+ private static final long PERSONNEL_LIST_CACHE_TTL_MS = 900_000L;
|
|
|
private final AtomicBoolean personnelRefreshing = new AtomicBoolean(false);
|
|
|
|
|
|
/**
|
|
|
@@ -321,6 +331,9 @@ public class BizDataService {
|
|
|
}
|
|
|
Map<String, Object> merged = new LinkedHashMap<String, Object>(old);
|
|
|
merged.putAll(fields);
|
|
|
+ if (!fields.containsKey("update_time")) {
|
|
|
+ merged.put("update_time", Long.valueOf(System.currentTimeMillis()));
|
|
|
+ }
|
|
|
upsertPersonnelCacheLocked(merged);
|
|
|
}
|
|
|
}
|
|
|
@@ -383,12 +396,6 @@ public class BizDataService {
|
|
|
Map<String, Map<String, Object>> idx = personnelRowByKey == null
|
|
|
? new HashMap<String, Map<String, Object>>()
|
|
|
: new HashMap<String, Map<String, Object>>(personnelRowByKey);
|
|
|
- if (StringUtils.hasText(dmsId)) {
|
|
|
- idx.put(dmsId, stored);
|
|
|
- }
|
|
|
- if (StringUtils.hasText(biz)) {
|
|
|
- idx.put(biz, stored);
|
|
|
- }
|
|
|
List<PersonnelVO> list = new ArrayList<PersonnelVO>(personnelListCache);
|
|
|
int found = -1;
|
|
|
for (int i = 0; i < list.size(); i++) {
|
|
|
@@ -401,10 +408,33 @@ public class BizDataService {
|
|
|
}
|
|
|
}
|
|
|
if (found >= 0) {
|
|
|
+ PersonnelVO oldVo = list.get(found);
|
|
|
+ Map<String, Object> prevStored = null;
|
|
|
+ if (oldVo != null && StringUtils.hasText(oldVo.getId())) {
|
|
|
+ prevStored = idx.get(oldVo.getId());
|
|
|
+ }
|
|
|
+ if (prevStored == null && StringUtils.hasText(dmsId)) {
|
|
|
+ prevStored = idx.get(dmsId);
|
|
|
+ }
|
|
|
+ if (prevStored != null) {
|
|
|
+ java.util.Iterator<Map.Entry<String, Map<String, Object>>> it =
|
|
|
+ idx.entrySet().iterator();
|
|
|
+ while (it.hasNext()) {
|
|
|
+ if (it.next().getValue() == prevStored) {
|
|
|
+ it.remove();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
list.set(found, vo);
|
|
|
} else {
|
|
|
list.add(vo);
|
|
|
}
|
|
|
+ if (StringUtils.hasText(dmsId)) {
|
|
|
+ idx.put(dmsId, stored);
|
|
|
+ }
|
|
|
+ if (StringUtils.hasText(biz)) {
|
|
|
+ idx.put(biz, stored);
|
|
|
+ }
|
|
|
personnelListCache = list;
|
|
|
personnelRowByKey = idx;
|
|
|
}
|
|
|
@@ -435,6 +465,11 @@ public class BizDataService {
|
|
|
} catch (Exception e) {
|
|
|
System.out.println("[payment-cache] warm failed: " + e.getMessage());
|
|
|
}
|
|
|
+ try {
|
|
|
+ loadAlertListCached();
|
|
|
+ } catch (Exception e) {
|
|
|
+ System.out.println("[alert-cache] warm failed: " + e.getMessage());
|
|
|
+ }
|
|
|
}
|
|
|
}, "warm-dms-caches");
|
|
|
t.setDaemon(true);
|
|
|
@@ -530,6 +565,7 @@ public class BizDataService {
|
|
|
List<PersonnelVO> filtered = all.stream()
|
|
|
.filter(p -> matchPersonnel(p, req, regionAlias))
|
|
|
.collect(Collectors.toList());
|
|
|
+ sortPersonnelForList(filtered);
|
|
|
PageResult<PersonnelVO> page = pageOf(filtered, pageNum, pageSize);
|
|
|
// 只回填当前页月标,避免每次列表再扫金额标准全表
|
|
|
boolean lite = Boolean.TRUE.equals(req.getLite()) || pageSize >= 100;
|
|
|
@@ -1238,6 +1274,7 @@ public class BizDataService {
|
|
|
|
|
|
private final Object batchByIdCacheLock = new Object();
|
|
|
private volatile Map<String, BatchVO> batchByIdCache;
|
|
|
+ private volatile Map<String, Map<String, Object>> batchRowByKey;
|
|
|
private volatile long batchByIdCacheExpireAt;
|
|
|
private static final long BATCH_BY_ID_CACHE_TTL_MS = 60_000L;
|
|
|
private final AtomicBoolean batchRefreshing = new AtomicBoolean(false);
|
|
|
@@ -1280,6 +1317,7 @@ public class BizDataService {
|
|
|
|
|
|
private void reloadBatchCacheLocked() {
|
|
|
Map<String, BatchVO> map = new HashMap<String, BatchVO>();
|
|
|
+ Map<String, Map<String, Object>> rawIdx = new HashMap<String, Map<String, Object>>();
|
|
|
if (dmsOn()) {
|
|
|
for (Map<String, Object> row : dmsClient.selectContentList(
|
|
|
props.getDms().getColumns().getBatch().getColumnId(),
|
|
|
@@ -1296,9 +1334,17 @@ public class BizDataService {
|
|
|
if (StringUtils.hasText(bizNo) && !bizNo.equals(vo.getId())) {
|
|
|
putPreferBatch(map, bizNo, vo);
|
|
|
}
|
|
|
+ String dmsId = DmsRowUtils.str(row, "id");
|
|
|
+ if (StringUtils.hasText(dmsId)) {
|
|
|
+ rawIdx.put(dmsId, row);
|
|
|
+ }
|
|
|
+ if (StringUtils.hasText(bizNo)) {
|
|
|
+ rawIdx.put(bizNo, row);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
batchByIdCache = map;
|
|
|
+ batchRowByKey = rawIdx;
|
|
|
batchByIdCacheExpireAt = System.currentTimeMillis() + BATCH_BY_ID_CACHE_TTL_MS;
|
|
|
}
|
|
|
|
|
|
@@ -1503,11 +1549,10 @@ public class BizDataService {
|
|
|
if (!StringUtils.hasText(id) || !dmsOn()) {
|
|
|
return null;
|
|
|
}
|
|
|
- for (Map<String, Object> row : dmsClient.selectContentList(
|
|
|
- props.getDms().getColumns().getPaymentDetail().getColumnId(),
|
|
|
- props.getDms().getColumns().getPaymentDetail().getModelId(), 500)) {
|
|
|
- if (id.equals(DmsRowUtils.str(row, "id"))) {
|
|
|
- return toPayment(row);
|
|
|
+ String key = id.trim();
|
|
|
+ for (PaymentDetailVO d : loadPaymentListCached()) {
|
|
|
+ if (d != null && key.equals(d.getId())) {
|
|
|
+ return d;
|
|
|
}
|
|
|
}
|
|
|
return null;
|
|
|
@@ -1660,8 +1705,21 @@ public class BizDataService {
|
|
|
&& !req.getPersonnelId().equals(d.getPersonnelId())) {
|
|
|
continue;
|
|
|
}
|
|
|
+ if (Boolean.TRUE.equals(req.getOnlyFuneral())) {
|
|
|
+ if (d.getFuneralAmount() == null
|
|
|
+ || d.getFuneralAmount().compareTo(java.math.BigDecimal.ZERO) <= 0) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (Boolean.TRUE.equals(req.getOnlySupplement())) {
|
|
|
+ if (d.getSupplementAmount() == null
|
|
|
+ || d.getSupplementAmount().compareTo(java.math.BigDecimal.ZERO) <= 0) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
filtered.add(d);
|
|
|
}
|
|
|
+ sortPaymentDetailsForList(filtered, req.getSortBy(), req.getSortOrder());
|
|
|
// 先分页再 enrichment,避免对全量结果逐条 getBatch 扫 DMS
|
|
|
PageResult<PaymentDetailVO> page = pageOf(filtered, pageNum, pageSize);
|
|
|
boolean lightEnrich = StringUtils.hasText(req.getPersonnelId());
|
|
|
@@ -1894,6 +1952,7 @@ public class BizDataService {
|
|
|
// ---------- 金额标准 ----------
|
|
|
private final Object standardListLock = new Object();
|
|
|
private volatile List<AmountStandardVO> standardListCache;
|
|
|
+ private volatile Map<String, Map<String, Object>> standardRowByKey;
|
|
|
private volatile long standardListCacheExpireAt;
|
|
|
private static final long STANDARD_LIST_CACHE_TTL_MS = 60_000L;
|
|
|
private final AtomicBoolean standardRefreshing = new AtomicBoolean(false);
|
|
|
@@ -1938,12 +1997,18 @@ public class BizDataService {
|
|
|
props.getDms().getColumns().getAmountStandard().getColumnId(),
|
|
|
props.getDms().getColumns().getAmountStandard().getModelId(), 200);
|
|
|
List<AmountStandardVO> all = new ArrayList<AmountStandardVO>();
|
|
|
+ Map<String, Map<String, Object>> idx = new HashMap<String, Map<String, Object>>();
|
|
|
if (rows != null) {
|
|
|
for (Map<String, Object> row : rows) {
|
|
|
all.add(toStandard(row));
|
|
|
+ String id = DmsRowUtils.str(row, "id");
|
|
|
+ if (StringUtils.hasText(id)) {
|
|
|
+ idx.put(id, row);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
standardListCache = all;
|
|
|
+ standardRowByKey = idx;
|
|
|
standardListCacheExpireAt = System.currentTimeMillis() + STANDARD_LIST_CACHE_TTL_MS;
|
|
|
}
|
|
|
|
|
|
@@ -2019,7 +2084,7 @@ public class BizDataService {
|
|
|
private final Object alertListLock = new Object();
|
|
|
private volatile List<AlertVO> alertListCache;
|
|
|
private volatile long alertListCacheExpireAt;
|
|
|
- private static final long ALERT_LIST_CACHE_TTL_MS = 30_000L;
|
|
|
+ private static final long ALERT_LIST_CACHE_TTL_MS = 300_000L;
|
|
|
private final AtomicBoolean alertRefreshing = new AtomicBoolean(false);
|
|
|
|
|
|
public void invalidateAlertCache() {
|
|
|
@@ -2034,6 +2099,13 @@ public class BizDataService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /** 运维清理后立即同步重载,避免仍返回删前缓存。 */
|
|
|
+ public void reloadAlertCacheSync() {
|
|
|
+ synchronized (alertListLock) {
|
|
|
+ reloadAlertCacheLocked();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private List<AlertVO> loadAlertListCached() {
|
|
|
List<AlertVO> hit = alertListCache;
|
|
|
if (hit != null && alertListCacheExpireAt > System.currentTimeMillis()) {
|
|
|
@@ -2058,12 +2130,112 @@ public class BizDataService {
|
|
|
}
|
|
|
|
|
|
private void reloadAlertCacheLocked() {
|
|
|
- List<AlertVO> all = dmsClient.selectContentList(
|
|
|
+ long t0 = System.currentTimeMillis();
|
|
|
+ List<Map<String, Object>> rows = dmsClient.selectContentListParallel(
|
|
|
props.getDms().getColumns().getAlert().getColumnId(),
|
|
|
- props.getDms().getColumns().getAlert().getModelId(), 200)
|
|
|
- .stream().map(this::toAlert).collect(Collectors.toList());
|
|
|
+ props.getDms().getColumns().getAlert().getModelId(), 100, 4);
|
|
|
+ List<AlertVO> all = new ArrayList<AlertVO>(rows == null ? 0 : rows.size());
|
|
|
+ if (rows != null) {
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
+ if (row == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ AlertVO vo = toAlert(row);
|
|
|
+ if (vo != null) {
|
|
|
+ all.add(vo);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
alertListCache = all;
|
|
|
alertListCacheExpireAt = System.currentTimeMillis() + ALERT_LIST_CACHE_TTL_MS;
|
|
|
+ System.out.println("[alert-cache] loaded " + all.size() + " in "
|
|
|
+ + (System.currentTimeMillis() - t0) + "ms");
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 从 DMS 直拉全量告警(不走短缓存),供运维去重/清理。 */
|
|
|
+ public List<AlertVO> listAllAlertsFromDms(String townId, String villageId) {
|
|
|
+ if (!dmsOn()) {
|
|
|
+ return new ArrayList<AlertVO>();
|
|
|
+ }
|
|
|
+ List<Map<String, Object>> rows = dmsClient.selectContentListParallel(
|
|
|
+ props.getDms().getColumns().getAlert().getColumnId(),
|
|
|
+ props.getDms().getColumns().getAlert().getModelId(), 100, 4);
|
|
|
+ List<AlertVO> out = new ArrayList<AlertVO>();
|
|
|
+ if (rows == null) {
|
|
|
+ return out;
|
|
|
+ }
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
+ if (row == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ AlertVO vo = toAlert(row);
|
|
|
+ if (vo == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (StringUtils.hasText(townId) && !townId.equals(vo.getTownId())) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (StringUtils.hasText(villageId) && !villageId.equals(vo.getVillageId())) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ out.add(vo);
|
|
|
+ }
|
|
|
+ return out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 工作台/看板:仅取待处置告警,按产生时间倒序截断,避免一次序列化上千条。 */
|
|
|
+ public List<AlertVO> listPendingAlertsRecent(String townId, String villageId, int limit) {
|
|
|
+ int cap = limit <= 0 ? 30 : Math.min(limit, 200);
|
|
|
+ AlertQueryRequest q = new AlertQueryRequest();
|
|
|
+ q.setHandleStatus(AlertHandleStatusEnum.PENDING);
|
|
|
+ q.setTownId(townId);
|
|
|
+ q.setVillageId(villageId);
|
|
|
+ q.setPageNum(1);
|
|
|
+ q.setPageSize(cap * 3);
|
|
|
+ List<AlertVO> hits = pageAlerts(q).getList();
|
|
|
+ if (hits == null || hits.isEmpty()) {
|
|
|
+ return new ArrayList<AlertVO>();
|
|
|
+ }
|
|
|
+ if (hits.size() <= cap) {
|
|
|
+ return hits;
|
|
|
+ }
|
|
|
+ return new ArrayList<AlertVO>(hits.subList(0, cap));
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 列表默认排序:等级高优先,同等级按产生时间新→旧。 */
|
|
|
+ private static void sortAlertsByPriority(List<AlertVO> list) {
|
|
|
+ if (list == null || list.size() < 2) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ list.sort(new Comparator<AlertVO>() {
|
|
|
+ @Override
|
|
|
+ public int compare(AlertVO a, AlertVO b) {
|
|
|
+ int ra = alertLevelRank(a);
|
|
|
+ int rb = alertLevelRank(b);
|
|
|
+ if (ra != rb) {
|
|
|
+ return Integer.compare(ra, rb);
|
|
|
+ }
|
|
|
+ long ta = a == null || a.getCreateTime() == null ? 0L : a.getCreateTime();
|
|
|
+ long tb = b == null || b.getCreateTime() == null ? 0L : b.getCreateTime();
|
|
|
+ return Long.compare(tb, ta);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private static int alertLevelRank(AlertVO a) {
|
|
|
+ if (a == null || a.getLevel() == null) {
|
|
|
+ return 99;
|
|
|
+ }
|
|
|
+ switch (a.getLevel()) {
|
|
|
+ case HIGH:
|
|
|
+ return 0;
|
|
|
+ case MEDIUM:
|
|
|
+ return 1;
|
|
|
+ case LOW:
|
|
|
+ return 2;
|
|
|
+ default:
|
|
|
+ return 99;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
public PageResult<AlertVO> pageAlerts(AlertQueryRequest req) {
|
|
|
@@ -2098,6 +2270,7 @@ public class BizDataService {
|
|
|
}
|
|
|
return true;
|
|
|
}).collect(Collectors.toList());
|
|
|
+ sortAlertsByPriority(filtered);
|
|
|
return pageOf(filtered, pageNum, pageSize);
|
|
|
}
|
|
|
|
|
|
@@ -2117,12 +2290,51 @@ public class BizDataService {
|
|
|
return row == null ? null : toAlert(row);
|
|
|
}
|
|
|
|
|
|
+ /** 从告警缓存移除已删除/已关闭条目(运维清理或处置后调用)。 */
|
|
|
+ public void removeAlertsFromCache(java.util.Collection<String> ids) {
|
|
|
+ if (ids == null || ids.isEmpty()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ java.util.HashSet<String> set = new java.util.HashSet<String>();
|
|
|
+ for (String id : ids) {
|
|
|
+ if (StringUtils.hasText(id)) {
|
|
|
+ set.add(id.trim());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (set.isEmpty()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ synchronized (alertListLock) {
|
|
|
+ removeAlertsFromCacheLocked(set);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void removeAlertsFromCacheLocked(java.util.Set<String> ids) {
|
|
|
+ List<AlertVO> old = alertListCache;
|
|
|
+ if (old == null || old.isEmpty()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ List<AlertVO> list = new ArrayList<AlertVO>(old.size());
|
|
|
+ for (AlertVO a : old) {
|
|
|
+ if (a == null || !ids.contains(a.getId())) {
|
|
|
+ list.add(a);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (list.size() != old.size()) {
|
|
|
+ alertListCache = list;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/** 处置后立刻替换缓存中的该条,避免 Stale-While-Revalidate 仍返回「待处理」。 */
|
|
|
public void replaceAlertInCache(AlertVO vo) {
|
|
|
if (vo == null || !StringUtils.hasText(vo.getId())) {
|
|
|
return;
|
|
|
}
|
|
|
synchronized (alertListLock) {
|
|
|
+ if (vo.getHandleStatus() == AlertHandleStatusEnum.CLOSED) {
|
|
|
+ removeAlertsFromCacheLocked(java.util.Collections.singleton(vo.getId()));
|
|
|
+ return;
|
|
|
+ }
|
|
|
List<AlertVO> old = alertListCache;
|
|
|
List<AlertVO> list = old == null
|
|
|
? new ArrayList<AlertVO>()
|
|
|
@@ -2310,6 +2522,7 @@ public class BizDataService {
|
|
|
/** 流程表全量短缓存:timeline / overview 反复扫 DMS 很慢 */
|
|
|
private final Object processListLock = new Object();
|
|
|
private volatile List<ProcessLogVO> processCache;
|
|
|
+ private volatile Map<String, Map<String, Object>> processRowByKey;
|
|
|
private volatile long processCacheExpireAt;
|
|
|
private static final long PROCESS_CACHE_TTL_MS = 45_000L;
|
|
|
private final AtomicBoolean processRefreshing = new AtomicBoolean(false);
|
|
|
@@ -2351,10 +2564,23 @@ public class BizDataService {
|
|
|
}
|
|
|
|
|
|
private void reloadProcessCacheLocked() {
|
|
|
- List<ProcessLogVO> all = dmsClient.selectContentList(
|
|
|
- props.getDms().getColumns().getProcessLog().getColumnId(),
|
|
|
- props.getDms().getColumns().getProcessLog().getModelId(), 100)
|
|
|
- .stream().map(this::toProcess).collect(Collectors.toList());
|
|
|
+ List<Map<String, Object>> rows = dmsClient.selectContentList(
|
|
|
+ props.getDms().getColumns().getProcessLog().getColumnId(),
|
|
|
+ props.getDms().getColumns().getProcessLog().getModelId(), 100);
|
|
|
+ List<ProcessLogVO> all = new ArrayList<ProcessLogVO>();
|
|
|
+ Map<String, Map<String, Object>> idx = new HashMap<String, Map<String, Object>>();
|
|
|
+ if (rows != null) {
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
+ if (row == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ all.add(toProcess(row));
|
|
|
+ String id = DmsRowUtils.str(row, "id");
|
|
|
+ if (StringUtils.hasText(id)) {
|
|
|
+ idx.put(id, row);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
Collections.sort(all, new Comparator<ProcessLogVO>() {
|
|
|
@Override
|
|
|
public int compare(ProcessLogVO a, ProcessLogVO b) {
|
|
|
@@ -2364,6 +2590,7 @@ public class BizDataService {
|
|
|
}
|
|
|
});
|
|
|
processCache = all;
|
|
|
+ processRowByKey = idx;
|
|
|
processCacheExpireAt = System.currentTimeMillis() + PROCESS_CACHE_TTL_MS;
|
|
|
}
|
|
|
|
|
|
@@ -2564,4 +2791,433 @@ public class BizDataService {
|
|
|
int to = Math.min(from + pageSize, all.size());
|
|
|
return PageResult.of(pageNum, pageSize, all.size(), all.subList(from, to));
|
|
|
}
|
|
|
+
|
|
|
+ /** 列表默认:待提交/新增优先,同组内按创建时间或 ID 倒序(新录入排前) */
|
|
|
+ private void sortPersonnelForList(List<PersonnelVO> list) {
|
|
|
+ if (list == null || list.size() < 2) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ list.sort((a, b) -> {
|
|
|
+ int pa = personnelListPriority(a);
|
|
|
+ int pb = personnelListPriority(b);
|
|
|
+ if (pa != pb) {
|
|
|
+ return Integer.compare(pa, pb);
|
|
|
+ }
|
|
|
+ Long ta = a.getCreateTime();
|
|
|
+ Long tb = b.getCreateTime();
|
|
|
+ if (ta != null && tb != null && !ta.equals(tb)) {
|
|
|
+ return Long.compare(tb, ta);
|
|
|
+ }
|
|
|
+ String ia = a.getId() == null ? "" : a.getId();
|
|
|
+ String ib = b.getId() == null ? "" : b.getId();
|
|
|
+ return ib.compareTo(ia);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private int personnelListPriority(PersonnelVO p) {
|
|
|
+ if (p == null || p.getBizStatus() == null) {
|
|
|
+ return 50;
|
|
|
+ }
|
|
|
+ switch (p.getBizStatus()) {
|
|
|
+ case DRAFT:
|
|
|
+ return 0;
|
|
|
+ case ABNORMAL:
|
|
|
+ return 1;
|
|
|
+ case PENDING_TOWN_APPROVE:
|
|
|
+ return 2;
|
|
|
+ case NEW:
|
|
|
+ return 3;
|
|
|
+ default:
|
|
|
+ return 10;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 审批/批次明细:新增/减少/异常状态优先展示 */
|
|
|
+ private void sortPaymentDetailsForList(List<PaymentDetailVO> list) {
|
|
|
+ sortPaymentDetailsForList(list, null, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void sortPaymentDetailsForList(List<PaymentDetailVO> list, String sortBy, String sortOrder) {
|
|
|
+ if (list == null || list.size() < 2) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ final boolean amountSort = StringUtils.hasText(sortBy)
|
|
|
+ && ("totalAmount".equals(sortBy)
|
|
|
+ || "funeralAmount".equals(sortBy)
|
|
|
+ || "supplementAmount".equals(sortBy)
|
|
|
+ || "monthlyAmount".equals(sortBy));
|
|
|
+ final boolean asc = "asc".equalsIgnoreCase(sortOrder);
|
|
|
+ list.sort((a, b) -> {
|
|
|
+ if (amountSort) {
|
|
|
+ java.math.BigDecimal va = paymentSortAmount(a, sortBy);
|
|
|
+ java.math.BigDecimal vb = paymentSortAmount(b, sortBy);
|
|
|
+ int cmp = va.compareTo(vb);
|
|
|
+ return asc ? cmp : -cmp;
|
|
|
+ }
|
|
|
+ int pa = paymentDetailListPriority(a);
|
|
|
+ int pb = paymentDetailListPriority(b);
|
|
|
+ if (pa != pb) {
|
|
|
+ return Integer.compare(pa, pb);
|
|
|
+ }
|
|
|
+ String na = a.getName() == null ? "" : a.getName();
|
|
|
+ String nb = b.getName() == null ? "" : b.getName();
|
|
|
+ return na.compareTo(nb);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private java.math.BigDecimal paymentSortAmount(PaymentDetailVO d, String sortBy) {
|
|
|
+ if (d == null) {
|
|
|
+ return java.math.BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+ java.math.BigDecimal v = null;
|
|
|
+ if ("funeralAmount".equals(sortBy)) {
|
|
|
+ v = d.getFuneralAmount();
|
|
|
+ } else if ("supplementAmount".equals(sortBy)) {
|
|
|
+ v = d.getSupplementAmount();
|
|
|
+ } else if ("monthlyAmount".equals(sortBy)) {
|
|
|
+ v = d.getMonthlyAmount();
|
|
|
+ } else {
|
|
|
+ v = d.getTotalAmount();
|
|
|
+ }
|
|
|
+ return v == null ? java.math.BigDecimal.ZERO : v;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int paymentDetailListPriority(PaymentDetailVO d) {
|
|
|
+ if (d == null) {
|
|
|
+ return 50;
|
|
|
+ }
|
|
|
+ String flag = d.getChangeFlag();
|
|
|
+ if ("NEW".equals(flag)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ if ("REMOVE".equals(flag)) {
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ String remarks = d.getRemarks() == null ? "" : d.getRemarks();
|
|
|
+ if (remarks.contains("暂停") || remarks.contains("停止") || remarks.contains("异常")
|
|
|
+ || remarks.contains("死亡")) {
|
|
|
+ return 2;
|
|
|
+ }
|
|
|
+ return 10;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ---------- 写路径/校验:禁止再全表扫 DMS,统一走内存缓存 ----------
|
|
|
+
|
|
|
+ /** 按区划 code 或 DMS id 取原始行(删改区划用)。 */
|
|
|
+ public Map<String, Object> findRegionRaw(String codeOrId) {
|
|
|
+ if (!StringUtils.hasText(codeOrId)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ loadRegionListCached();
|
|
|
+ Map<String, Map<String, Object>> idx = regionRowByKey;
|
|
|
+ return idx == null ? null : idx.get(codeOrId.trim());
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 是否存在下级区划。 */
|
|
|
+ public boolean hasRegionChild(String parentCode) {
|
|
|
+ if (!StringUtils.hasText(parentCode)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ String pid = parentCode.trim();
|
|
|
+ for (RegionVO r : loadRegionListCached()) {
|
|
|
+ if (r != null && pid.equals(r.getParentId())) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 流程表原始行(按 DMS id)。 */
|
|
|
+ public Map<String, Object> findProcessRaw(String id) {
|
|
|
+ if (!StringUtils.hasText(id)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ loadAllProcessCached();
|
|
|
+ Map<String, Map<String, Object>> idx = processRowByKey;
|
|
|
+ return idx == null ? null : idx.get(id.trim());
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 特殊业务单原始行(流程表 action=SPECIAL_BIZ)。 */
|
|
|
+ public List<Map<String, Object>> listSpecialBizProcessRows(
|
|
|
+ String bizType, String approveStatus, String townId, String personnelId) {
|
|
|
+ loadAllProcessCached();
|
|
|
+ Map<String, Map<String, Object>> idx = processRowByKey;
|
|
|
+ if (idx == null || idx.isEmpty()) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ List<Map<String, Object>> out = new ArrayList<Map<String, Object>>();
|
|
|
+ java.util.Set<String> seen = new java.util.HashSet<String>();
|
|
|
+ for (Map<String, Object> row : idx.values()) {
|
|
|
+ if (row == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String id = DmsRowUtils.str(row, "id");
|
|
|
+ if (!StringUtils.hasText(id) || !seen.add(id)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (!"SPECIAL_BIZ".equals(DmsRowUtils.str(row, "c_action"))) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (StringUtils.hasText(bizType) && !bizType.equals(DmsRowUtils.str(row, "c_biz_type"))) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (StringUtils.hasText(approveStatus)
|
|
|
+ && !approveStatus.equals(DmsRowUtils.str(row, "c_status"))) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (StringUtils.hasText(townId) && !townId.equals(DmsRowUtils.str(row, "c_town_id"))) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (StringUtils.hasText(personnelId)
|
|
|
+ && !personnelId.equals(DmsRowUtils.str(row, "c_personnel_id"))) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ out.add(row);
|
|
|
+ }
|
|
|
+ return out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 发放明细:按人员 ID 集合筛(删人摘明细等写路径用)。 */
|
|
|
+ public List<PaymentDetailVO> listPaymentsByPersonnelIds(java.util.Collection<String> personnelIds) {
|
|
|
+ if (personnelIds == null || personnelIds.isEmpty()) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ java.util.Set<String> ids = new java.util.HashSet<String>();
|
|
|
+ for (String id : personnelIds) {
|
|
|
+ if (StringUtils.hasText(id)) {
|
|
|
+ ids.add(id.trim());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (ids.isEmpty()) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ List<PaymentDetailVO> out = new ArrayList<PaymentDetailVO>();
|
|
|
+ for (PaymentDetailVO d : loadPaymentListCached()) {
|
|
|
+ if (d == null || isRemovedPayment(d)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (d.getPersonnelId() != null && ids.contains(d.getPersonnelId())) {
|
|
|
+ out.add(d);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 发放明细:按批次+人员筛(丧葬清零等写路径用)。 */
|
|
|
+ public List<PaymentDetailVO> listPaymentsByBatchAndPersonnel(String batchId, String personnelId) {
|
|
|
+ if (!StringUtils.hasText(batchId) || !StringUtils.hasText(personnelId)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ List<PaymentDetailVO> out = new ArrayList<PaymentDetailVO>();
|
|
|
+ for (PaymentDetailVO d : loadPaymentListCached()) {
|
|
|
+ if (d == null || isRemovedPayment(d)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (batchId.equals(d.getBatchId()) && personnelId.equals(d.getPersonnelId())) {
|
|
|
+ out.add(d);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 回盘统计:按批次 ID 查缓存。 */
|
|
|
+ public BatchStatVO findStatByBatchId(String batchId) {
|
|
|
+ if (!StringUtils.hasText(batchId)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ String bid = batchId.trim();
|
|
|
+ for (BatchStatVO s : loadStatListCached()) {
|
|
|
+ if (s == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (bid.equals(s.getBatchId())) {
|
|
|
+ return s;
|
|
|
+ }
|
|
|
+ String remarks = s.getRemarks();
|
|
|
+ if (remarks != null && remarks.contains("RETURN_CONFIRM|" + bid)) {
|
|
|
+ return s;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 金额标准原始行。 */
|
|
|
+ public Map<String, Object> findStandardRaw(String id) {
|
|
|
+ if (!StringUtils.hasText(id)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ loadStandardListCached();
|
|
|
+ Map<String, Map<String, Object>> idx = standardRowByKey;
|
|
|
+ return idx == null ? null : idx.get(id.trim());
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 同镇金额标准原始行(保存/失效标准时用,不走 DMS 全表)。 */
|
|
|
+ public List<Map<String, Object>> listStandardRawByTown(String townId) {
|
|
|
+ loadStandardListCached();
|
|
|
+ Map<String, Map<String, Object>> idx = standardRowByKey;
|
|
|
+ if (idx == null || idx.isEmpty()) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ List<Map<String, Object>> out = new ArrayList<Map<String, Object>>();
|
|
|
+ java.util.Set<String> seen = new java.util.HashSet<String>();
|
|
|
+ for (Map<String, Object> row : idx.values()) {
|
|
|
+ if (row == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String id = DmsRowUtils.str(row, "id");
|
|
|
+ if (!StringUtils.hasText(id) || !seen.add(id)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (StringUtils.hasText(townId) && !townId.equals(DmsRowUtils.str(row, "c_town_id"))) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ out.add(row);
|
|
|
+ }
|
|
|
+ return out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 流程表原始行:按 action / bizType 筛(欠费、系统配置、导入历史等)。 */
|
|
|
+ public List<Map<String, Object>> listProcessRawRows(String action, String bizType) {
|
|
|
+ loadAllProcessCached();
|
|
|
+ Map<String, Map<String, Object>> idx = processRowByKey;
|
|
|
+ if (idx == null || idx.isEmpty()) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ List<Map<String, Object>> out = new ArrayList<Map<String, Object>>();
|
|
|
+ java.util.Set<String> seen = new java.util.HashSet<String>();
|
|
|
+ for (Map<String, Object> row : idx.values()) {
|
|
|
+ if (row == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String id = DmsRowUtils.str(row, "id");
|
|
|
+ if (!StringUtils.hasText(id) || !seen.add(id)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (StringUtils.hasText(action) && !action.equals(DmsRowUtils.str(row, "c_action"))) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (StringUtils.hasText(bizType)) {
|
|
|
+ String bt = DmsRowUtils.str(row, "c_biz_type");
|
|
|
+ String act = DmsRowUtils.str(row, "c_action");
|
|
|
+ if (!bizType.equals(bt) && !bizType.equals(act)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ out.add(row);
|
|
|
+ }
|
|
|
+ return out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 人员库全部原始行(走内存缓存,供批量运维/导入索引用)。 */
|
|
|
+ public List<Map<String, Object>> listAllPersonnelRawRows() {
|
|
|
+ loadPersonnelListCached();
|
|
|
+ Map<String, Map<String, Object>> idx = personnelRowByKey;
|
|
|
+ if (idx == null || idx.isEmpty()) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ List<Map<String, Object>> out = new ArrayList<Map<String, Object>>();
|
|
|
+ java.util.Set<String> seen = new java.util.HashSet<String>();
|
|
|
+ for (Map<String, Object> row : idx.values()) {
|
|
|
+ if (row == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String dmsId = DmsRowUtils.str(row, "id");
|
|
|
+ if (!StringUtils.hasText(dmsId) || !seen.add(dmsId)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ out.add(row);
|
|
|
+ }
|
|
|
+ return out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 导入/校验:身份证号 → 人员原始行(内存索引,不扫 DMS)。 */
|
|
|
+ public Map<String, Map<String, Object>> mapPersonnelByIdNumber() {
|
|
|
+ loadPersonnelListCached();
|
|
|
+ Map<String, Map<String, Object>> out = new HashMap<String, Map<String, Object>>();
|
|
|
+ Map<String, Map<String, Object>> idx = personnelRowByKey;
|
|
|
+ if (idx == null) {
|
|
|
+ return out;
|
|
|
+ }
|
|
|
+ java.util.Set<String> seen = new java.util.HashSet<String>();
|
|
|
+ for (Map<String, Object> row : idx.values()) {
|
|
|
+ if (row == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String dmsId = DmsRowUtils.str(row, "id");
|
|
|
+ if (!StringUtils.hasText(dmsId) || !seen.add(dmsId)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String idn = DmsRowUtils.str(row, "c_id_number");
|
|
|
+ if (StringUtils.hasText(idn)) {
|
|
|
+ out.put(idn.trim().toUpperCase(), row);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 当前人员库最大 P 序号(新增/导入取号用)。 */
|
|
|
+ public int maxPersonnelBizSeq() {
|
|
|
+ int max = 100;
|
|
|
+ for (PersonnelVO p : loadPersonnelListCached()) {
|
|
|
+ if (p == null || !StringUtils.hasText(p.getId())) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String id = p.getId().trim();
|
|
|
+ if (id.matches("P\\d+")) {
|
|
|
+ try {
|
|
|
+ max = Math.max(max, Integer.parseInt(id.substring(1)));
|
|
|
+ } catch (Exception ignore) {
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return max;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 同业务号全部批次原始行(purge/查找用,走批次缓存)。 */
|
|
|
+ public List<Map<String, Object>> findAllBatchRawRows(String bizNo) {
|
|
|
+ if (!StringUtils.hasText(bizNo)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ loadBatchByIdCached();
|
|
|
+ Map<String, Map<String, Object>> idx = batchRowByKey;
|
|
|
+ if (idx == null || idx.isEmpty()) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ String key = bizNo.trim();
|
|
|
+ List<Map<String, Object>> out = new ArrayList<Map<String, Object>>();
|
|
|
+ java.util.Set<String> seen = new java.util.HashSet<String>();
|
|
|
+ for (Map<String, Object> row : idx.values()) {
|
|
|
+ if (row == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String id = DmsRowUtils.str(row, "id");
|
|
|
+ if (!StringUtils.hasText(id) || !seen.add(id)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String no = DmsRowUtils.str(row, "c_batch_number");
|
|
|
+ if (key.equals(id) || key.equals(no)) {
|
|
|
+ out.add(row);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 同业务号全部批次(purge 用,走批次缓存)。 */
|
|
|
+ public List<BatchVO> findBatchesByBizNo(String bizNo) {
|
|
|
+ if (!StringUtils.hasText(bizNo)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ String key = bizNo.trim();
|
|
|
+ List<BatchVO> out = new ArrayList<BatchVO>();
|
|
|
+ java.util.Set<String> seen = new java.util.HashSet<String>();
|
|
|
+ for (BatchVO b : loadBatchByIdCached().values()) {
|
|
|
+ if (b == null || !StringUtils.hasText(b.getId()) || !seen.add(b.getId())) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (key.equals(b.getId()) || key.equals(b.getBatchNo())) {
|
|
|
+ out.add(b);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return out;
|
|
|
+ }
|
|
|
}
|