实践案例 | 基于苍穹平台,灵活实现一站式报账

栏目:云苍穹知识作者:金蝶来源:金蝶云社区发布:2024-09-23浏览:1

实践案例 | 基于苍穹平台,灵活实现一站式报账

小编推荐

面对财务报销种类繁多,报销流程场景多变,财务系统复杂的业务流程,非财务人员亟待简明的报销流程展示。如何更加高效地整合、协同各个报销类别的信息交互,并且更好地整合报销系统,实现个人快速高效的报销方式+报账一体化的报销平台呢?


本期【基于苍穹平台的一站式财务报账解决方案】告诉你答案,通过简化个人报销流程、统一报销入口,提供多种类可扩展的多报销系统集成能力,并具备支持配置式热部署、开发成本低、可适配等特性~


案例撰稿人:FA。



1 业务背景


随着国家大力推动企业数字化转型,企业内部数字化建设需要各种业务系统来提高企业生产力。面对财务报销种类繁多,报销流程场景多变,财务系统复杂的业务流程,非财务人员亟待简明的报销流程展示,如何更加高效地整合、协同各个报销类别的信息交互,并且更好地整合报销系统,实现个人快速高效的报销方式+报账一体化的报销平台,是一个亟待解决的问题。


客户A在数字化建设过程中也面临了上述问题,具体痛点如下:


1、客户财务业务系统报销类型繁多,财务系统数据相对独立,导致人员报销基础数据交互不够流畅,报销入口太多不够简洁。

2、报账报表合并复杂,报表开发成本高,场景适配差,部署频次高。

3、随着企业规模增大,业务量剧增,需要用户深度参与的报销流程需求量巨大,财务系统业务复杂,加大了普通报销用户报销的工作量。

4、企业对于业务响应的时效性要求越来越高,如何确保用户能够快速高效地实现报销流程,加快业务审批的效率,也是客户急需解决的痛点。


基于以上痛点分析,不难发现,用户需要的是:


1、简化个人报销流程,统一报销入口

集成各个应用之间的报销数据同步,确保人员报销流程简化,统一入口,无需切换多个应用,避免个人报销的繁复。


2、具备多种类,可扩展的多报销系统集成能力的对接平台

预置各种常见的报销单的快速集成能力,并整合抽象,形成可配置化的报销系统对接方案,能够实现如费用、差旅、对公等常用报销类型单据的快速集成能力。


3、拥有支持配置式热部署,开发成本低,可适配的能力

拥有完善的多报销合并方式,且配置方式简便,具备无需部署可直接生效,开发工作量小,成本低等特性,开发适配性高,适配一切报销单类型。


2 解决方案

2.1 方案整体思路


针对以上问题,如何基于苍穹平台,实现一站式财务报账,可以从以下四个方面入手:


报账开发:继承平台的标准页面,开发报账表单及逻辑

单据接入:接入苍穹已有的报销单据类型,实现报账数据的合并,无需复杂报表开发。

操作配置:无需进行繁多的单据开发,单据操作配置简洁,直接通过后台任务处理单据对应的操作同步,进而实现最终的数据实时同步。

历史数据同步:苍穹平台内置了报销单据的历史数据同步服务,可通过一次配置,一键同步历史单据,无需关注历史数据问题。


2.2 关键实现步骤


步骤1:报账开发


在【开发平台】→【员工服务云】→【报账工作台】→【我的报账】中找到【我的报账】单据元数据(dhc_mybilllist),继承生成一个新的报账表单用于自由二开,如下图所示:


上传图片

继承报账表单


在【开发平台】打开二开的元数据(8b4t_dhc_mybilllist_jt),选择列表,绑定二开插件后保存。


上传图片

绑定报账列表插件


通过插件实现功能:增加二开单据类型过滤并去除标准内置单据类型,扩展查看单据范围,核心代码如下:


