每工作日一、三、五早上07:45
准时为你带来最新产品资讯
面对财务审核的复杂性,企业如何构筑一道坚固的防线,确保资金审核支付的安全性?
本案例介绍了一种审批流程自定义弹窗校验解决方案,简化财务审核流程的同时大幅提升了支付安全性,且二开量小、可复用性强,推荐阅读。
1 业务背景
在国家积极推动企业数字化转型的浪潮中,企业内部数字化升级亟需高效整合各类业务系统,以驱动生产力飞跃。然而,面对财务报销的多样化、流程场景的灵活变化以及财务系统本身的复杂性,如何强化权限控制、确保资金审核支付的安全性,成为亟待破解的难题。
客户A在数字化转型征途中亦遭遇此挑战,其核心痛点包括:
报销类型繁多,财务系统数据相对独立,审核人员需独立控制付款账户权限,并执行独立校验。
财务审核链条冗长,校验机制复杂且灵活性不足,难以适应特殊场景下的安全校验需求,缺乏统一的成熟方案。
鉴于成本控制考虑,如何低成本、高效率地实现可配置化的定制功能开发,促进资产复用,也是客户亟待解决的关键问题。
基于上述痛点分析,客户需求聚焦于:
1. 简化审批流程校验,统一校验入口
集成审核校验流程,简化分散的校验条件,统一入口,无需开发多种校验类型。
2. 场景适应强,自由度高的流程弹窗校验
流程弹窗完全二开,拥有极高的自由度,可适配各类复杂财务审核场景。
3. 资产复用,扩展产品应用场景的能力
标准产品仅支持工作流弹窗校验账户密码或者手机验证码校验,不可同时使用,也无法进行扩展开发。二开方案需扩展标准产品的工作流弹窗应用场景,提高场景适配度和自由度,实现资产复用。
2 解决方案
2.1 方案整体思路
针对以上问题,如何基于苍穹平台,实现工作流自定义弹窗校验,可以从以下三个方面入手:
校验表单开发:根据业务校验流程,二开一个动态校验表单。
审批页面校验弹窗开发:统一审批校验流程入口,流程审批人操作时弹出自定义校验表单弹窗。
工作流审批节点配置:配置工作流审核操作校验,操作完成弹出校验表单弹窗,完成校验信息填写后,后台完成信息校验才可放行审核(强校验),否则不可进行审核。
业务校验流程
2.2 关键实现步骤
1. 校验表单开发
在“开发平台>财务云>出纳>付款单”中新建动态表达元数据(kye_zpsecondvalidate),如下图所示:
在【开发平台】打开二开的元数据(kye_zpsecondvalidate),绑定二开插件后保存。
绑定插件
获取密码及发送的验证码信息校验完成后,向父级页面回传参数(核心要点:通过父级页面回调判断是否已进行二次校验,判断是否提示操作工作流弹窗)
this.getView().returnDataToParent() 插件核心代码如下:
/**
*
* @author cyf
* 2024.6.22
* 组批审核节点,二次校验表单插件
*/
private static Logger logger = Logger.getLogger(PayzpSecondValiFormPlugin.class);
@Override
public void registerListener(EventObject e) {
super.registerListener(e);
this.addClickListeners("kye_send");
this.addClickListeners("kye_login");
CountDown countdown = this.getView().getControl("kye_countdownap");
countdown.addCountDownListener(this);
}
@Override
public void click(EventObject evt) {
super.click(evt);
Control control = (Control) evt.getSource();
String itemKey = control.getKey();
switch (itemKey) {
case "kye_send":
sendSMS();
break;
case "kye_login":
close();
break;
default:
break;
}
}
/**
* 发送短信验证码接口
*/
private void sendSMS() {
//发送验证码,并缓存到页面隐藏字段
SecondaccountVerifyHelper.sendSMS(this.getView());
}
@Override
public void onCountDownEnd(CountDownEvent evt) {
CountDownListener.super.onCountDownEnd(evt);
this.getView().setEnable(true,"kye_send");
this.getView().setVisible(true,"kye_labelap1","kye_labelap2","kye_countdownap");
}
@Override
public void afterCreateNewData(EventObject e) {
super.afterCreateNewData(e);
this.getView().setVisible(false,"kye_labelap1","kye_labelap2","kye_countdownap");
}
/**
* 关闭页面,并且把账号,密码,验证码回传到父级页面缓存
*/
private void close() {
String input = (String) this.getModel().getValue("kye_input");
String userPassword = (String) this.getModel().getValue("kye_password");
if (StringUtils.isBlank(input) || StringUtils.isBlank(userPassword)) {
this.getView().showErrorNotification("请输入验证码或密码!");
} else {
//当前账户id
String accountId = RequestContext.get().getAccountId();
//先直接验证,不通过不允许关闭弹窗
SecondaccountVerifyHelper.validateForm(accountId, userPassword, input, this.getView().getPageCache().get("captcha_chird"), this.getView());
//验证成功,向父级页面-付款组批单传递缓存
IPageCache iPageCache = this.getView().getParentView().getService(IPageCache.class);
iPageCache.put("accountId", accountId);
iPageCache.put("userPassword", userPassword);
iPageCache.put("messageCode", input);
this.getView().returnDataToParent("isOPen");
//关闭当前页面
this.getView().invokeOperation("close");
}
}
2. 审批页面校验弹窗开发
在“开发平台>流程服务云>工作流服务>任务中心”扩展标准审批表单wf_approvalpage_bac,用于审批人操作弹出自定义校验表单,绑定二开插件即可。
注意:必须使用this.getPageCache().get("bizIdentifyKey")来判断当前单据节点是否需要弹窗校验,目前无其他办法。
弹窗核心代码如下:
/**
*
* @author cyf
* 2024.8.13
* 扩展审批处理页面编码为wf_approvalpage_bac 进行弹窗处理
*
*/
public class ApprovalpageBacFormPlugin extends AbstractFormPlugin{
private static Logger logger = Logger.getLogger(ApprovalpageBacFormPlugin.class);
// 缓存中的kev -用于记录是否已经加载过弹出窗口
private final String PAGECACHEKEY_CALLBACK = "beforeSubmitCustomEventArgsCallbackResult";
//弹出窗口的callbackidprivate
private final String CALLBACKID ="beforeSubmitCustomEventArgsCallback";
@Override
public void afterCreateNewData(EventObject e) {
super.afterCreateNewData(e);
String isneedValidta=getView().getFormShowParameter().getCustomParam("isneedValidta");
logger.info("=====s审批界面自定义参数isneedValidta:"+isneedValidta);
if("true".equals(isneedValidta)) {
this.getPageCache().put("isneedValidta", "true");
}
}
public void customEvent(CustomEventArgs e) {
if(e instanceof BeforeSubmitCustomEventArgs){
//丛缓存中装取标志
String result = getPageCache().get(PAGECACHEKEY_CALLBACK);
//如果有缓存 且为true,说明之前已经弹出窗口,并且继续往下流转
if(null != result && Boolean.parseBoolean(result)) {
return;
}
//先查询自定义界面参数,看是否需要弹窗校验
String isneedValidta=this.getPageCache().get("bizIdentifyKey");
logger.info("=====审批界面自定义参数bizIdentifyKey:"+isneedValidta);
//获取审批决策项。只有提交才需要弹窗
String combo_decision=(String) this.getModel().getValue("combo_decision");
logger.info("=====审批界面审批决策项combo_decision:"+combo_decision);
if("isneedValidta".equals(isneedValidta)&&combo_decision.contains("Consent")) {
BeforeSubmitCustomEventArgs be=(BeforeSubmitCustomEventArgs)e;
//设置任务不提交
be.setCancel(true);
//弹出自己页面,进行展现
FormShowParameter parameter=new FormShowParameter();
String formId ="kye_zpsecondvalidate";
// TODO 替换为自己的fonmid
parameter.setFormId(formId);
parameter.getOpenStyle().setShowType(ShowType.Modal);
parameter.setCloseCallBack(new CloseCallBack(this, CALLBACKID));
getView().showForm(parameter);
}
}
}
@Override
public void closedCallBack(ClosedCallBackEvent e) {
if(null == e){
return;
}
Object returnData=e.getReturnData();
if(null == returnData) {
return;
}
String callBackId =e.getActionId();
if(CALLBACKID.equals(callBackId)) {
BeforeSubmitCustomEventArgsClosedCallBack args = new BeforeSubmitCustomEventArgsClosedCallBack(this.getView(), null, null, null);
// TODO 做逻辑判断- 是否需要提交任务;true表示需要重新提交、false表示不需要任务提交boolean doSubmitTask =true;
boolean doSubmitTask =true;
//设置是否直接提交任务
args.setDoSubmitTask(doSubmitTask);
//将设置的结果,放到cache中,用于下一次进入custom事件进行判断,是否需要再次弹出窗口
getPageCache().put(PAGECACHEKEY_CALLBACK, String.valueOf(doSubmitTask));
//发送回调事件proxy.fireCustomEvent(args);
FormViewPluginProxy proxy = (FormViewPluginProxy)getView().getService(FormViewPluginProxy.class);
proxy.fireCustomEvent(args);
}
}
3. 工作流审批节点配置
在单据的流程审批节点配置业务标识(第二步this.getPageCache().get("bizIdentifyKey")获取的),用于用户操作弹出自定义校验表单:
配置审核操作校验第二步配置的自定义操作accountpss,审核时可触发单据的表单事件beforeDoOperation,在此事件校验业务。
表单事件核心代码如下:
public class OPenValiFormPlugin extends AbstractFormPlugin{
@Override
public void beforeDoOperation(BeforeDoOperationEventArgs args) {
super.beforeDoOperation(args);
FormOperate oprate=(FormOperate) args.getSource();
String opkey=oprate.getOperateKey();
//审批操作二次校验并同时校验用户的银行操作权限(由于工作流只可配置一个操作校验,故先执行弹窗二次校验校验),accountpss操作含银行操作权限插件校验
if("accountpss".equals(opkey)) {
//先判断当前页面缓存,判断是否已经进行弹窗二次校验,
boolean isOPenForm=SecondaccountVerifyHelper.isOPenForm(this.getView());
if(!isOPenForm) {
//未进行弹窗校验,直接弹窗
SecondaccountVerifyHelper.OpenForm(this.getView());
args.setCancel(true);
}
}
}
最后注意点:
由于本质是在审核单据弹窗,标准消息中心批量审核会绕过本表单校验配置,故需要禁用批量审核操作。扩展标准表单禁用批量操作即可。
3 方案效果
直接审核弹出二次校验表单,弹出校验表单,填写校验信息,确认后,后台校验完全通过才可审批通过。
4 方案的可推广价值
行业的普适程度
该方案不限制行业及校验内容,所有需要进行审批自定义弹窗场景的,均可使用。扩展了标准产品的二次校验场景范围,可形成资产复用。
对客户的价值
简化校验流程,开发量小,自由度极高,场景适配广泛。
#往期推荐#
对文章有任何疑问或建议,欢迎评论区留言~