穿越时空审批
指定某个日期发起审批,所有审批任务记录为该时间,使用场景请假事后补审。
测试用例
所在源码位置
test.mysql.config.TestCreateTime测试用例。
java
public class TestCreateTime extends MysqlTest {
public static final String setFlowCreateTime = "setFlowCreateTime";
@BeforeEach
public void before() {
processId = this.deployByResource("test/simpleProcess.json", testCreator);
}
@Test
public void testCreateTime() {
// 发起流程设置指定日期
FlowDataTransfer.put(setFlowCreateTime, "2025-01-10");
// 发起流程实例
flowLongEngine.startInstanceById(processId, testCreator).ifPresent(instance -> {
// 默认主管审批
this.executeTask(instance.getId(), testCreator);
// 条件路由子审批
this.executeTask(instance.getId(), testCreator);
});
}
}创建时间处理器接口
- 实现创建时间处理器接口
FlowCreateTimeHandler在生产使用根据instanceId绑定动态设定该流程下的任务审批时间指定处理。
java
public interface FlowCreateTimeHandler {
/**
* 获取当前时间,用于设置创建时间或更新时间
*
* @param executeType 工作流执行类型 {@link ExecuteType}
* @param instanceId 流程实例ID
* @param taskId 审批任务ID(不存在为流程实例调用情况)
*/
Date getCurrentTime(ExecuteType executeType, Long instanceId, Long taskId);
/**
* 获取完成时间,用于设置流程实例结束时间或任务完成时间
*
* @param instanceId 流程实例ID
* @param taskId 审批任务ID(不存在为流程实例调用情况)
*/
Date getFinishTime(Long instanceId, Long taskId);
}创建时间处理器接口实现
需要注意的是缓存需要清理,这里使用
ConcurrentHashMap缓存,使用instanceId绑定动态设定该流程下的任务审批时间指定处理,分布式可以使用redis等缓存。
java
public class TestFlowCreateTimeHandler implements FlowCreateTimeHandler {
private final Map<Long, String> instanceIdMap = new ConcurrentHashMap<>();
@Override
public Date getCurrentTime(ExecuteType executeType, Long instanceId, Long taskId) {
String setTime = null;
if (ExecuteType.process != executeType) {
// 创建实例后续逻辑
if (null != instanceId) {
setTime = instanceIdMap.get(instanceId);
}
if (null == setTime) {
setTime = FlowDataTransfer.get(TestCreateTime.setFlowCreateTime);
if (null != setTime && null != instanceId) {
// 缓存实例指定时间
instanceIdMap.put(instanceId, setTime);
// 移除传递参数
FlowDataTransfer.removeByKey(TestCreateTime.setFlowCreateTime);
}
}
}
return getSetTime(setTime);
}
@Override
public Date getFinishTime(Long instanceId, Long taskId) {
if (null == instanceId) {
return DateUtils.getCurrentDate();
}
String setTime = instanceIdMap.get(instanceId);
if (null != setTime && null == taskId) {
// 实例完成,删除缓存记录
instanceIdMap.remove(instanceId);
}
return getSetTime(setTime);
}
public Date getSetTime(String setTime) {
if (null != setTime) {
try {
// 获取当前时间的时分秒
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
String currentTimeStr = timeFormat.format(new Date());
// 组合日期和时间
String combinedDateTimeStr = setTime + " " + currentTimeStr;
SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return dateTimeFormat.parse(combinedDateTimeStr);
} catch (ParseException e) {
e.printStackTrace();
return null;
}
}
return DateUtils.getCurrentDate();
}
}典型应用场景
1. 财务费用报销与成本核算
这是最经典的应用场景。
- 场景描述: 员工在12月29日发生了一笔业务招待费,但直到1月4日才回到公司提交报销申请。根据会计准则,这笔费用必须计入上一财年(12月) 的成本中。
- 工作流如何工作:
- 员工在1月4日提交报销单,但在表单中手动选择或填写“费用发生日期”为 12月29日。
- 系统将此日期作为整个审批流程的“基准日期”。
- 无论部门经理、财务人员在1月的哪一天审批,所有审批记录和最终的系统凭证日期都显示为 12月29日。
- 财务系统在生成报表时,这笔费用会自动归集到12月的账目中,确保了财务报表的准确性。
2. 合同与协议审批
- 场景描述: 一份销售合同的实际签署日和生效日是12月25日,但内部法务和管理层因为假期原因,在1月初才完成线上审批流程。
- 工作流如何工作:
- 销售人员在1月5日发起合同审批,但指定“合同生效日期”为 12月25日。
- 所有审批节点的操作时间在系统记录上都关联到12月25日。
- 最终生成的合同编号、系统记录的合同生效日期都是12月25日,这与纸质合同保持一致,避免了法律和业务上的混淆。
3. 补打卡、补请假等考勤流程
- 场景描述: 员工忘记在12月30日打卡或申请事假,需要在1月3日进行补申请。
- 工作流如何工作:
- 员工在1月3日发起“补打卡申请”或“事假申请”,并选择申请日期为 12月30日。
- 审批流程记录为该日期。
- 最终,HR系统和考勤计算会认为该申请是在12月30日生效的,从而正确计算12月的出勤、薪资和扣款。
4. 采购与资产入库
- 场景描述: 一批货物在12月31日已经送达仓库并验收完毕(实物入库),但相关的采购发票和审批流程在1月份才完成。
- 工作流如何工作:
- 采购人员在1月发起采购付款审批,但指定“货物接收日期”或“资产确认日期”为 12月31日。
- 审批流程以此日期为准。
- 这批货物在资产管理系统和财务账上会被记为上一财年的资产,符合权责发生制原则。
5. 项目里程碑确认
- 场景描述: 一个项目在12月28日达到了某个关键里程碑,但项目报告和确认流程在1月初才走完。
- 工作流如何工作:
- 项目经理在1月提交里程碑达成审批,指定“里程碑达成日期”为 12月28日。
- 所有审批记录与此日期绑定。
- 项目管理系统和绩效评估会基于12月28日这个日期来记录进度和计算奖金。