/**
 * 
 * @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;
    }


通过二开插件开发扩展标准功能,可实现对标准的报账单据类型进行单独隔离(标准预置的标准单据无法删除,而实际业务场景无需标准单据的分类过滤),报账数据查看范围可实现多样化的过滤需求(标准只可查看当前登录人的报账数据)。


上传图片

报账列表页


步骤2:单据接入


 在【开发平台】→【开发服务云】→【元数据模型】→【首页】-引用平台基础资料——表单元数据(bos_formmeta),便于在人人报账应用内进行个性化的配置。打开接入单据元数据,切换到列表预览界面,新增一条数据后,保存即可:


上传图片

上传图片

接入单据元数据


在【开发平台】→【员工服务云】→【报账工作台】→【基础资料】-引用平台基础资料——表单元数据(dhc_billclassification),用于单据分类,例如“出差申请单”隶属于“费用报账”分类。打开报账分类元数据,切换到列表预览界面,点击超链“费用报账”:


上传图片

上传图片

点击超链“费用报账”


进入子页面,点击“增行”,在该分类下增加一条“出差申请单”的数据,保存即可:


上传图片

增加单据数据


注:如果需要新增分类,就先新增一个分类再把单据挂在该分类下即可。新增分类多,则需要导出的预置数据会更多一些。


步骤3:操作接入


此步骤为核心配置,决定单据在哪些操作后会将数据同步至人人报账。在【开发平台】-【员工服务云】-【报账工作台】-【基础配置】-引用平台基础资料(dhc_operation)打开单据操作元数据,切换到列表预览界面,新增一条数据,保存即可:


上传图片

上传图片

接入单据操作


注: “触发操作名称”虽可选择该单据所有操作项,但目前标准产品在插件层面仅支持了保存/删除,提交/提交并新增/撤销,以及特定单据的审核/反审核操作的数据写入,其它操作则被拦截。如果需要配置其它操作并期望它生效,需要在插件层面做相应的二开。


接入单据需作单据映射,单据映射决定单据在将数据同步到人人报账时,单据字段与人人报账中我的报账报表字段的值映射关系。接入新单据需要配置系统默认的12条映射数据。


打开单据映射元数据,切换到列表预览界面,严格按下表内容新增12条数据并保存。其中,字段“映射字段名”和“映射字段编码”先不填(映射方式为“无需映射”的项,建议给“映射字段名”填一个横杠):


上传图片

单据映射表


然后,进入【人人报账】→【基础设置】→【报账单据配置】,点击页面左树的“费用报账”分类下的“出差申请单”节点,可以看到已经加载了上面配置的12条系统默认映射数据。我们需要将映射方式非“无需映射”的条目,按需求配置默认的映射目标字段,再保存页面即可:


上传图片

配置映射目标字段


注:当接入单据较多时,建议使用单据映射基础资料的引入功能新增数据。


接下来,需要将单据配置到【人人报账】菜单上。可以通过拓展应用元数据直接进行菜单配置,也可以在【人人报账】→【基础设置】→【报账分类】设置中进行菜单配置(配置完成后重新进入应用即生效),效果基本一致。


上传图片

将单据配置到【人人报账】菜单上


至此,该单据接入人人报账的配置工作全部完成。


步骤4:历史数据同步


进入【人人报账】→【基础设置】→【报账数据初始化】进行历史数据的初始化同步,选择一条需要初始化的单据类型,初始化一次即可,点击“查看初始化进度“可以查看初始化的具体情况。


上传图片

【报账数据初始化】列表

上传图片

查看初始化进度


3 竞品比较


相比于其他同类平台,苍穹一站式报账解决方案,开发成本小,高度可配置,支持热部署,同步逻辑已高度抽象封装,无需过多二开改造和维护。


4 方案的复用价值


行业的普适程度


该方案不限制行业,所有需要一站式报账系统的企业,均可使用,只需要结合企业的规模大小、业务复杂程度以及客户投入成本综合评估,选择最合适的解决方案即可。


对客户的价值


简化报销流程,提升报销效率,保障及时性,提高生产力。


普通员工报销无需关注财务报销系统的复杂业务流程,统一了入口,只专注自身的报销流程,提升了员工的财务报销效率,极大地便捷了员工。


在使用该方案前,财务报账原先的报表展示方式纷杂,报表合并逻辑复杂,开发成本高,周期长,部署频繁;使用该方案后,财务报账高自由配置化、开发成本低、支持热部署,缓解部署压力,为企业节约了巨大的时间成本。




#往期推荐#


# 实践案例 | 超详细苍穹环境迁移方案,你值得拥有!

实践案例 | 工作效率翻倍的秘籍:快捷键功能全面解析

实践案例 | 如何使用多维数据库数据制做经营分析报表

实践案例 | 多异构系统间高效系统集成方案


更多精彩内容,“码”上了解!↓

上传图片

实践案例 | 基于苍穹平台,灵活实现一站式报账

小编推荐面对财务报销种类繁多,报销流程场景多变,财务系统复杂的业务流程,非财务人员亟待简明的报销流程展示。如何更加高效地整合、协同...
点击下载文档
确认删除?
回到顶部
客服QQ
  • 客服QQ点击这里给我发消息