package jnpf.dmsService;
|
|
import jnpf.dmsEntity.center.DmsCenterVersionSnapshot;
|
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import jnpf.dmsEntity.*;
|
import jnpf.dmsEntity.storage.approval.*;
|
import jnpf.dmsMapper.*;
|
import jnpf.exception.DataException;
|
import lombok.RequiredArgsConstructor;
|
import org.springframework.stereotype.Component;
|
import java.nio.charset.StandardCharsets;
|
import java.security.MessageDigest;
|
import java.util.*;
|
|
/** Reads the complete native source; never accepts a client-provided subset of records. */
|
@Component
|
@RequiredArgsConstructor
|
public class DmsApprovedAuditSource {
|
// Persisted approval hashes are an internal contract, independent of HTTP JSON settings.
|
// Retain the existing UTC ISO dates, camelCase fields, nulls and compact JSON bytes.
|
private static final ObjectMapper SOURCE_JSON = new ObjectMapper()
|
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
|
.setTimeZone(TimeZone.getTimeZone("UTC"));
|
private final DmsWenjianXinzengShenheMapper newMapper;
|
private final DmsWailaiWenjianXinzengShenheMapper externalMapper;
|
private final DmsWenjianXiudingShenheMapper revisionMapper;
|
private final DmsWenjianXinzengShenheJiluMingxiMapper newRecords;
|
private final DmsWenjianXiudingShenheJiluMingxiMapper revisionRecords;
|
private final DmsWenjianXiudingShenqingZixiangMapper revisionDetails;
|
private final DmsWenjianXiudingShenqingMapper revisionRequests;
|
private final DmsWenjianGuanxiMapper existingRelations;
|
private final DmsWenjianBianhaoMapper numbers;
|
private final DmsWenjianBanbenMapper businessVersions;
|
private final jnpf.dmsService.center.DmsCenterQueryService centerVersions;
|
private final DmsWenjianGuanlianMingxiMapper relations;
|
private final ObjectMapper json;
|
private final DmsDisableApplicationGuard disableApplications;
|
private final DmsXiudingKanwuService revisionErrata;
|
|
public static final class Source {
|
private final DmsApprovedBatch batch;
|
private final String preparedBy;
|
private final String reviewerIds;
|
private final Date preparedOn;
|
private final boolean settled;
|
private final List<jnpf.dmsEntity.kanwu.DmsXiudingKanwuRow> errata;
|
public Source(DmsApprovedBatch batch, String preparedBy, Date preparedOn, boolean settled) {
|
this(batch, preparedBy, preparedOn, settled, Collections.emptyList());
|
}
|
public Source(DmsApprovedBatch batch, String preparedBy, Date preparedOn, boolean settled,
|
List<jnpf.dmsEntity.kanwu.DmsXiudingKanwuRow> errata) {
|
this(batch, preparedBy, preparedOn, settled, errata, null);
|
}
|
public Source(DmsApprovedBatch batch, String preparedBy, Date preparedOn, boolean settled,
|
List<jnpf.dmsEntity.kanwu.DmsXiudingKanwuRow> errata, String reviewerIds) {
|
this.reviewerIds = reviewerIds;
|
this.batch = batch; this.preparedBy = preparedBy; this.preparedOn = preparedOn; this.settled = settled;
|
this.errata = errata;
|
}
|
public DmsApprovedBatch batch() { return batch; }
|
public String reviewerIds() { return reviewerIds; }
|
public String preparedBy() { return preparedBy; }
|
public Date preparedOn() { return preparedOn; }
|
public boolean settled() { return settled; }
|
public List<jnpf.dmsEntity.kanwu.DmsXiudingKanwuRow> errata() { return errata; }
|
}
|
|
public Source load(String tenant, String flow, String task, String type, String actor, Date approvedAt) {
|
if ("wailai_wenjian_xinzeng_shenhe".equals(type))
|
return loadExternal(tenant, flow, task, actor, approvedAt);
|
Object audit;
|
List<?> records;
|
String baseId = null;
|
String baseFileId = null;
|
String documentNo = null;
|
String approvalPageFileJson = null;
|
String numberSnapshot = null;
|
Date effective, expires, created;
|
String creator;
|
String reviewerIds;
|
if ("xinzeng_shenhe".equals(type)) {
|
DmsWenjianXinzengShenheEntity a = unique(newMapper.selectByFlowForUpdate(tenant, flow, task));
|
audit = a;
|
reviewerIds = a.getShenheRenId();
|
records = newRecords.selectByAuditForUpdate(tenant, a.getId());
|
DmsWenjianBianhaoEntity n = numbers.selectActiveByIdForUpdate(tenant, a.getWenjianBianhaoId());
|
if (n == null || !"using".equals(n.getBizStatus()) || n.getDangqianWenjianMingcheng() == null || !Objects.equals(n.getWenjianBianhao(), a.getWenjianBianhao()))
|
throw new DataException("审核文件编号与已发放编号不一致");
|
documentNo = n.getWenjianBianhao();
|
approvalPageFileJson = approvalPageFileJson(n);
|
numberSnapshot = n.getGuizeKuaizhao();
|
effective = a.getShengxiaoRiqi(); expires = a.getYouxiaoQiZhi();
|
creator = a.getCreatorUserId(); created = a.getCreatorTime();
|
} else if ("xiuding_shenhe".equals(type)) {
|
DmsWenjianXiudingShenheEntity a = unique(revisionMapper.selectByFlowForUpdate(tenant, flow, task));
|
audit = a;
|
reviewerIds = a.getShenheRenId();
|
records = revisionRecords.selectByAuditForUpdate(tenant, a.getId());
|
DmsWenjianXiudingShenqingZixiangEntity detail = revisionDetails.selectByIdForUpdate(tenant, a.getXiudingShenqingZixiangId());
|
if (detail == null) throw new DataException("修订申请子项不存在");
|
DmsWenjianXiudingShenqingEntity request = revisionRequests.selectByIdForUpdate(tenant, detail.getForeignId());
|
if (request == null || !Integer.valueOf(2).equals(request.getFlowState()))
|
throw new DataException("修订申请尚未审批通过");
|
if ("occupied".equals(detail.getBizStatus()) && !a.getId().equals(detail.getZhanYongDanjuId()))
|
throw new DataException("修订申请子项已被其他审核单占用");
|
baseId = detail.getYuanWenjianBanbenId();
|
baseFileId = detail.getWenjianId();
|
disableApplications.requireFileEditable(tenant, baseFileId);
|
effective = a.getShengxiaoRiqi(); expires = a.getYouxiaoQiZhi();
|
creator = a.getCreatorUserId() != null ? a.getCreatorUserId() : request.getCreatorUserId();
|
created = a.getCreatorTime() != null ? a.getCreatorTime() : request.getCreatorTime();
|
} else throw new DataException("审核类型无效");
|
ObjectNode main = SOURCE_JSON.valueToTree(audit);
|
if (main.path("flowState").asInt(-1) != 2) throw new DataException("原生审核单尚未保存批准状态");
|
if ("yes".equals(main.path("shifouPeixun").asText()) && !main.hasNonNull("peixunWanchengRiqi"))
|
throw new DataException("需要培训的文件尚未完成培训");
|
if (creator == null || created == null) throw new DataException("原生审核单缺少起草人或起草时间");
|
String auditId = text(main, "id");
|
List<jnpf.dmsEntity.kanwu.DmsXiudingKanwuRow> errata = "xiuding_shenhe".equals(type)
|
? revisionErrata.loadAndValidate(tenant, auditId, baseFileId, baseId) : Collections.emptyList();
|
DmsApprovedBatch batch = new DmsApprovedBatch();
|
batch.setTenantId(tenant); batch.setFlowId(flow); batch.setTaskId(task); batch.setAuditId(auditId);
|
batch.setAuditType(type); batch.setActorId(actor); batch.setApprovedAt(approvedAt);
|
batch.setScheduledAt(DmsApprovedFileDates.scheduled(effective));
|
batch.setEffectiveTo(DmsApprovedFileDates.expires(expires, batch.getScheduledAt()));
|
List<DmsApprovedItem> items = new ArrayList<>();
|
String prefix = type + ":" + auditId + ":";
|
DmsApprovedItem mainItem = item(tenant, prefix + "zhuwenjian", null, main, true,
|
"xinzeng_shenhe".equals(type) ? "xinjian" : text(main, "biangengFangshi"), baseId, baseFileId, documentNo);
|
if ("REVISE".equals(mainItem.getChangeType())) {
|
approvalPageFileJson = revisionApprovalPageFileJson(tenant, baseId, baseFileId);
|
}
|
mainItem.setApprovalPageFileJson(approvalPageFileJson);
|
items.add(mainItem);
|
copySignaturePage(numberSnapshot, mainItem);
|
// Use the already validated source for both the legacy and the new physical-import paths.
|
mainItem.setQianpiyeFiles(approvalPageFileJson);
|
List<JsonNode> sourceRows = new ArrayList<>();
|
if (records != null) for (Object row : records) sourceRows.add(SOURCE_JSON.valueToTree(row));
|
sourceRows.sort(Comparator.comparing(row -> text(row, "id")));
|
for (JsonNode row : sourceRows) {
|
if (!Objects.equals(text(main, "wenjianMuluId"), text(row, "wenjianMuluId")))
|
throw new DataException("记录文件目录必须与主文件一致");
|
String change = "xinzeng_shenhe".equals(type) ? "xinjian" : text(row, "biangengFangshi");
|
if ("xinzeng_shenhe".equals(type) && row.hasNonNull("biangengFangshi")
|
&& !row.path("biangengFangshi").asText().isBlank() && !"xinjian".equals(row.path("biangengFangshi").asText()))
|
throw new DataException("新增记录只能选择新建");
|
items.add(item(tenant, prefix + "jilu:" + text(row, "id"), text(row, "id"), row, false,
|
change, row.path("yuanWenjianBanbenId").asText(null), row.path("jiluWenjianId").asText(null), row.path("fujianBianhao").asText(null)));
|
}
|
List<DmsWenjianGuanlianMingxiEntity> related = "xinzeng_shenhe".equals(type) ? relations.selectNewAuditRelations(tenant, auditId)
|
: relations.selectRevisionAuditRelations(tenant, auditId);
|
if (baseFileId != null) {
|
Set<String> recordIds = new HashSet<>();
|
sourceRows.forEach(row -> { if (row.hasNonNull("jiluWenjianId")) recordIds.add(row.path("jiluWenjianId").asText()); });
|
appendRemovedRecords(tenant, baseFileId, recordIds, prefix, items);
|
Set<String> relatedIds = new HashSet<>();
|
if (related != null) for (DmsWenjianGuanlianMingxiEntity relation : related) {
|
if (!relatedIds.add(relation.getGuanlianWenjianId())) throw new DataException("审核单包含重复关联文件");
|
}
|
requireRetained(tenant, baseFileId, "guanlian_wenjian", relatedIds);
|
}
|
if (items.stream().map(DmsApprovedItem::getSourceKey).distinct().count() != items.size())
|
throw new DataException("审核来源包含重复记录");
|
if (items.stream().filter(i -> i.getBaseFileVersionId() != null).map(DmsApprovedItem::getBaseFileVersionId).distinct().count()
|
!= items.stream().filter(i -> i.getBaseFileVersionId() != null).count())
|
throw new DataException("审核单不能重复使用同一来源文件");
|
// Exclude only result fields written by settlement. All exposed native inputs participate.
|
boolean settled = main.hasNonNull("shengxiaoWenjianBanbenId");
|
scrub(main);
|
omitEmptyReadScopeFromDigest(main);
|
main.remove(Arrays.asList("wenjianId", "yuanWenjianBanbenId"));
|
sourceRows.forEach(row -> {
|
scrub((ObjectNode) row);
|
omitEmptyReadScopeFromDigest((ObjectNode) row);
|
if ("xinzeng_shenhe".equals(type) || "xinjian".equals(row.path("biangengFangshi").asText()))
|
((ObjectNode) row).remove("jiluWenjianId");
|
});
|
List<Object> digestInputs = new ArrayList<>(Arrays.asList(main, sourceRows, related, baseId, baseFileId));
|
List<Object> removals = new ArrayList<>();
|
items.stream().filter(i -> "RETIRE".equals(i.getChangeType())).forEach(i ->
|
removals.add(Arrays.asList(i.getFileId(), i.getBusinessVersionId(), i.getDocumentId(), i.getFileVersionId())));
|
if (!removals.isEmpty()) digestInputs.add(removals);
|
if (approvalPageFileJson != null) digestInputs.add(approvalPageFileJson);
|
// Preserve historic hashes when no errata exist; new handling decisions are approval inputs.
|
if (!errata.isEmpty()) digestInputs.add(errata);
|
batch.setSourceDigest(digest(digestInputs));
|
batch.setItems(items);
|
return new Source(batch, creator, created, settled, errata, reviewerIds);
|
}
|
|
private Source loadExternal(String tenant, String flow, String task, String actor, Date approvedAt) {
|
DmsWailaiWenjianXinzengShenheEntity audit = unique(externalMapper.selectByFlowForUpdate(tenant, flow, task));
|
if (!Integer.valueOf(2).equals(audit.getFlowState())) throw new DataException("外来文件审核单尚未批准");
|
if (audit.getShenqingRen() == null || audit.getShenqingRen().isBlank()
|
|| audit.getShenqingRiqi() == null) throw new DataException("外来文件审核单缺少申请人或申请日期");
|
String version = requireExternal(audit.getBanbenhao(), "版本号");
|
String fileJson = requireExternal(audit.getBizFiles(), "附件");
|
try {
|
JsonNode files = json.readTree(fileJson);
|
if (files == null || !files.isArray() || files.size() != 1 || !files.get(0).isObject())
|
throw new DataException("外来文件审核单必须包含一份附件");
|
} catch (java.io.IOException error) { throw new DataException("外来文件附件格式无效"); }
|
String folder = requireExternal(audit.getWenjianMuluId(), "文件目录");
|
try { UUID.fromString(folder); } catch (IllegalArgumentException error) {
|
throw new DataException("外来文件目录不是有效的中心目录ID");
|
}
|
DmsApprovedItem item = new DmsApprovedItem();
|
item.setTenantId(tenant);
|
item.setSourceKey("wailai_wenjian_xinzeng_shenhe:" + audit.getId() + ":wailaiWenjian");
|
item.setChangeType("CREATE");
|
item.setDocumentNo(requireExternal(audit.getWailaiWenjianBianhao(), "文件编号"));
|
item.setDocumentName(requireExternal(audit.getWenjianMingcheng(), "文件名称"));
|
item.setDepartmentId(requireExternal(audit.getBumenId(), "归属部门"));
|
item.setFolderId(folder);
|
item.setVersionLabel(version);
|
item.setFileJson(fileJson);
|
item.setShifouShoukong("yes");
|
|
DmsApprovedBatch batch = new DmsApprovedBatch();
|
batch.setTenantId(tenant);
|
batch.setFlowId(flow);
|
batch.setTaskId(task);
|
batch.setAuditId(audit.getId());
|
batch.setAuditType("wailai_wenjian_xinzeng_shenhe");
|
batch.setActorId(actor);
|
batch.setApprovedAt(approvedAt);
|
batch.setScheduledAt(DmsApprovedFileDates.scheduled(approvedAt));
|
ObjectNode source = SOURCE_JSON.valueToTree(audit);
|
source.remove(Arrays.asList("wenjianId", "shengxiaoWenjianBanbenId", "banbenShengxiaoShijian"));
|
batch.setSourceDigest(digest(source));
|
batch.setItems(List.of(item));
|
return new Source(batch, audit.getShenqingRen(), audit.getShenqingRiqi(),
|
audit.getShengxiaoWenjianBanbenId() != null);
|
}
|
|
private static String requireExternal(String value, String name) {
|
if (value == null || value.isBlank()) throw new DataException("外来文件审核单缺少" + name);
|
return value.trim();
|
}
|
|
private void copySignaturePage(String snapshot, DmsApprovedItem main) {
|
if (snapshot == null || snapshot.isBlank()) return;
|
try {
|
JsonNode rule = json.readTree(snapshot);
|
if (!rule.isObject()) throw new DataException("编号规则快照格式不正确");
|
main.setQianpiyeId(optionalSnapshotText(rule, "qianpiye_id"));
|
main.setQianpiyeFiles(optionalSnapshotText(rule, "qianpiye_files"));
|
} catch (DataException e) { throw e; }
|
catch (Exception e) { throw new DataException("无法读取编号规则签批页快照"); }
|
}
|
|
private String optionalSnapshotText(JsonNode rule, String field) {
|
JsonNode value = rule.get(field);
|
if (value == null || value.isNull()) return null;
|
if (!value.isTextual()) throw new DataException("编号规则快照字段必须为文本:" + field);
|
return value.textValue();
|
}
|
|
private DmsApprovedItem item(String tenant, String key, String rowId, JsonNode row, boolean main,
|
String change, String baseId, String baseFileId, String no) {
|
DmsApprovedItem item = new DmsApprovedItem();
|
item.setTenantId(tenant); item.setSourceKey(key); item.setSourceRowId(rowId);
|
item.setReadAccessScope(DmsApprovedDepartmentScope.normalize(row.path("chayueShouquanFanweiId").asText(null)));
|
item.setDocumentName(text(row, main ? "wenjianMingcheng" : "fujianMingcheng"));
|
String fileJson = row.path(main ? "wenjian" : "jiluWenjian").asText(null);
|
if ((fileJson == null || fileJson.isBlank()) && !"wubianhua".equals(change)) throw new DataException("审核来源缺少文件");
|
item.setFileJson(fileJson);
|
item.setDepartmentId(text(row, "guishuBumenId")); item.setFolderId(text(row, "wenjianMuluId"));
|
try { UUID.fromString(item.getFolderId()); } catch (IllegalArgumentException e) { throw new DataException("文件目录不是有效的中心目录ID"); }
|
item.setDocumentNo(no);
|
if (main) item.setShifouShoukong("yes");
|
if (!main) {
|
// Audit records choose their control flag directly; legacy grade metadata is optional.
|
String controlled = text(row, "shifouShoukong");
|
if (!"yes".equals(controlled) && !"no".equals(controlled)) throw new DataException("记录受控属性无效");
|
item.setShifouShoukong(controlled);
|
if ("xinjian".equals(change) && row.hasNonNull("banbenhao") && !row.path("banbenhao").asText().isBlank()
|
&& !"D(00)".equals(row.path("banbenhao").asText())) throw new DataException("新增记录版本必须为 D(00)");
|
}
|
switch (change) {
|
case "xinjian": item.setChangeType("CREATE"); item.setVersionLabel("D(00)"); break;
|
case "shengji":
|
case "wubianhua": {
|
disableApplications.requireFileEditable(tenant, baseFileId);
|
DmsWenjianBanbenEntity base = businessVersions.selectById(tenant, baseId);
|
if (base == null || !Objects.equals(base.getWenjianId(), baseFileId) || base.getWenjianZhongxinBanbenId() == null)
|
throw new DataException("来源业务版本尚未关联文件中心,不能自动升级或沿用");
|
DmsCenterVersionSnapshot center = centerVersions.version(tenant, UUID.fromString(base.getWenjianZhongxinBanbenId()));
|
if (center == null) throw new DataException("来源中心版本不存在");
|
item.setDocumentId(center.documentId()); item.setBaseFileVersionId(center.fileVersionId());
|
item.setChangeType("wubianhua".equals(change) ? "REUSE" : "REVISE");
|
item.setVersionLabel("REUSE".equals(item.getChangeType()) ? center.versionLabel()
|
: String.format(java.util.Locale.ROOT, "D(%02d)", Math.addExact(base.getBanbenXuhao(), 1)));
|
if ("REUSE".equals(item.getChangeType())) {
|
if (item.getFileJson() != null && !item.getFileJson().isBlank()
|
&& !sameContent(base.getWenjianNeirong(), item.getFileJson()))
|
throw new DataException("沿用版本不能修改文件内容");
|
item.setFileJson(base.getWenjianNeirong());
|
item.setFileVersionId(center.fileVersionId()); item.setChecksumSha256(center.checksumSha256());
|
}
|
}
|
break;
|
default: throw new DataException("文件变更方式无效");
|
}
|
if ("CREATE".equals(item.getChangeType()) && (no == null || no.isBlank())) throw new DataException("文件编号缺失");
|
return item;
|
}
|
|
private void appendRemovedRecords(String tenant, String mainFileId, Set<String> retained,
|
String prefix, List<DmsApprovedItem> items) {
|
List<DmsWenjianGuanxiEntity> existing = existingRelations.selectEffectiveByMainAndType(
|
tenant, mainFileId, DmsWenjianYewuBanbenService.RELATION_RECORD);
|
if (existing == null) return;
|
existing.stream().filter(r -> !retained.contains(r.getGuanlianWenjianId()))
|
.sorted(Comparator.comparing(DmsWenjianGuanxiEntity::getGuanlianWenjianId)).forEach(relation -> {
|
String fileId = relation.getGuanlianWenjianId();
|
disableApplications.requireFileEditable(tenant, fileId);
|
DmsWenjianBanbenEntity base = businessVersions.selectCurrentByFileId(tenant, fileId);
|
if (base == null || !DmsWenjianYewuBanbenService.RECORD_FILE.equals(base.getWenjianLeixing())
|
|| !"effective".equals(base.getBizStatus()) || base.getWenjianZhongxinBanbenId() == null)
|
throw new DataException("移除记录缺少有效业务版本或中心关联");
|
DmsCenterVersionSnapshot center = centerVersions.version(tenant, UUID.fromString(base.getWenjianZhongxinBanbenId()));
|
if (center == null) throw new DataException("移除记录的中心版本不存在");
|
DmsApprovedItem item = new DmsApprovedItem();
|
item.setTenantId(tenant); item.setSourceKey(prefix + "removed:" + fileId);
|
item.setChangeType("RETIRE"); item.setFileId(fileId); item.setBusinessVersionId(base.getId());
|
item.setDocumentId(center.documentId()); item.setFileVersionId(center.fileVersionId());
|
item.setBaseFileVersionId(center.fileVersionId()); item.setChecksumSha256(center.checksumSha256());
|
items.add(item);
|
});
|
}
|
|
private void requireRetained(String tenant, String fileId, String type, Set<String> retained) {
|
List<DmsWenjianGuanxiEntity> existing = existingRelations.selectEffectiveByMainAndType(tenant, fileId, type);
|
if (existing != null) for (DmsWenjianGuanxiEntity row : existing) {
|
if (!retained.contains(row.getGuanlianWenjianId())) throw new DataException("现行记录或关联文件不能从修订审核中删除");
|
}
|
}
|
|
private void scrub(ObjectNode value) {
|
value.remove(Arrays.asList("shengxiaoWenjianBanbenId", "banbenShengxiaoShijian", "banbenhao"));
|
}
|
|
private String approvalPageFileJson(DmsWenjianBianhaoEntity number) {
|
String snapshot = number.getGuizeKuaizhao();
|
if (snapshot == null || snapshot.isBlank()) return null;
|
try {
|
JsonNode value = json.readTree(snapshot).get("qianpiye_files");
|
if (value == null || value.isNull()) return null;
|
String fileJson = value.isTextual() ? value.asText().trim() : value.toString();
|
if (fileJson.isEmpty()) return null;
|
JsonNode files = json.readTree(fileJson);
|
if (files == null || files.isNull() || (files.isArray() && files.isEmpty())) return null;
|
return fileJson;
|
} catch (Exception exception) {
|
throw new DataException("文件编号规则签批页快照格式不正确");
|
}
|
}
|
|
private String revisionApprovalPageFileJson(String tenant, String baseId, String baseFileId) {
|
DmsWenjianBanbenEntity base = businessVersions.selectById(tenant, baseId);
|
if (base == null || !Objects.equals(base.getWenjianId(), baseFileId)) {
|
throw new DataException("来源业务版本不存在,无法读取签批页规则");
|
}
|
String numberId = base.getWenjianBianhaoId();
|
if (numberId == null || numberId.isBlank()) return null;
|
DmsWenjianBianhaoEntity number = numbers.selectActiveByIdForUpdate(tenant, numberId);
|
if (number == null || !"using".equals(number.getBizStatus())
|
|| !Objects.equals(number.getWenjianBianhao(), base.getWenjianBianhao())) {
|
return null;
|
}
|
return approvalPageFileJson(number);
|
}
|
|
// Empty scope has one business meaning. Omitting it only from the digest preserves pre-field approvals
|
// after migration fills their native fields with []; non-empty scopes remain protected inputs.
|
private void omitEmptyReadScopeFromDigest(ObjectNode row) {
|
if (DmsApprovedDepartmentScope.parse(row.path("chayueShouquanFanweiId").asText(null)).isEmpty())
|
row.remove("chayueShouquanFanweiId");
|
}
|
|
private String digest(Object value) {
|
try {
|
return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256")
|
.digest(SOURCE_JSON.writeValueAsString(value).getBytes(StandardCharsets.UTF_8)));
|
} catch (Exception e) { throw new DataException("无法计算批准来源摘要"); }
|
}
|
|
@org.springframework.beans.factory.annotation.Autowired
|
private jnpf.file.FileContentApi fileContentApi;
|
|
private boolean sameContent(String left, String right) {
|
return jnpf.file.FileContentEquality.same(left, right, fileContentApi);
|
}
|
private static String text(JsonNode value, String field) {
|
String result = value.path(field).asText(null);
|
if (result == null || result.isBlank()) throw new DataException("审核来源缺少字段:" + field);
|
return result.trim();
|
}
|
private static <T> T unique(List<T> rows) {
|
if (rows == null || rows.size() != 1) throw new DataException("必须匹配唯一的原生审核单");
|
return rows.get(0);
|
}
|
}
|