package jnpf.dmsService.impl;
|
|
import cn.hutool.core.util.IdUtil;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import jnpf.audit.sdk.annotation.AuditLog;
|
import jnpf.base.ActionResult;
|
import jnpf.base.ActionResultCode;
|
import jnpf.base.BillRuleApi;
|
import jnpf.base.UserInfo;
|
import jnpf.dms.DmsResourceApi;
|
import jnpf.dmsEntity.DmsWenjianBanbenEntity;
|
import jnpf.dmsEntity.permission.form.DmsPersonalFolderFileForm;
|
import jnpf.dmsEntity.print.form.DmsWorkflowPrintFilesGrantForm;
|
import jnpf.dmsEntity.print.vo.DmsWorkflowPrintFilesGrantResultVO;
|
import jnpf.dmsEntity.recordprint.DmsJiluDayinLiushuiEntity;
|
import jnpf.dmsEntity.recordprint.DmsJiluDayinShenqingEntity;
|
import jnpf.dmsEntity.recordprint.DmsJiluDayinShenqingZixiangEntity;
|
import jnpf.dmsEntity.recordprint.form.DmsRecordPrintHuishouForm;
|
import jnpf.dmsEntity.recordprint.form.DmsRecordPrintHuishouItemForm;
|
import jnpf.dmsEntity.recordprint.form.DmsRecordPrintIssueForm;
|
import jnpf.dmsEntity.recordprint.form.DmsRecordPrintIssueItemForm;
|
import jnpf.dmsEntity.recordprint.vo.DmsRecordPrintCopyVO;
|
import jnpf.dmsEntity.recordprint.vo.DmsRecordPrintConfirmVO;
|
import jnpf.dmsEntity.recordprint.vo.DmsRecordPrintPrepareVO;
|
import jnpf.dmsEntity.recordprint.vo.DmsRecordPrintSerialVO;
|
import jnpf.dmsEntity.preview.vo.DmsFilePageCountVO;
|
import jnpf.dmsMapper.DmsJiluDayinLiushuiMapper;
|
import jnpf.dmsMapper.DmsRecordPrintItemMapper;
|
import jnpf.dmsMapper.DmsRecordPrintRequestMapper;
|
import jnpf.dmsMapper.DmsWenjianBanbenMapper;
|
import jnpf.dmsPermission.DmsCurrentIdentity;
|
import jnpf.dmsService.DmsFilePageCountService;
|
import jnpf.dmsService.DmsRecordPrintService;
|
import jnpf.dmsService.recordprint.DmsRecordPrintPageParser;
|
import jnpf.dmsService.recordprint.DmsRecordPrintProperties;
|
import jnpf.dmsService.recordprint.DmsRecordPrintReservation;
|
import jnpf.dmsService.recordprint.DmsRecordPrintReservationStore;
|
import jnpf.exception.DataException;
|
import lombok.RequiredArgsConstructor;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.support.TransactionSynchronization;
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
|
import java.io.IOException;
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.Date;
|
import java.util.HashSet;
|
import java.util.LinkedHashMap;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.Set;
|
import java.util.TreeSet;
|
import java.util.UUID;
|
import java.util.concurrent.TimeUnit;
|
|
/**
|
* 记录文件打印准备业务实现。
|
*/
|
@Service
|
@RequiredArgsConstructor
|
@Slf4j
|
public class DmsRecordPrintServiceImpl implements DmsRecordPrintService {
|
|
private static final String PRINT_TYPE_BOUND = "chengce";
|
private static final String PRINT_TYPE_LOOSE = "fensan";
|
private static final String PRINT_STATUS_PROCESSING = "dayinzhong";
|
private static final String PRINT_STATUS_COMPLETE = "dayin_wancheng";
|
private static final String CHAZHAO_LEIXING_HUISHOU = "huishou";
|
private static final String FLOWABLE_SERVICE_ID = "jnpf-flowable";
|
|
private final DmsJiluDayinLiushuiMapper mapper;
|
private final DmsRecordPrintItemMapper itemMapper;
|
private final DmsRecordPrintRequestMapper requestMapper;
|
private final DmsWenjianBanbenMapper banbenMapper;
|
private final DmsResourceApi dmsResourceApi;
|
private final DmsCurrentIdentity identity;
|
private final BillRuleApi billRuleApi;
|
private final DmsFilePageCountService pageCountService;
|
private final DmsRecordPrintPageParser pageParser;
|
private final DmsRecordPrintProperties properties;
|
private final DmsRecordPrintReservationStore reservationStore;
|
|
@Override
|
public void complete(String printItemId) throws DataException {
|
String id = requireValue(printItemId, "打印申请子项ID");
|
DmsJiluDayinShenqingZixiangEntity item = new DmsJiluDayinShenqingZixiangEntity();
|
item.setDayinZhuangtai(PRINT_STATUS_COMPLETE);
|
if (itemMapper.update(item,
|
Wrappers.<DmsJiluDayinShenqingZixiangEntity>lambdaUpdate()
|
.eq(DmsJiluDayinShenqingZixiangEntity::getId, id)
|
.isNull(DmsJiluDayinShenqingZixiangEntity::getDeleteMark)) != 1) {
|
throw new DataException("未找到有效的记录打印申请子项:" + id);
|
}
|
}
|
|
/**
|
* 查询流程对应的记录打印申请和全部子项,并为申请人授予永久打印权限。
|
*
|
* @param flowId 流程实例 ID
|
* @param flowTaskId 流程任务 ID
|
* @return 文件打印授权结果
|
* @throws DataException 申请、子项或业务版本无效,或者授权接口调用失败
|
*/
|
@Override
|
public DmsWorkflowPrintFilesGrantResultVO authorize(
|
String flowId, String flowTaskId) throws DataException {
|
String normalizedFlowId = requireValue(flowId, "流程ID(flow_id)");
|
String normalizedFlowTaskId = requireValue(flowTaskId, "流程任务ID(flow_task_id)");
|
DmsJiluDayinShenqingEntity request = findPrintRequest(
|
normalizedFlowId, normalizedFlowTaskId);
|
List<DmsJiluDayinShenqingZixiangEntity> items = findPrintItems(request.getId());
|
Set<String> versionIds = extractVersionIds(items);
|
List<DmsWenjianBanbenEntity> versions = findBusinessVersions(versionIds);
|
DmsWorkflowPrintFilesGrantForm form = buildPrintGrantForm(request, versions);
|
|
ActionResult<DmsWorkflowPrintFilesGrantResultVO> response;
|
try {
|
response = dmsResourceApi.grantWorkflowPrintFiles(FLOWABLE_SERVICE_ID, form);
|
} catch (RuntimeException exception) {
|
throw new DataException("调用DMS文件打印授权接口失败");
|
}
|
if (response == null || !ActionResultCode.SUCCESS.getCode().equals(response.getCode())
|
|| response.getData() == null) {
|
String message = response == null ? null : response.getMsg();
|
throw new DataException(isBlank(message) ? "记录文件打印授权失败或响应数据不完整" : message);
|
}
|
return response.getData();
|
}
|
|
/** 查询流程唯一对应的未删除记录打印申请。 */
|
private DmsJiluDayinShenqingEntity findPrintRequest(String flowId, String flowTaskId) {
|
List<DmsJiluDayinShenqingEntity> rows = requestMapper.selectList(
|
Wrappers.<DmsJiluDayinShenqingEntity>lambdaQuery()
|
.eq(DmsJiluDayinShenqingEntity::getFlowId, flowId)
|
.eq(DmsJiluDayinShenqingEntity::getFlowTaskId, flowTaskId)
|
.isNull(DmsJiluDayinShenqingEntity::getDeleteMark));
|
if (rows.isEmpty()) {
|
throw new DataException("未找到记录打印申请:flow_id=" + flowId
|
+ ", flow_task_id=" + flowTaskId);
|
}
|
if (rows.size() > 1) {
|
throw new DataException("flow_id与flow_task_id关联了多条记录打印申请,无法授权");
|
}
|
return rows.get(0);
|
}
|
|
/** 根据申请主键查询全部未删除的记录打印子项。 */
|
private List<DmsJiluDayinShenqingZixiangEntity> findPrintItems(String requestId) {
|
List<DmsJiluDayinShenqingZixiangEntity> rows = itemMapper.selectList(
|
Wrappers.<DmsJiluDayinShenqingZixiangEntity>lambdaQuery()
|
.eq(DmsJiluDayinShenqingZixiangEntity::getForeignId, requestId)
|
.isNull(DmsJiluDayinShenqingZixiangEntity::getDeleteMark));
|
if (rows.isEmpty()) {
|
throw new DataException("记录打印申请未关联打印子项,无法授权");
|
}
|
return rows;
|
}
|
|
/** 提取子项业务版本 ID,并按原顺序去重。 */
|
private Set<String> extractVersionIds(List<DmsJiluDayinShenqingZixiangEntity> items) {
|
Set<String> ids = new LinkedHashSet<>();
|
for (DmsJiluDayinShenqingZixiangEntity item : items) {
|
ids.add(requireValue(item.getBanbenId(), "记录打印子项版本ID(banben_id)"));
|
}
|
return ids;
|
}
|
|
/** 批量查询未删除的业务文件版本。 */
|
private List<DmsWenjianBanbenEntity> findBusinessVersions(Set<String> versionIds) {
|
List<DmsWenjianBanbenEntity> rows = banbenMapper.selectList(
|
Wrappers.<DmsWenjianBanbenEntity>lambdaQuery()
|
.in(DmsWenjianBanbenEntity::getId, versionIds)
|
.isNull(DmsWenjianBanbenEntity::getDeleteMark));
|
if (rows.size() != versionIds.size()) {
|
throw new DataException("部分记录打印子项未找到对应的有效文件版本");
|
}
|
return rows;
|
}
|
|
/** 组装申请人的永久文件打印授权请求。 */
|
private DmsWorkflowPrintFilesGrantForm buildPrintGrantForm(
|
DmsJiluDayinShenqingEntity request, List<DmsWenjianBanbenEntity> versions) {
|
List<DmsPersonalFolderFileForm.FileRelation> files = new ArrayList<>(versions.size());
|
for (DmsWenjianBanbenEntity version : versions) {
|
DmsPersonalFolderFileForm.FileRelation relation =
|
new DmsPersonalFolderFileForm.FileRelation();
|
try {
|
relation.setDocumentId(UUID.fromString(requireValue(
|
version.getWenjianZhongxinWenjianId(), "文件中心文件ID")));
|
relation.setFileVersionId(UUID.fromString(requireValue(
|
version.getWenjianZhongxinBanbenId(), "文件中心版本ID")));
|
} catch (IllegalArgumentException exception) {
|
throw new DataException("文件中心文件ID或版本ID格式不正确:banben_id="
|
+ version.getId());
|
}
|
relation.setValidTo(null);
|
files.add(relation);
|
}
|
DmsWorkflowPrintFilesGrantForm form = new DmsWorkflowPrintFilesGrantForm();
|
form.setUserId(requireValue(request.getShenqingren(), "记录打印申请人(shenqingren)"));
|
form.setFiles(files);
|
return form;
|
}
|
|
/** 校验必填文本并返回去除首尾空白后的值。 */
|
private String requireValue(String value, String fieldName) {
|
if (isBlank(value)) {
|
throw new DataException(fieldName + "不能为空");
|
}
|
return value.trim();
|
}
|
|
/** 判断文本是否为空。 */
|
private boolean isBlank(String value) {
|
return value == null || value.trim().isEmpty();
|
}
|
|
/**
|
* 校验整批流水后,使用同一时间完成发放和接收信息更新。
|
*
|
* @param form 电子签名及发放明细
|
* @throws DataException 参数无效、流水不存在或条码已经发放
|
*/
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
@AuditLog(
|
action = "FAFANG",
|
label = "记录打印发放",
|
failureAction = "FAFANG_FAIL",
|
failureLabel = "记录打印发放失败",
|
bizModule = "记录打印发放",
|
bizType = "DMS_RECORD_PRINT_FAFANG",
|
bizCodeExpr = "#p0.data[0].fId",
|
bizCodeRequired = false,
|
targetTable = "dms_jilu_dayin_liushui",
|
targetIdExpr = "#p0.data[0].fId",
|
targetIdsExpr = "#p0.data.![fId]",
|
diff = true,
|
entityClass = DmsJiluDayinLiushuiEntity.class
|
)
|
public void issue(DmsRecordPrintIssueForm form) throws DataException {
|
Map<String, DmsRecordPrintIssueItemForm> items = resolveIssueItems(form);
|
List<DmsJiluDayinLiushuiEntity> records = mapper.selectList(
|
Wrappers.<DmsJiluDayinLiushuiEntity>lambdaQuery()
|
.select(DmsJiluDayinLiushuiEntity::getId,
|
DmsJiluDayinLiushuiEntity::getLiushuihao,
|
DmsJiluDayinLiushuiEntity::getFafangren,
|
DmsJiluDayinLiushuiEntity::getDayinZixiangId)
|
.in(DmsJiluDayinLiushuiEntity::getLiushuihao, items.keySet())
|
.isNull(DmsJiluDayinLiushuiEntity::getDeleteMark));
|
validateIssueRecords(items.keySet(), records);
|
|
Date now = new Date();
|
Map<String, Integer> issuedCounts = new LinkedHashMap<>();
|
for (DmsJiluDayinLiushuiEntity record : records) {
|
String printItemId = requireValue(record.getDayinZixiangId(), "打印申请子项ID");
|
DmsRecordPrintIssueItemForm item = items.get(record.getLiushuihao());
|
LambdaUpdateWrapper<DmsJiluDayinLiushuiEntity> update =
|
Wrappers.<DmsJiluDayinLiushuiEntity>lambdaUpdate()
|
.eq(DmsJiluDayinLiushuiEntity::getId, record.getId())
|
.isNull(DmsJiluDayinLiushuiEntity::getDeleteMark)
|
.and(wrapper -> wrapper
|
.isNull(DmsJiluDayinLiushuiEntity::getFafangren)
|
.or()
|
.eq(DmsJiluDayinLiushuiEntity::getFafangren, ""))
|
.set(DmsJiluDayinLiushuiEntity::getFafangren,
|
item.getFafangren().trim())
|
.set(DmsJiluDayinLiushuiEntity::getFafangBumen,
|
item.getFafangBumen().trim())
|
.set(DmsJiluDayinLiushuiEntity::getJieshouren,
|
item.getJieshouren().trim())
|
.set(DmsJiluDayinLiushuiEntity::getJieshouBumen,
|
item.getJieshouBumen().trim())
|
.set(DmsJiluDayinLiushuiEntity::getFafangShijian, now)
|
.set(DmsJiluDayinLiushuiEntity::getJieshouShijian, now);
|
if (mapper.update(null, update) != 1) {
|
throw new DataException("该条码已签收或发放请求重复操作");
|
}
|
issuedCounts.merge(printItemId, 1, Integer::sum);
|
}
|
for (String printItemId : new TreeSet<>(issuedCounts.keySet())) {
|
DmsJiluDayinShenqingZixiangEntity printItem =
|
mapper.selectPrintItemForUpdate(printItemId);
|
if (printItem == null) {
|
throw new DataException("未找到有效的记录打印申请子项:" + printItemId);
|
}
|
int total = parsePositiveInt(printItem.getDayinFenshu(), "打印份数");
|
int issued = parseNonNegativeInt(printItem.getYiqianfaShuliang(), "已签发数量");
|
try {
|
issued = Math.addExact(issued, issuedCounts.get(printItemId));
|
} catch (ArithmeticException exception) {
|
throw new DataException("已签发数量超出允许范围:" + printItemId);
|
}
|
DmsJiluDayinShenqingZixiangEntity updated =
|
new DmsJiluDayinShenqingZixiangEntity();
|
updated.setYiqianfaShuliang(String.valueOf(issued));
|
updated.setWeiqianfaShuliang(String.valueOf(total - issued));
|
if (itemMapper.update(updated,
|
Wrappers.<DmsJiluDayinShenqingZixiangEntity>lambdaUpdate()
|
.eq(DmsJiluDayinShenqingZixiangEntity::getId, printItemId)
|
.isNull(DmsJiluDayinShenqingZixiangEntity::getDeleteMark)) != 1) {
|
throw new DataException("更新记录打印申请子项发放数量失败:" + printItemId);
|
}
|
}
|
}
|
|
/**
|
* 校验整批流水后,使用同一时间完成归还和回收信息更新。
|
*
|
* @param form 电子签名及回收明细
|
* @throws DataException 参数无效、流水不存在或条码已经回收
|
*/
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
@AuditLog(
|
action = "HUISHOU",
|
label = "记录打印回收",
|
failureAction = "HUIUSHOU_FAIL",
|
failureLabel = "记录打印回收失败",
|
bizModule = "记录打印回收",
|
bizType = "DMS_RECORD_PRINT_HUISHOU",
|
bizCodeExpr = "#p0.data[0].fId",
|
bizCodeRequired = false,
|
targetTable = "dms_jilu_dayin_liushui",
|
targetIdExpr = "#p0.data[0].fId",
|
targetIdsExpr = "#p0.data.![fId]",
|
diff = true,
|
entityClass = DmsJiluDayinLiushuiEntity.class
|
)
|
public void huishou(DmsRecordPrintHuishouForm form) throws DataException {
|
Map<String, DmsRecordPrintHuishouItemForm> items = resolveHuishouItems(form);
|
List<DmsJiluDayinLiushuiEntity> records = mapper.selectList(
|
Wrappers.<DmsJiluDayinLiushuiEntity>lambdaQuery()
|
.select(DmsJiluDayinLiushuiEntity::getId,
|
DmsJiluDayinLiushuiEntity::getLiushuihao,
|
DmsJiluDayinLiushuiEntity::getGuihuanren,
|
DmsJiluDayinLiushuiEntity::getDayinZixiangId)
|
.in(DmsJiluDayinLiushuiEntity::getLiushuihao, items.keySet())
|
.isNull(DmsJiluDayinLiushuiEntity::getDeleteMark));
|
validateIssueRecords(items.keySet(), records);
|
|
Date now = new Date();
|
Map<String, Integer> recoveredCounts = new LinkedHashMap<>();
|
for (DmsJiluDayinLiushuiEntity record : records) {
|
String printItemId = requireValue(record.getDayinZixiangId(), "打印申请子项ID");
|
DmsRecordPrintHuishouItemForm item = items.get(record.getLiushuihao());
|
LambdaUpdateWrapper<DmsJiluDayinLiushuiEntity> update =
|
Wrappers.<DmsJiluDayinLiushuiEntity>lambdaUpdate()
|
.eq(DmsJiluDayinLiushuiEntity::getId, record.getId())
|
.isNull(DmsJiluDayinLiushuiEntity::getDeleteMark)
|
.and(wrapper -> wrapper
|
.isNull(DmsJiluDayinLiushuiEntity::getGuihuanren)
|
.or()
|
.eq(DmsJiluDayinLiushuiEntity::getGuihuanren, ""))
|
.set(DmsJiluDayinLiushuiEntity::getGuihuanren,
|
item.getGuihuanren())
|
.set(DmsJiluDayinLiushuiEntity::getGuihuanBumen,
|
item.getGuihuanBumen())
|
.set(DmsJiluDayinLiushuiEntity::getHuishouren,
|
item.getHuishouren())
|
.set(DmsJiluDayinLiushuiEntity:: getHuishouBumen,
|
item.getHuishouBumen())
|
.set(DmsJiluDayinLiushuiEntity:: getHuishouYuanyin,
|
item.getHuishouYuanyin())
|
.set(DmsJiluDayinLiushuiEntity:: getHuishouLeixing,
|
item.getHuishouLeixing())
|
.set(DmsJiluDayinLiushuiEntity::getGuihuanShijian, now)
|
.set(DmsJiluDayinLiushuiEntity::getHuishouShijian, now);
|
if (mapper.update(null, update) != 1) {
|
throw new DataException("该条码已回收请求重复操作");
|
}
|
recoveredCounts.merge(printItemId, 1, Integer::sum);
|
}
|
for (String printItemId : new TreeSet<>(recoveredCounts.keySet())) {
|
DmsJiluDayinShenqingZixiangEntity printItem =
|
mapper.selectPrintItemForUpdate(printItemId);
|
if (printItem == null) {
|
throw new DataException("未找到有效的记录打印申请子项:" + printItemId);
|
}
|
int recovered = parseNonNegativeInt(printItem.getYihuishouShuliang(), "已回收数量");
|
try {
|
recovered = Math.addExact(recovered, recoveredCounts.get(printItemId));
|
} catch (ArithmeticException exception) {
|
throw new DataException("已回收数量超出允许范围:" + printItemId);
|
}
|
DmsJiluDayinShenqingZixiangEntity updated =
|
new DmsJiluDayinShenqingZixiangEntity();
|
updated.setYihuishouShuliang(String.valueOf(recovered));
|
if (itemMapper.update(updated,
|
Wrappers.<DmsJiluDayinShenqingZixiangEntity>lambdaUpdate()
|
.eq(DmsJiluDayinShenqingZixiangEntity::getId, printItemId)
|
.isNull(DmsJiluDayinShenqingZixiangEntity::getDeleteMark)) != 1) {
|
throw new DataException("更新记录打印申请子项回收数量失败:" + printItemId);
|
}
|
}
|
}
|
|
/**
|
* 校验请求及签名,并按流水号整理发放明细。
|
*
|
* @param form 发放请求
|
* @return 流水号到发放明细的有序映射
|
* @throws DataException 必填字段为空或流水号重复
|
*/
|
private Map<String, DmsRecordPrintIssueItemForm> resolveIssueItems(
|
DmsRecordPrintIssueForm form) throws DataException {
|
if (form == null) {
|
throw new DataException("发放参数不能为空");
|
}
|
if (form.getData() == null || form.getData().isEmpty()) {
|
throw new DataException("发放数据不能为空");
|
}
|
Map<String, DmsRecordPrintIssueItemForm> items = new LinkedHashMap<>();
|
for (DmsRecordPrintIssueItemForm item : form.getData()) {
|
if (item == null) {
|
throw new DataException("发放数据中不能包含空记录");
|
}
|
String serialNumber = requireText(item.getLiushuihao(), "流水号不能为空");
|
requireText(item.getFafangren(), "发放人不能为空");
|
requireText(item.getFafangBumen(), "发放部门不能为空");
|
requireText(item.getJieshouren(), "接收人不能为空");
|
requireText(item.getJieshouBumen(), "接收部门不能为空");
|
item.setLiushuihao(serialNumber);
|
if (items.put(serialNumber, item) != null) {
|
throw new DataException("发放数据中不能包含重复流水号");
|
}
|
}
|
return items;
|
}
|
|
/**
|
* 校验请求及签名,并按流水号整理回收明细。
|
*
|
* @param form 回收请求
|
* @return 流水号到回收明细的有序映射
|
* @throws DataException 必填字段为空或流水号重复
|
*/
|
private Map<String, DmsRecordPrintHuishouItemForm> resolveHuishouItems(
|
DmsRecordPrintHuishouForm form) throws DataException {
|
if (form == null) {
|
throw new DataException("发放参数不能为空");
|
}
|
if (form.getData() == null || form.getData().isEmpty()) {
|
throw new DataException("发放数据不能为空");
|
}
|
Map<String, DmsRecordPrintHuishouItemForm> items = new LinkedHashMap<>();
|
for (DmsRecordPrintHuishouItemForm item : form.getData()) {
|
if (item == null) {
|
throw new DataException("发放数据中不能包含空记录");
|
}
|
String serialNumber = requireText(item.getLiushuihao(), "流水号不能为空");
|
requireText(item.getGuihuanren(), "归还人人不能为空");
|
requireText(item.getGuihuanBumen(), "归还部门不能为空");
|
requireText(item.getHuishouren(), "回收人不能为空");
|
requireText(item.getHuishouBumen(), "回收部门不能为空");
|
item.setLiushuihao(serialNumber);
|
if (items.put(serialNumber, item) != null) {
|
throw new DataException("回收数据中不能包含重复流水号");
|
}
|
}
|
return items;
|
}
|
|
/**
|
* 校验所有请求流水均存在且尚未发放。
|
*
|
* @param serialNumbers 请求流水号
|
* @param records 查询出的 A 表实体
|
* @throws DataException 存在无效流水或已经发放的条码
|
*/
|
private void validateIssueRecords(Set<String> serialNumbers,
|
List<DmsJiluDayinLiushuiEntity> records)
|
throws DataException {
|
Set<String> foundSerialNumbers = new LinkedHashSet<>();
|
for (DmsJiluDayinLiushuiEntity record : records) {
|
foundSerialNumbers.add(record.getLiushuihao());
|
if (!trim(record.getFafangren()).isEmpty()) {
|
throw new DataException("该条码已签收或发放请勿重复操作");
|
}
|
}
|
if (records.size() != serialNumbers.size()
|
|| !foundSerialNumbers.equals(serialNumbers)) {
|
throw new DataException("存在未找到或已删除的打印流水号,无法发放");
|
}
|
}
|
|
/**
|
* 根据流水号读取 A 表实体,校验发放状态后读取对应 B 表实体。
|
*
|
* @param serialNumber 打印流水号
|
* @return 流水记录及文件信息
|
* @throws DataException 流水号为空、记录不存在或已经签收/发放
|
*/
|
@Override
|
public DmsRecordPrintSerialVO findBySerialNumber(String serialNumber, String chazhaoLeixing) throws DataException {
|
String resolvedSerialNumber = requireText(serialNumber, "流水号不能为空");
|
DmsJiluDayinLiushuiEntity record = mapper.selectOne(
|
Wrappers.<DmsJiluDayinLiushuiEntity>lambdaQuery()
|
.select(DmsJiluDayinLiushuiEntity::getId,
|
DmsJiluDayinLiushuiEntity::getDayinZixiangId,
|
DmsJiluDayinLiushuiEntity::getLiushuihao,
|
DmsJiluDayinLiushuiEntity::getFafangren,
|
DmsJiluDayinLiushuiEntity::getHuishouren,
|
DmsJiluDayinLiushuiEntity::getJieshouren,
|
DmsJiluDayinLiushuiEntity::getJieshouBumen)
|
.eq(DmsJiluDayinLiushuiEntity::getLiushuihao,
|
resolvedSerialNumber)
|
.isNull(DmsJiluDayinLiushuiEntity::getDeleteMark));
|
if (record == null) {
|
throw new DataException("未找到该流水号对应的条码信息");
|
}
|
if (!trim(record.getFafangren()).isEmpty() && !CHAZHAO_LEIXING_HUISHOU.equals(chazhaoLeixing)) {
|
throw new DataException("该条码已签收或发放请勿重复操作");
|
}
|
if (!trim(record.getHuishouren()).isEmpty() && CHAZHAO_LEIXING_HUISHOU.equals(chazhaoLeixing)) {
|
throw new DataException("该条码已回签收请勿重复操作");
|
}
|
|
DmsJiluDayinShenqingZixiangEntity item = itemMapper.selectOne(
|
Wrappers.<DmsJiluDayinShenqingZixiangEntity>lambdaQuery()
|
.select(DmsJiluDayinShenqingZixiangEntity::getId,
|
DmsJiluDayinShenqingZixiangEntity::getWenjianMingcheng,
|
DmsJiluDayinShenqingZixiangEntity::getWenjianBianhao,
|
DmsJiluDayinShenqingZixiangEntity::getBanbenhao)
|
.eq(DmsJiluDayinShenqingZixiangEntity::getId,
|
record.getDayinZixiangId())
|
.isNull(DmsJiluDayinShenqingZixiangEntity::getDeleteMark));
|
if (item == null) {
|
throw new DataException("未找到该流水号对应的打印申请子项");
|
}
|
|
DmsRecordPrintSerialVO result = new DmsRecordPrintSerialVO();
|
result.setLiushuihao(record.getLiushuihao());
|
result.setId(record.getId());
|
result.setWenjianMingcheng(item.getWenjianMingcheng());
|
result.setWenjianBianhao(item.getWenjianBianhao());
|
result.setBanbenhao(item.getBanbenhao());
|
result.setJieshouren(record.getJieshouren());
|
result.setJieshouBumen(record.getJieshouBumen());
|
return result;
|
}
|
|
/**
|
* 锁定打印申请、计算打印单元并预占流水号,不写入打印业务表。
|
*
|
* @param printItemId B 表打印申请子项主键
|
* @return 本次唯一打印内容所需的流水号映射
|
* @throws DataException 任一步业务校验或持久化失败
|
*/
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public DmsRecordPrintPrepareVO prepare(String printItemId) throws DataException {
|
String resolvedId = requireText(printItemId, "打印申请子项ID不能为空");
|
DmsJiluDayinShenqingZixiangEntity item = mapper.selectPrintItemForUpdate(resolvedId);
|
validatePrintable(item, resolvedId);
|
|
UserInfo user = identity.requireUser();
|
String userId = requireText(user.getUserId(), "当前登录用户ID不能为空");
|
String tenantId = identity.tenantId(user);
|
DmsRecordPrintReservation existing = reservationStore.findByPrintItem(
|
tenantId, resolvedId);
|
if (existing != null) {
|
validateReservationIdentity(existing, tenantId, userId);
|
validateReservationVersion(existing, item);
|
if (!DmsRecordPrintReservation.STATUS_PENDING.equals(existing.getStatus())
|
|| existing.getExpiresAt() <= System.currentTimeMillis()) {
|
throw new DataException("打印预占状态无效或已过期,请重新预览");
|
}
|
return buildPrepareResult(existing);
|
}
|
|
String printType = trim(item.getDayinLeixing());
|
int printCount = parsePositiveInt(item.getDayinFenshu(), "打印份数");
|
List<Integer> selectedPages = resolveSelectedPages(item, tenantId);
|
int serialCount = calculateSerialCount(printType, printCount, selectedPages);
|
List<String> serialNumbers = allocateSerialNumbers(serialCount);
|
|
long ttlMillis = reservationTtlMillis();
|
long now = System.currentTimeMillis();
|
DmsRecordPrintReservation reservation = new DmsRecordPrintReservation();
|
reservation.setReservationId(UUID.randomUUID().toString());
|
reservation.setTenantId(tenantId);
|
reservation.setUserId(userId);
|
reservation.setPrintItemId(item.getId());
|
reservation.setItemVersion(item.getVersion());
|
reservation.setPrintType(printType);
|
reservation.setPrintCount(printCount);
|
reservation.setSelectedPages(new ArrayList<>(selectedPages));
|
reservation.setSerialNumbers(new ArrayList<>(serialNumbers));
|
reservation.setStatus(DmsRecordPrintReservation.STATUS_PENDING);
|
reservation.setCreatedAt(now);
|
reservation.setExpiresAt(now + ttlMillis);
|
reservationStore.create(reservation, ttlMillis);
|
return buildPrepareResult(reservation);
|
}
|
|
/**
|
* 将预览阶段预占的流水号写入 A 表,并更新 B 表打印状态。
|
*/
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public DmsRecordPrintConfirmVO confirm(String reservationId) throws DataException {
|
String resolvedReservationId = requireText(
|
reservationId, "打印预占标识不能为空");
|
UserInfo user = identity.requireUser();
|
String userId = requireText(user.getUserId(), "当前登录用户ID不能为空");
|
String tenantId = identity.tenantId(user);
|
DmsRecordPrintReservation reservation = reservationStore.findById(
|
tenantId, resolvedReservationId);
|
if (reservation == null || reservation.getExpiresAt() <= System.currentTimeMillis()) {
|
throw new DataException("打印预占不存在或已过期,请重新预览");
|
}
|
validateReservationIdentity(reservation, tenantId, userId);
|
if (DmsRecordPrintReservation.STATUS_CONFIRMED.equals(reservation.getStatus())) {
|
return buildConfirmResult(reservation);
|
}
|
if (!DmsRecordPrintReservation.STATUS_PENDING.equals(reservation.getStatus())) {
|
throw new DataException("打印预占状态无效,请重新预览");
|
}
|
|
DmsJiluDayinShenqingZixiangEntity item = mapper.selectPrintItemForUpdate(
|
reservation.getPrintItemId());
|
if (item == null) {
|
throw new DataException("打印申请子项不存在");
|
}
|
List<DmsJiluDayinLiushuiEntity> existingRecords = findActivePrintRecords(item.getId());
|
if (!existingRecords.isEmpty()) {
|
DmsRecordPrintConfirmVO confirmed = resolveConfirmedRetry(
|
reservation, existingRecords, userId);
|
markConfirmedAfterCommit(reservation);
|
return confirmed;
|
}
|
|
validatePrintable(item, item.getId());
|
validateReservationVersion(reservation, item);
|
validateReservationLayout(reservation, item, tenantId);
|
|
Date printTime = new Date();
|
List<DmsJiluDayinLiushuiEntity> records = buildRecords(
|
item, reservation.getSerialNumbers(), userId, printTime);
|
int inserted = mapper.insertPrintRecords(records);
|
if (inserted != records.size()) {
|
throw new DataException("生成打印流水记录失败");
|
}
|
markPrintItemProcessing(item, userId, printTime);
|
reservation.setRecordIds(recordIds(records));
|
reservation.setStatus(DmsRecordPrintReservation.STATUS_CONFIRMED);
|
markConfirmedAfterCommit(reservation);
|
return buildConfirmResult(reservation);
|
}
|
|
/**
|
* 校验打印申请存在、未打印且数据状态一致。
|
*
|
* @param item B 表实体
|
* @param printItemId 打印申请子项主键
|
* @throws DataException 申请不存在、类型无效或已经打印
|
*/
|
private void validatePrintable(DmsJiluDayinShenqingZixiangEntity item, String printItemId)
|
throws DataException {
|
if (item == null) {
|
throw new DataException("打印申请子项不存在");
|
}
|
String printType = trim(item.getDayinLeixing());
|
if (!PRINT_TYPE_BOUND.equals(printType) && !PRINT_TYPE_LOOSE.equals(printType)) {
|
throw new DataException("打印类型无效:" + printType);
|
}
|
int activeRecords = mapper.countActivePrintRecords(printItemId);
|
if (activeRecords > 0) {
|
throw new DataException("该记录已经打印,不允许重复打印");
|
}
|
int printedCount = parseNonNegativeInt(item.getYidayinFenshu(), "已打印份数");
|
if (printedCount > 0 || PRINT_STATUS_PROCESSING.equals(trim(item.getDayinZhuangtai()))) {
|
throw new DataException("该记录已经打印,不允许重复打印");
|
}
|
}
|
|
/**
|
* 按打印类型解析分散打印页码,成册打印返回空集合。
|
*
|
* @param item B 表实体
|
* @return 分散打印有序页码集合
|
* @throws DataException 文件页数或页码集合无效
|
*/
|
private List<Integer> resolveSelectedPages(DmsJiluDayinShenqingZixiangEntity item,
|
String tenantId)
|
throws DataException {
|
if (PRINT_TYPE_BOUND.equals(trim(item.getDayinLeixing()))) {
|
return Collections.emptyList();
|
}
|
String banbenId = requireText(item.getBanbenId(), "文件版本ID不能为空");
|
DmsWenjianBanbenEntity banben = banbenMapper.selectById(tenantId, banbenId);
|
if (banben == null) {
|
throw new DataException("未找到文件版本:" + banbenId);
|
}
|
String fileCenterVersionId = requireText(
|
banben.getWenjianZhongxinBanbenId(), "文件版本缺少文件中心版本ID");
|
try {
|
DmsFilePageCountVO pageCount = pageCountService.getPageCount(
|
UUID.fromString(fileCenterVersionId));
|
return pageParser.parse(item.getYema(), pageCount.getPageCount());
|
} catch (IllegalArgumentException exception) {
|
throw new DataException("文件中心版本ID格式不正确:" + fileCenterVersionId);
|
} catch (IOException exception) {
|
throw new DataException("获取文件总页数失败");
|
}
|
}
|
|
/**
|
* 根据打印类型计算需要生成的流水号总数并校验上限。
|
*
|
* @param printType 打印类型
|
* @param printCount 打印份数
|
* @param selectedPages 分散打印有序页码集合
|
* @return 流水号总数
|
* @throws DataException 数量溢出或超过配置上限
|
*/
|
private int calculateSerialCount(String printType, int printCount,
|
List<Integer> selectedPages) throws DataException {
|
final int serialCount;
|
try {
|
serialCount = PRINT_TYPE_BOUND.equals(printType)
|
? printCount : Math.multiplyExact(printCount, selectedPages.size());
|
} catch (ArithmeticException exception) {
|
throw new DataException("打印流水号数量超出允许范围");
|
}
|
int maxSerialCount = properties.getMaxSerialCount();
|
if (maxSerialCount <= 0) {
|
throw new DataException("打印流水号数量上限配置无效");
|
}
|
if (serialCount <= 0 || serialCount > maxSerialCount) {
|
throw new DataException("打印流水号数量必须在1至" + maxSerialCount + "之间");
|
}
|
return serialCount;
|
}
|
|
/**
|
* 在 DMS 事务锁保护下调用单据规则接口生成本批全部流水号。
|
*
|
* @param count 流水号数量
|
* @return 按生成顺序排列的流水号
|
* @throws DataException 锁获取异常、规则不可用或返回重复号码
|
*/
|
private List<String> allocateSerialNumbers(int count) throws DataException {
|
Integer locked = mapper.lockSerialAllocation();
|
if (locked == null || locked != 1) {
|
throw new DataException("获取记录打印流水号批量取号锁失败");
|
}
|
String ruleCode = requireText(properties.getBillRuleCode(), "记录打印流水号规则编码不能为空");
|
List<String> numbers = new ArrayList<>(count);
|
Set<String> uniqueNumbers = new HashSet<>(count);
|
for (int index = 0; index < count; index++) {
|
ActionResult<String> result = billRuleApi.getBillNumber(ruleCode);
|
String number = result == null ? "" : trim(result.getData());
|
if (number.isEmpty()) {
|
throw new DataException("打印流水号取号失败:单据规则[" + ruleCode + "]不可用");
|
}
|
if (!uniqueNumbers.add(number)) {
|
throw new DataException("打印流水号规则返回重复号码:" + number);
|
}
|
numbers.add(number);
|
}
|
return numbers;
|
}
|
|
/**
|
* 使用实体构造本次全部 A 表打印流水记录。
|
*
|
* @param item B 表实体
|
* @param serialNumbers 流水号集合
|
* @param printUserId 打印人
|
* @param printTime 本批统一打印时间
|
* @return A 表实体集合
|
* @throws DataException 打印申请主表主键为空
|
*/
|
private List<DmsJiluDayinLiushuiEntity> buildRecords(
|
DmsJiluDayinShenqingZixiangEntity item, List<String> serialNumbers,
|
String printUserId, Date printTime) throws DataException {
|
String printId = requireText(item.getForeignId(), "打印申请主表ID不能为空");
|
List<DmsJiluDayinLiushuiEntity> records = new ArrayList<>(serialNumbers.size());
|
for (String serialNumber : serialNumbers) {
|
DmsJiluDayinLiushuiEntity record = new DmsJiluDayinLiushuiEntity();
|
record.setId(IdUtil.getSnowflakeNextIdStr());
|
record.setDayinId(printId);
|
record.setDayinZixiangId(item.getId());
|
record.setLiushuihao(serialNumber);
|
record.setDayinren(printUserId);
|
record.setDayinShijian(printTime);
|
record.setDeleteMark(null);
|
records.add(record);
|
}
|
return records;
|
}
|
|
/**
|
* 使用 B 表实体统一回写打印人、打印时间和打印中状态。
|
*
|
* @param item B 表实体
|
* @param printUserId 打印人
|
* @param printTime 与 A 表相同的打印时间
|
* @throws DataException B 表状态未按预期更新
|
*/
|
private void markPrintItemProcessing(DmsJiluDayinShenqingZixiangEntity item,
|
String printUserId, Date printTime)
|
throws DataException {
|
item.setDayinren(printUserId);
|
item.setDayinShijian(printTime);
|
item.setDayinZhuangtai(PRINT_STATUS_PROCESSING);
|
item.setYidayinFenshu(item.getDayinFenshu());
|
if (mapper.markPrintItemProcessing(item) != 1) {
|
throw new DataException("更新打印申请子项状态失败");
|
}
|
}
|
|
/**
|
* 按稳定顺序构造不含数据库记录主键的打印预览结果。
|
*/
|
private DmsRecordPrintPrepareVO buildPrepareResult(
|
DmsRecordPrintReservation reservation) {
|
DmsRecordPrintPrepareVO result = new DmsRecordPrintPrepareVO();
|
result.setReservationId(reservation.getReservationId());
|
result.setExpiresAt(new Date(reservation.getExpiresAt()));
|
result.setPrintItemId(reservation.getPrintItemId());
|
result.setPrintType(reservation.getPrintType());
|
result.setPrintCount(reservation.getPrintCount());
|
result.setSelectedPages(new ArrayList<>(reservation.getSelectedPages()));
|
result.setSerialCount(reservation.getSerialNumbers().size());
|
result.setCopies(buildCopies(reservation, false));
|
return result;
|
}
|
|
/**
|
* 构造已经写入 A 表的打印确认结果。
|
*/
|
private DmsRecordPrintConfirmVO buildConfirmResult(
|
DmsRecordPrintReservation reservation) {
|
DmsRecordPrintConfirmVO result = new DmsRecordPrintConfirmVO();
|
result.setReservationId(reservation.getReservationId());
|
result.setPrintItemId(reservation.getPrintItemId());
|
result.setConfirmed(Boolean.TRUE);
|
result.setCopies(buildCopies(reservation, true));
|
return result;
|
}
|
|
/**
|
* 根据预占中的稳定顺序构造打印单元。
|
*/
|
private List<DmsRecordPrintCopyVO> buildCopies(
|
DmsRecordPrintReservation reservation, boolean includeRecordIds) {
|
List<String> serialNumbers = reservation.getSerialNumbers();
|
List<String> recordIds = reservation.getRecordIds();
|
if (includeRecordIds && (recordIds == null
|
|| recordIds.size() != serialNumbers.size())) {
|
throw new DataException("打印确认结果缺少数据库记录主键");
|
}
|
|
List<DmsRecordPrintCopyVO> copies = new ArrayList<>(serialNumbers.size());
|
int recordIndex = 0;
|
for (int copyIndex = 1; copyIndex <= reservation.getPrintCount(); copyIndex++) {
|
if (PRINT_TYPE_BOUND.equals(reservation.getPrintType())) {
|
copies.add(toCopy(includeRecordIds ? recordIds.get(recordIndex) : null,
|
serialNumbers.get(recordIndex++), copyIndex, null));
|
continue;
|
}
|
for (Integer pageNo : reservation.getSelectedPages()) {
|
copies.add(toCopy(includeRecordIds ? recordIds.get(recordIndex) : null,
|
serialNumbers.get(recordIndex++), copyIndex, pageNo));
|
}
|
}
|
if (recordIndex != serialNumbers.size()) {
|
throw new DataException("打印预占的流水号数量与打印单元数量不一致");
|
}
|
return copies;
|
}
|
|
/**
|
* 构造单个打印单元响应。
|
*/
|
private DmsRecordPrintCopyVO toCopy(String recordId, String serialNumber,
|
int copyIndex, Integer pageNo) {
|
DmsRecordPrintCopyVO copy = new DmsRecordPrintCopyVO();
|
copy.setRecordId(recordId);
|
copy.setCopyIndex(copyIndex);
|
copy.setPageNo(pageNo);
|
copy.setSerialNo(serialNumber);
|
return copy;
|
}
|
|
/**
|
* 校验预占只能由创建它的租户和用户确认。
|
*/
|
private void validateReservationIdentity(DmsRecordPrintReservation reservation,
|
String tenantId, String userId)
|
throws DataException {
|
if (!Objects.equals(tenantId, reservation.getTenantId())) {
|
throw new DataException("打印预占不属于当前租户");
|
}
|
if (!Objects.equals(userId, reservation.getUserId())) {
|
throw new DataException("该打印申请正在被其他用户预览");
|
}
|
}
|
|
/**
|
* 校验预览后打印申请没有被修改。
|
*/
|
private void validateReservationVersion(DmsRecordPrintReservation reservation,
|
DmsJiluDayinShenqingZixiangEntity item)
|
throws DataException {
|
if (!Objects.equals(reservation.getItemVersion(), item.getVersion())) {
|
throw new DataException("打印申请在预览后已经变更,请重新预览");
|
}
|
}
|
|
/**
|
* 重新计算打印布局,防止预览后参数发生变化。
|
*/
|
private void validateReservationLayout(DmsRecordPrintReservation reservation,
|
DmsJiluDayinShenqingZixiangEntity item,
|
String tenantId) throws DataException {
|
int printCount = parsePositiveInt(item.getDayinFenshu(), "打印份数");
|
List<Integer> selectedPages = resolveSelectedPages(item, tenantId);
|
String printType = trim(item.getDayinLeixing());
|
int serialCount = calculateSerialCount(printType, printCount, selectedPages);
|
if (!Objects.equals(reservation.getPrintType(), printType)
|
|| !Objects.equals(reservation.getPrintCount(), printCount)
|
|| !Objects.equals(reservation.getSelectedPages(), selectedPages)
|
|| reservation.getSerialNumbers() == null
|
|| reservation.getSerialNumbers().size() != serialCount
|
|| new HashSet<>(reservation.getSerialNumbers()).size() != serialCount) {
|
throw new DataException("打印参数在预览后已经变更,请重新预览");
|
}
|
for (String serialNumber : reservation.getSerialNumbers()) {
|
requireText(serialNumber, "打印预占中存在空流水号");
|
}
|
}
|
|
/**
|
* 查询申请已经写入的有效打印流水,用于确认请求幂等恢复。
|
*/
|
private List<DmsJiluDayinLiushuiEntity> findActivePrintRecords(String printItemId) {
|
List<DmsJiluDayinLiushuiEntity> records = mapper.selectList(
|
Wrappers.<DmsJiluDayinLiushuiEntity>lambdaQuery()
|
.select(DmsJiluDayinLiushuiEntity::getId,
|
DmsJiluDayinLiushuiEntity::getLiushuihao,
|
DmsJiluDayinLiushuiEntity::getDayinren)
|
.eq(DmsJiluDayinLiushuiEntity::getDayinZixiangId, printItemId)
|
.isNull(DmsJiluDayinLiushuiEntity::getDeleteMark));
|
return records == null ? Collections.<DmsJiluDayinLiushuiEntity>emptyList() : records;
|
}
|
|
/**
|
* 已存在完全相同的一批流水时,将网络重试恢复为成功响应。
|
*/
|
private DmsRecordPrintConfirmVO resolveConfirmedRetry(
|
DmsRecordPrintReservation reservation,
|
List<DmsJiluDayinLiushuiEntity> existingRecords, String userId)
|
throws DataException {
|
Map<String, DmsJiluDayinLiushuiEntity> bySerial = new LinkedHashMap<>();
|
for (DmsJiluDayinLiushuiEntity record : existingRecords) {
|
if (!Objects.equals(userId, record.getDayinren())
|
|| bySerial.put(record.getLiushuihao(), record) != null) {
|
throw new DataException("该记录已经打印,不允许重复打印");
|
}
|
}
|
List<String> recordIds = new ArrayList<>();
|
for (String serialNumber : reservation.getSerialNumbers()) {
|
DmsJiluDayinLiushuiEntity record = bySerial.remove(serialNumber);
|
if (record == null) {
|
throw new DataException("该记录已经使用其他流水号打印,不允许重复打印");
|
}
|
recordIds.add(record.getId());
|
}
|
if (!bySerial.isEmpty()) {
|
throw new DataException("该记录已经使用其他流水号打印,不允许重复打印");
|
}
|
reservation.setRecordIds(recordIds);
|
reservation.setStatus(DmsRecordPrintReservation.STATUS_CONFIRMED);
|
return buildConfirmResult(reservation);
|
}
|
|
private List<String> recordIds(List<DmsJiluDayinLiushuiEntity> records) {
|
List<String> ids = new ArrayList<>(records.size());
|
for (DmsJiluDayinLiushuiEntity record : records) {
|
ids.add(record.getId());
|
}
|
return ids;
|
}
|
|
/**
|
* 数据库提交后保留一段时间的确认结果,为网络重试提供幂等响应。
|
*/
|
private void markConfirmedAfterCommit(DmsRecordPrintReservation reservation) {
|
Runnable completion = () -> {
|
try {
|
long ttlMillis = confirmedResultTtlMillis();
|
reservation.setExpiresAt(System.currentTimeMillis() + ttlMillis);
|
reservationStore.markConfirmed(reservation, ttlMillis);
|
} catch (RuntimeException exception) {
|
log.error("更新打印预占确认状态失败,reservationId={}",
|
reservation.getReservationId(), exception);
|
}
|
};
|
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
|
completion.run();
|
return;
|
}
|
TransactionSynchronizationManager.registerSynchronization(
|
new TransactionSynchronization() {
|
@Override
|
public void afterCommit() {
|
completion.run();
|
}
|
});
|
}
|
|
private long reservationTtlMillis() throws DataException {
|
int minutes = properties.getReservationTtlMinutes();
|
if (minutes <= 0) {
|
throw new DataException("打印预占有效期配置无效");
|
}
|
return TimeUnit.MINUTES.toMillis(minutes);
|
}
|
|
private long confirmedResultTtlMillis() throws DataException {
|
int hours = properties.getConfirmedResultTtlHours();
|
if (hours <= 0) {
|
throw new DataException("打印确认结果有效期配置无效");
|
}
|
return TimeUnit.HOURS.toMillis(hours);
|
}
|
|
/**
|
* 解析必须为正整数的字符串字段。
|
*
|
* @param value 原始字段值
|
* @param fieldName 字段显示名
|
* @return 正整数
|
* @throws DataException 为空、非整数或小于 1
|
*/
|
private int parsePositiveInt(String value, String fieldName) throws DataException {
|
int number = parseInteger(value, fieldName, false);
|
if (number <= 0) {
|
throw new DataException(fieldName + "必须大于0");
|
}
|
return number;
|
}
|
|
/**
|
* 解析允许为空或零的非负整数字段。
|
*
|
* @param value 原始字段值
|
* @param fieldName 字段显示名
|
* @return 非负整数,空值按 0 处理
|
* @throws DataException 非整数或小于 0
|
*/
|
private int parseNonNegativeInt(String value, String fieldName) throws DataException {
|
String resolved = trim(value);
|
if (resolved.isEmpty()) {
|
return 0;
|
}
|
int number = parseInteger(resolved, fieldName, true);
|
if (number < 0) {
|
throw new DataException(fieldName + "不能小于0");
|
}
|
return number;
|
}
|
|
/**
|
* 解析整数字符串。
|
*
|
* @param value 原始字段值
|
* @param fieldName 字段显示名
|
* @param allowEmpty 是否允许空值;空值由调用方提前处理
|
* @return 整数
|
* @throws DataException 字段为空或不是整数
|
*/
|
private int parseInteger(String value, String fieldName, boolean allowEmpty)
|
throws DataException {
|
String resolved = trim(value);
|
if (resolved.isEmpty() && !allowEmpty) {
|
throw new DataException(fieldName + "不能为空");
|
}
|
try {
|
return Integer.parseInt(resolved);
|
} catch (NumberFormatException exception) {
|
throw new DataException(fieldName + "必须为整数");
|
}
|
}
|
|
/**
|
* 校验并返回非空文本。
|
*
|
* @param value 原始值
|
* @param message 为空时的错误消息
|
* @return 去除首尾空白后的值
|
* @throws DataException 值为空
|
*/
|
private String requireText(String value, String message) throws DataException {
|
String resolved = trim(value);
|
if (resolved.isEmpty()) {
|
throw new DataException(message);
|
}
|
return resolved;
|
}
|
|
/**
|
* 将可空文本转换为去除首尾空白后的值。
|
*
|
* @param value 原始值
|
* @return 非 null 文本
|
*/
|
private String trim(String value) {
|
return value == null ? "" : value.trim();
|
}
|
}
|