实践案例 | 基于苍穹平台,灵活实现一站式报账
面对财务报销种类繁多,报销流程场景多变,财务系统复杂的业务流程,非财务人员亟待简明的报销流程展示。如何更加高效地整合、协同各个报销类别的信息交互,并且更好地整合报销系统,实现个人快速高效的报销方式+报账一体化的报销平台呢? 本期【基于苍穹平台的一站式财务报账解决方案】告诉你答案,通过简化个人报销流程、统一报销入口,提供多种类可扩展的多报销系统集成能力,并具备支持配置式热部署、开发成本低、可适配等特性~ 案例撰稿人:FA。 随着国家大力推动企业数字化转型,企业内部数字化建设需要各种业务系统来提高企业生产力。面对财务报销种类繁多,报销流程场景多变,财务系统复杂的业务流程,非财务人员亟待简明的报销流程展示,如何更加高效地整合、协同各个报销类别的信息交互,并且更好地整合报销系统,实现个人快速高效的报销方式+报账一体化的报销平台,是一个亟待解决的问题。 客户A在数字化建设过程中也面临了上述问题,具体痛点如下: 1、客户财务业务系统报销类型繁多,财务系统数据相对独立,导致人员报销基础数据交互不够流畅,报销入口太多不够简洁。 2、报账报表合并复杂,报表开发成本高,场景适配差,部署频次高。 3、随着企业规模增大,业务量剧增,需要用户深度参与的报销流程需求量巨大,财务系统业务复杂,加大了普通报销用户报销的工作量。 4、企业对于业务响应的时效性要求越来越高,如何确保用户能够快速高效地实现报销流程,加快业务审批的效率,也是客户急需解决的痛点。 基于以上痛点分析,不难发现,用户需要的是: 1、简化个人报销流程,统一报销入口 集成各个应用之间的报销数据同步,确保人员报销流程简化,统一入口,无需切换多个应用,避免个人报销的繁复。 2、具备多种类,可扩展的多报销系统集成能力的对接平台 预置各种常见的报销单的快速集成能力,并整合抽象,形成可配置化的报销系统对接方案,能够实现如费用、差旅、对公等常用报销类型单据的快速集成能力。 3、拥有支持配置式热部署,开发成本低,可适配的能力 拥有完善的多报销合并方式,且配置方式简便,具备无需部署可直接生效,开发工作量小,成本低等特性,开发适配性高,适配一切报销单类型。 针对以上问题,如何基于苍穹平台,实现一站式财务报账,可以从以下四个方面入手: 报账开发:继承平台的标准页面,开发报账表单及逻辑 单据接入:接入苍穹已有的报销单据类型,实现报账数据的合并,无需复杂报表开发。 操作配置:无需进行繁多的单据开发,单据操作配置简洁,直接通过后台任务处理单据对应的操作同步,进而实现最终的数据实时同步。 历史数据同步:苍穹平台内置了报销单据的历史数据同步服务,可通过一次配置,一键同步历史单据,无需关注历史数据问题。 步骤1:报账开发 在【开发平台】→【员工服务云】→【报账工作台】→【我的报账】中找到【我的报账】单据元数据(dhc_mybilllist),继承生成一个新的报账表单用于自由二开,如下图所示: 继承报账表单 在【开发平台】打开二开的元数据(8b4t_dhc_mybilllist_jt),选择列表,绑定二开插件后保存。 绑定报账列表插件 通过插件实现功能:增加二开单据类型过滤并去除标准内置单据类型,扩展查看单据范围,核心代码如下: 通过二开插件开发扩展标准功能,可实现对标准的报账单据类型进行单独隔离(标准预置的标准单据无法删除,而实际业务场景无需标准单据的分类过滤),报账数据查看范围可实现多样化的过滤需求(标准只可查看当前登录人的报账数据)。 报账列表页 步骤2:单据接入 在【开发平台】→【开发服务云】→【元数据模型】→【首页】-引用平台基础资料——表单元数据(bos_formmeta),便于在人人报账应用内进行个性化的配置。打开接入单据元数据,切换到列表预览界面,新增一条数据后,保存即可: 接入单据元数据 在【开发平台】→【员工服务云】→【报账工作台】→【基础资料】-引用平台基础资料——表单元数据(dhc_billclassification),用于单据分类,例如“出差申请单”隶属于“费用报账”分类。打开报账分类元数据,切换到列表预览界面,点击超链“费用报账”: 点击超链“费用报账” 进入子页面,点击“增行”,在该分类下增加一条“出差申请单”的数据,保存即可: 增加单据数据 注:如果需要新增分类,就先新增一个分类再把单据挂在该分类下即可。新增分类多,则需要导出的预置数据会更多一些。 步骤3:操作接入 此步骤为核心配置,决定单据在哪些操作后会将数据同步至人人报账。在【开发平台】-【员工服务云】-【报账工作台】-【基础配置】-引用平台基础资料(dhc_operation)打开单据操作元数据,切换到列表预览界面,新增一条数据,保存即可: 接入单据操作 注: “触发操作名称”虽可选择该单据所有操作项,但目前标准产品在插件层面仅支持了保存/删除,提交/提交并新增/撤销,以及特定单据的审核/反审核操作的数据写入,其它操作则被拦截。如果需要配置其它操作并期望它生效,需要在插件层面做相应的二开。 接入单据需作单据映射,单据映射决定单据在将数据同步到人人报账时,单据字段与人人报账中我的报账报表字段的值映射关系。接入新单据需要配置系统默认的12条映射数据。 打开单据映射元数据,切换到列表预览界面,严格按下表内容新增12条数据并保存。其中,字段“映射字段名”和“映射字段编码”先不填(映射方式为“无需映射”的项,建议给“映射字段名”填一个横杠): 单据映射表 然后,进入【人人报账】→【基础设置】→【报账单据配置】,点击页面左树的“费用报账”分类下的“出差申请单”节点,可以看到已经加载了上面配置的12条系统默认映射数据。我们需要将映射方式非“无需映射”的条目,按需求配置默认的映射目标字段,再保存页面即可: 配置映射目标字段 注:当接入单据较多时,建议使用单据映射基础资料的引入功能新增数据。 接下来,需要将单据配置到【人人报账】菜单上。可以通过拓展应用元数据直接进行菜单配置,也可以在【人人报账】→【基础设置】→【报账分类】设置中进行菜单配置(配置完成后重新进入应用即生效),效果基本一致。 将单据配置到【人人报账】菜单上 至此,该单据接入人人报账的配置工作全部完成。 步骤4:历史数据同步 进入【人人报账】→【基础设置】→【报账数据初始化】进行历史数据的初始化同步,选择一条需要初始化的单据类型,初始化一次即可,点击“查看初始化进度“可以查看初始化的具体情况。 【报账数据初始化】列表 查看初始化进度 相比于其他同类平台,苍穹一站式报账解决方案,开发成本小,高度可配置,支持热部署,同步逻辑已高度抽象封装,无需过多二开改造和维护。 行业的普适程度 该方案不限制行业,所有需要一站式报账系统的企业,均可使用,只需要结合企业的规模大小、业务复杂程度以及客户投入成本综合评估,选择最合适的解决方案即可。 对客户的价值 简化报销流程,提升报销效率,保障及时性,提高生产力。 普通员工报销无需关注财务报销系统的复杂业务流程,统一了入口,只专注自身的报销流程,提升了员工的财务报销效率,极大地便捷了员工。 在使用该方案前,财务报账原先的报表展示方式纷杂,报表合并逻辑复杂,开发成本高,周期长,部署频繁;使用该方案后,财务报账高自由配置化、开发成本低、支持热部署,缓解部署压力,为企业节约了巨大的时间成本。 #往期推荐#小编推荐
1 业务背景
2 解决方案
2.1 方案整体思路
2.2 关键实现步骤
/**
*
* @author cyf
* 我的报账:增加二开单据类型过滤并去除标准内置单据类型,扩展查看单据范围
*/
public class MyBillListPlugin_JTExt extends AbstractListPlugin{
private static final Log log = LogFactory.getLog(MyBillListPlugin_JTExt.class);
private List<FilterColumn> commonFilterColumnList;
private FieldMapService service;
private static final String CK_BILLKIND = "billkind";
public MyBillListPlugin_JTExt() {
//初始化字段
this.commonFilterColumnList = new ArrayList<FilterColumn>();
this.service = (FieldMapService)new FieldMapServiceImpl();
}
public void filterContainerInit( FilterContainerInitArgs args) {
super.filterContainerInit(args);
this.commonFilterColumnList = (List<FilterColumn>)args.getCommonFilterColumns();
if (!this.isOverClick()) {
for ( FilterColumn column : this.commonFilterColumnList) {
CommonFilterColumn commonColumn = (CommonFilterColumn)column;
if (StringUtils.equals((CharSequence)commonColumn.getFieldName(), (CharSequence)"bill.name")) {
this.iniL5Filter(commonColumn);
String cacheValue = SerializationUtils.toJsonString((Object)commonColumn.getComboItems());
this.getPageCache().put("allbill", cacheValue);
this.getPageCache().put("l5bill", cacheValue);
column.setDefaultValue("");
}
}
}
}
/*
* 对二开单据类型过滤
* */
public void filterContainerBeforeF7Select( BeforeFilterF7SelectEvent args) {
super.filterContainerBeforeF7Select(args);
String key = args.getFieldName();
//单据类型
if (StringUtils.isNotBlank((CharSequence)key) && StringUtils.contains((CharSequence)key, (CharSequence)"bill.")) {
String selectedField = "entryentity.bill.number,entryentity.bill.name";
String billKindJson = this.getPageCache().get("billkind");
List<QFilter> qFilter = new ArrayList<QFilter>(1);
if (StringUtils.isNotBlank((CharSequence)billKindJson)) {
List idList = (List)SerializationUtils.fromJsonString(billKindJson, (Class)List.class);
if (!idList.isEmpty()) {
qFilter.add(new QFilter("id", "in", (Object)idList));
}
}
//综合集团单据支持列表过滤
DynamicObjectCollection collections = QueryServiceHelper.query("dhc_billclassification", selectedField, (QFilter[])qFilter.toArray(new QFilter[0]));
List<String> billIdList = new ArrayList<String>(10);
for ( DynamicObject dyo : collections) {
if(dyo!=null) {
String name=dyo.getString("entryentity.bill.name");
if(StringUtils.isNotEmpty(name)) {
if(name.contains("集团")||name.equals("收款信息更正单")) {
billIdList.add(dyo.getString("entryentity.bill.number"));
}
}
}
}
List<QFilter> qFilters = (List<QFilter>)args.getQfilters();
qFilters.add(new QFilter("number", "in", (Object)billIdList));
}
}
//对登录人查看数据过滤
@Override
public void setFilter(SetFilterEvent e) {
RequestContext requestContext = RequestContext.get();
long currentUserId = Long.parseLong(requestContext.getUserId());
QFilter currentPerson = new QFilter("applicant", "=", (Object) currentUserId);
// 过滤正确的编码vs单据名称
/* 集团业务招待报销单\集团费用报销单\集团固定资产报销单\集团对公报销单\集团员工借款单\集团备用金借款单\集团预付单
\集团费用分摊单\集团差旅报销单\收款信息更正单\集团还款单*/
final String BILL_NO = "billno";
QFilter billQf1 = new QFilter(BILL_NO, QCP.like, "%JTZD%").and(new QFilter("bill.name", QCP.like, "%招待%"));
QFilter billQf2 = new QFilter(BILL_NO, QCP.like, "%JTFY%").and(new QFilter("bill.name", QCP.like, "%费用%"));
QFilter billQf3 = new QFilter(BILL_NO, QCP.like, "%JTGD%").and(new QFilter("bill.name", QCP.like, "%固定%"));
QFilter billQf4 = new QFilter(BILL_NO, QCP.like, "%JTDG%").and(new QFilter("bill.name", QCP.like, "%对公%"));
QFilter billQf5 = new QFilter(BILL_NO, QCP.like, "%JTYGJK%").and(new QFilter("bill.name", QCP.like, "%员工借款%"));
QFilter billQf6 = new QFilter(BILL_NO, QCP.like, "%JTBYJ%").and(new QFilter("bill.name", QCP.like, "%备用金借款%"));
QFilter billQf7 = new QFilter(BILL_NO, QCP.like, "%JTYF%").and(new QFilter("bill.name", QCP.like, "%预付%"));
QFilter billQf8 = new QFilter(BILL_NO, QCP.like, "%JTFT%");
QFilter billQf9 = new QFilter(BILL_NO, QCP.like, "%JTCL%");
QFilter billQf10 = new QFilter(BILL_NO, QCP.like, "%SKGZ%");
QFilter billQf11 = new QFilter(BILL_NO, QCP.like, "%JTHK%");
currentPerson.and((billQf1).or(billQf2).or(billQf3).or(billQf4).or(billQf5).
or(billQf6).or(billQf7).or(billQf8).or(billQf9).or(billQf10).or(billQf11));
//报账数据过滤
currentPerson.or(new QFilter("creator", "=", (Object) currentUserId));
String idFromCache = this.getIdFromCache(currentPerson);
List<String> ids = Arrays.asList(idFromCache.split(":"));
currentPerson.and(new QFilter("id", QCP.in, ids));
e.getQFilters().add(currentPerson);
}
/*
*初始化单据审批节点信息
*/
public void beforeCreateListDataProvider(BeforeCreateListDataProviderArgs args) {
super.beforeCreateListDataProvider(args);
args.setListDataProvider((IListDataProvider) new ListDataProvider() {
public DynamicObjectCollection getData(int arg0, int arg1) {
DynamicObjectCollection collection = super.getData(arg0, arg1);
final Map<String, String> billInfoMap = new HashMap<String, String>(22);
//获取单据id
Map<String, String> map = new HashMap<>();
collection.forEach(v -> {
String billId = v.get("billid").toString();
if (StringUtils.isNotBlank((CharSequence) billId)) {
map.put(String.valueOf(v.get("billid")), String.valueOf(v.get("bill.number")));
}
return;
});
if (billInfoMap.size() > 0) {
try {
List<String> pkId = new ArrayList<String>(billInfoMap.keySet());
//查询单据实时审批节点信息
MyBillListPlugin_JTExt.this.setNextAuditor(collection, pkId);
MyBillListPlugin_JTExt.this.setBillStatus(collection, billInfoMap);
}
catch (Exception e) {
log.error("ErWorkFlowFlexListForOtherPlugin >>>>> 设置审批人时出现异常", (Throwable)e);
}
}
return collection;
}
});
}
private void setNextAuditor(DynamicObjectCollection collection,List<String> pkId) {
Map<String, String> auditorsNameMap = this.getNextAuditor(pkId);
collection.forEach(v -> {
String pkIdTemp = String.valueOf(v.get("billid"));
try {
v.set("currentdealer", (Object)((auditorsNameMap.get(pkIdTemp) == null) ? "" : auditorsNameMap.get(pkIdTemp)));
}
catch (Exception e) {
log.error("下一步审批人字段在列表中不存在", (Throwable)e);
}
});
}
private Map<String, String> getNextAuditor(final List<String> pkIds) {
Map<String, String> nodeMap = new HashMap<String, String>(20);
String[] ids = new String[pkIds.size()];
pkIds.toArray(ids);
Map<String, List<BizProcessStatus>> allPro = (Map<String, List<BizProcessStatus>>)WorkflowServiceHelper.getBizProcessStatus(ids);
for (final Map.Entry<String, List<BizProcessStatus>> entry : allPro.entrySet()) {
String pkid = entry.getKey();
List<BizProcessStatus> node = allPro.get(pkid);
Map<String, String> map=new HashMap<>();
node.forEach(e -> {
String nodeStr = e.getCurrentNodeName();
String auditor = e.getParticipantName();
if (auditor != null && !"".equals(auditor.trim())) {
nodeStr = nodeStr + " / " + auditor;
}
if (nodeStr.length() > 26) {
nodeStr = nodeStr.substring(0, 26);
}
map.put(pkid, nodeStr);
return;
});
}
return nodeMap;
}
3 竞品比较
4 方案的复用价值
实践案例 | 基于苍穹平台,灵活实现一站式报账
本文2024-09-23 00:20:46发表“云苍穹知识”栏目。
本文链接:https://wenku.my7c.com/article/kingdee-cangqiong-138875.html