预付款申请

一、背景

客户购买一批材料,做了采购订单。需要支付一笔定金。这个定金的操作不能直接到财务的付款。需要完善审批流程,所以做了本单据,预付款申请

通过此案例我们来学习开发技术,业务逻辑并不完善,请注意

二、能力清单

编号 能力清单
1 业务流:原厂推自建
2 开放平台接口调用
3 页面设计器常用操作
4 常用函数使用

三、业务流程

图1

四、操作流程

图29

五、数据建模

5.1、【款项类型】枚举

名称: 款项类型
编码: quickType
类型: 字符
枚举内容: 枚举值 名称
5 预付款
6 应付款

5.2、【是否同步】枚举

名称: 是否同步
编码: isSync
类型: 字符
枚举内容: 枚举值 名称
1
2

5.3、【预付款申请】实体

操作 描述
名称 预付款申请
编码 prepayment
父实体
引用接口 审批 业务流 自动编码 交易类型
属性 编码 名称 类型 引用
vouchdate 单据日期 日期
accentity 会计主体 单选引用 会计主体 bd.adminOrg.FinanceOrgVO
oriSum 付款金额 数值
natSum 本币金额 数值
balance 余额 数值
supplier 供应商 单选引用 供应商 aa.vendor.Vendor
supplierbankname 收款银行 文本
supplierbankaccount_accountname 收款银行账户 文本
period 会计期间 单选引用 会计期间 bd.period.Period
settlemode 结算方式 单选引用 结算方式 aa.settlemethod.SettleMethod
currency 币种 单选引用 币种 bd.currencytenant.CurrencyTenantVO
currency_priceDigit 币种单价精度 数值
currency_moneyDigit 币种金额精度 数值
natCurrency 本币币种 单选引用 币种 bd.currencytenant.CurrencyTenantVO
natCurrency_priceDigit 本币币种单价精度 数值
natCurrency_moneyDigit 本币币种金额精度 数值
exchRate 汇率 数值
dept 部门 单选引用 部门 bd.adminOrg.AdminOrgVO
operator 业务员 单选引用 员工 bd.staff.StaffNew
exchangeRateType_digit 汇率类型金额精度 数值
exchangeRateType 汇率类型 单选引用 汇率类型 bd.exchangeRate.ExchangeRateTypeVO
project 项目 单选引用 项目 bd.project.ProjectVO
enterprisebankaccount 付款银行账户id 单选引用 企业银行账户 bd.enterprise.OrgFinBankacctVO
description 备注 文本
noteno 票据号 文本
org 采购组织 单选引用 采购组织 bd.adminOrg.PurchaseOrgVO
cashaccount 付款现金账户 单选引用 企业现金账户 bd.enterprise.OrgFinCashacctVO
isSync 是否同步 单选 是否同步
orderno 订单编号 文本
sourcebillnum 原单金额 数值

5.4、【预付款支付明细】实体

操作 描述
名称 预付款支付明细
编码 prepaybillb
父实体 预付款申请
引用接口 业务流
属性 编码 名称 类型 引用
expenseitem 费用项目 单选引用 费用项目 bd.expenseitem.ExpenseItem
oriSum 金额 数值
natSum 本币金额 数值
customer_code 客户编码 文本
customer 客户 单选引用 客户档案 aa.merchant.Merchant
supplier 供应商 单选引用 供应商 aa.vendor.Vendor
employee 员工 单选引用 员工 bd.staff.StaffNew
dept 部门 单选引用 部门 bd.adminOrg.AdminOrgVO
project 项目 单选引用 项目 bd.project.ProjectVO
orderno 订单编号 文本
description 备注 文本
product 物料 单选引用 物料 pc.product.Product
quickType_code 款项类型 单选 款项类型

六、页面建模

  • 创建【预付款申请】主子表页面

图2

  • 调整【预付款申请列表】页面布局;显示样式调整不必完全一致

图3

  • 调整【预付款申请】页面布局;显示样式调整不必完全一致

图4

  • 根据下图设置默认值

图5 图5

  • 根据下图设置不允许修改字段

图5

  • 配置交易类型,设置默认值为采购付款,选择第一个即可

图5

  • 配置汇率类型,设置默认值为基础汇率

图5

  • 点击保存

七、【采购订单】下推【预付款申请】业务流

7.1、创建预付款申请业务流

  • 新建预付款申请业务流

图6

  • 点击设计进入业务流设计器

图7

  • 选择领域单据-【采购订单】拖进中间操作台
  • 选择应用构建-【预付款申请】拖进中间操作台
  • 选择应用构建【采购订单】流程指向【预付款申请】

图8 图9

  • 点击新增规则

图10

  • 【基本信息】-【规则名称】: 预付款
  • 【触发设置】-【推送时机】: 手工触发
  • 【生单方式】如下图

图11

  • 【规则转换】如下图; 注意此处只是简单的配置了一下转换,并不代表真正的业务逻辑

图12 图13

  • 保存

图14 图15

7.2、【预付款申请】保存前校验

  • 客户可能多次生成付款申请,所以要增加校验逻辑。预付款申请金额总数不能大于等于采购订单金额

  • 新增API函数,根据原单编号(采购订单编号)查询出所有的付款申请单

图64


let AbstractAPIHandler = require('AbstractAPIHandler');
  class MyAPIHandler extends AbstractAPIHandler {
   execute(request){
     var orderno = request.orderno;
     var sql = "select oriSum from GT12951AT32.GT12951AT32.prepaymentnishch where orderno = '"+orderno+"'";
     var res = ObjectStore.queryByYonQL(sql);
   return {res};
 }
}
exports({"entryPoint":MyAPIHandler});
  • 预付款申请卡片界面增加初始化函数

图63 图63


function (event) {
  var viewModel = this;
  viewModel.on('beforeSave',function(){
      var sourcebillnum  = viewModel.get('sourcebillnum').getValue(); //原单金额
      var oriSum = viewModel.get('oriSum').getValue(); //付款金额
      if(oriSum>=sourcebillnum){
        cb.utils.alert("付款金额要小于原单金额");
        return false;
      }
      var orderno = viewModel.get('orderno').getValue();
      var returnPromise =  new cb.promise();
      cb.rest.invokeFunction("GT12951AT32.prepay.prepaylist", {orderno},
      function(err, res) {
        if(err!=null){
          cb.utils.alert(JSON.stringify(err));
          returnPromise.reject();
        }else{
          console.log(res);
          var arrData = res.res;
          var finallyValue = 0 ;
          for(let i in arrData){
            if(arrData[i].oriSum!=undefined)finallyValue +=arrData[i].oriSum;
          }
          if(oriSum>=sourcebillnum-finallyValue){
            cb.utils.alert("累计生成的预付款单金额要小于原单金额");
            returnPromise.reject();
          }else{
            returnPromise.resolve();
          }
        }
      })
      return returnPromise;
    })
}

7.3、设置金额联动

图63


function (event) {
  var viewModel = this;
  var gridModel = viewModel.getGridModel();
  if(event.params.cellName=="oriSum"){
    var value = event.params.value;
    gridModel.setCellValue(event.params.rowIndex,"natSum",value);
    viewModel.get("oriSum").setValue(value);
    viewModel.get("natSum").setValue(value);
    viewModel.get("balance").setValue(value);   
  }
}

7.4、测试【采购订单】下推【预付款申请】

  • 打开【采购订单】节点,选择一条数据,点击下推,选择自己配置的业务流即可 【采购订单】为原厂单据,此处暂不做限制及校验

图16

  • 修改金额,点击保存

图17

  • 【预付款申请列表】 查看生成数据

图18 图19

  • 【采购订单】再次生成预付款申请,会校验金额

图19 图19

八、【预付款申请】生成【付款】单

目前财务领域未提供【领域单据】扩展方式,因此需要转换思路,我们可以通过调用【开放平台】接口实现功能

8.1、 创建API公共函数,作为配置文件,节省环境迁移成本

图23 图23


let AbstractAPIHandler = require('AbstractAPIHandler');
  class MyAPIHandler extends AbstractAPIHandler {
   execute(request){
     var config = {
       "appKey":"827e9923533e41787456c74b9ae54a8",
       "appSecret":"9bdc5f2814914cab81561759a8acfd0",
       "baseUrl":"https://open-api-dbox.yyuap.com"
     }
   return {config};
 }
}
exports({"entryPoint":MyAPIHandler});

8.2、 创建API公共函数,获取开放平台token

图23

新增API-用友开放API-选择付款相关接口

图24



let AbstractAPIHandler = require('AbstractAPIHandler');
class MyAPIHandler extends AbstractAPIHandler {
    execute(request){
        //获取公共函数配置
        let configfun = extrequire("GT12951AT32.config.baseConfig");
        let config = configfun.execute(request);
        var access_token = getToken(config.config.appKey,config.config.appSecret,config.config.baseUrl);
      //获取token方法  
        function getToken(yourappkey,yourappsecrect,baseurl) {
            //设置返回的access_token
            var access_token;
            // 获取token的url
            const token_url = baseurl+"/open-auth/selfAppAuth/getAccessToken";
            // appkey
            const appkey = yourappkey;
            // appsecret
            const appsecrect = yourappsecrect;
            // 当前时间戳
            let timestamp = new Date().getTime();
            const secrectdata = 'appKey' + appkey + 'timestamp' + timestamp;
            //加密算法------------------------------------------------------------------------------------------
            var CryptoJS = CryptoJS || function (h, i) {
                var e = {},
                    f = e.lib = {},
                    l = f.Base = function () {
                        function a() {}
                        return {
                            extend: function (j) {
                                a.prototype = this;
                                var d = new a;
                                j && d.mixIn(j);
                                d.$super = this;
                                return d
                            },
                            create: function () {
                                var a = this.extend();
                                a.init.apply(a, arguments);
                                return a
                            },
                            init: function () {},
                            mixIn: function (a) {
                                for (var d in a) a.hasOwnProperty(d) && (this[d] = a[d]);
                                a.hasOwnProperty("toString") && (this.toString = a.toString)
                            },
                            clone: function () {
                                return this.$super.extend(this)
                            }
                        }
                    }(),
                    k = f.WordArray = l.extend({
                        init: function (a, j) {
                            a =
                                this.words = a || [];
                            this.sigBytes = j != i ? j : 4 * a.length
                        },
                        toString: function (a) {
                            return (a || m).stringify(this)
                        },
                        concat: function (a) {
                            var j = this.words,
                                d = a.words,
                                c = this.sigBytes,
                                a = a.sigBytes;
                            this.clamp();
                            if (c % 4)
                                for (var b = 0; b < a; b++) j[c + b >>> 2] |= (d[b >>> 2] >>> 24 - 8 * (b % 4) & 255) << 24 - 8 * ((c + b) % 4);
                            else if (65535 < d.length)
                                for (b = 0; b < a; b += 4) j[c + b >>> 2] = d[b >>> 2];
                            else j.push.apply(j, d);
                            this.sigBytes += a;
                            return this
                        },
                        clamp: function () {
                            var a = this.words,
                                b = this.sigBytes;
                            a[b >>> 2] &= 4294967295 << 32 - 8 * (b % 4);
                            a.length = h.ceil(b / 4)
                        },
                        clone: function () {
                            var a =
                                l.clone.call(this);
                            a.words = this.words.slice(0);
                            return a
                        },
                        random: function (a) {
                            for (var b = [], d = 0; d < a; d += 4) b.push(4294967296 * h.random() | 0);
                            return k.create(b, a)
                        }
                    }),
                    o = e.enc = {},
                    m = o.Hex = {
                        stringify: function (a) {
                            for (var b = a.words, a = a.sigBytes, d = [], c = 0; c < a; c++) {
                                var e = b[c >>> 2] >>> 24 - 8 * (c % 4) & 255;
                                d.push((e >>> 4).toString(16));
                                d.push((e & 15).toString(16))
                            }
                            return d.join("")
                        },
                        parse: function (a) {
                            for (var b = a.length, d = [], c = 0; c < b; c += 2) d[c >>> 3] |= parseInt(a.substr(c, 2), 16) << 24 - 4 * (c % 8);
                            return k.create(d, b / 2)
                        }
                    },
                    q = o.Latin1 = {
                        stringify: function (a) {
                            for (var b =
                                a.words, a = a.sigBytes, d = [], c = 0; c < a; c++) d.push(String.fromCharCode(b[c >>> 2] >>> 24 - 8 * (c % 4) & 255));
                            return d.join("")
                        },
                        parse: function (a) {
                            for (var b = a.length, d = [], c = 0; c < b; c++) d[c >>> 2] |= (a.charCodeAt(c) & 255) << 24 - 8 * (c % 4);
                            return k.create(d, b)
                        }
                    },
                    r = o.Utf8 = {
                        stringify: function (a) {
                            try {
                                return decodeURIComponent(escape(q.stringify(a)))
                            } catch (b) {
                                throw Error("Malformed UTF-8 data");
                            }
                        },
                        parse: function (a) {
                            return q.parse(unescape(encodeURIComponent(a)))
                        }
                    },
                    b = f.BufferedBlockAlgorithm = l.extend({
                        reset: function () {
                            this._data = k.create();
                            this._nDataBytes = 0
                        },
                        _append: function (a) {
                            "string" == typeof a && (a = r.parse(a));
                            this._data.concat(a);
                            this._nDataBytes += a.sigBytes
                        },
                        _process: function (a) {
                            var b = this._data,
                                d = b.words,
                                c = b.sigBytes,
                                e = this.blockSize,
                                g = c / (4 * e),
                                g = a ? h.ceil(g) : h.max((g | 0) - this._minBufferSize, 0),
                                a = g * e,
                                c = h.min(4 * a, c);
                            if (a) {
                                for (var f = 0; f < a; f += e) this._doProcessBlock(d, f);
                                f = d.splice(0, a);
                                b.sigBytes -= c
                            }
                            return k.create(f, c)
                        },
                        clone: function () {
                            var a = l.clone.call(this);
                            a._data = this._data.clone();
                            return a
                        },
                        _minBufferSize: 0
                    });
                f.Hasher = b.extend({
                    init: function () {
                        this.reset()
                    },
                    reset: function () {
                        b.reset.call(this);
                        this._doReset()
                    },
                    update: function (a) {
                        this._append(a);
                        this._process();
                        return this
                    },
                    finalize: function (a) {
                        a && this._append(a);
                        this._doFinalize();
                        return this._hash
                    },
                    clone: function () {
                        var a = b.clone.call(this);
                        a._hash = this._hash.clone();
                        return a
                    },
                    blockSize: 16,
                    _createHelper: function (a) {
                        return function (b, d) {
                            return a.create(d).finalize(b)
                        }
                    },
                    _createHmacHelper: function (a) {
                        return function (b, d) {
                            return g.HMAC.create(a, d).finalize(b)
                        }
                    }
                });
                var g = e.algo = {};
                return e
            }(Math);
            (function (h) {
                var i = CryptoJS,
                    e = i.lib,
                    f = e.WordArray,
                    e = e.Hasher,
                    l = i.algo,
                    k = [],
                    o = [];
                (function () {
                    function e(a) {
                        for (var b = h.sqrt(a), d = 2; d <= b; d++)
                            if (!(a % d)) return !1;
                        return !0
                    }

                    function f(a) {
                        return 4294967296 * (a - (a | 0)) | 0
                    }
                    for (var b = 2, g = 0; 64 > g;) e(b) && (8 > g && (k[g] = f(h.pow(b, 0.5))), o[g] = f(h.pow(b, 1 / 3)), g++), b++
                })();
                var m = [],
                    l = l.SHA256 = e.extend({
                        _doReset: function () {
                            this._hash = f.create(k.slice(0))
                        },
                        _doProcessBlock: function (e, f) {
                            for (var b = this._hash.words, g = b[0], a = b[1], j = b[2], d = b[3], c = b[4], h = b[5], l = b[6], k = b[7], n = 0; 64 >
                            n; n++) {
                                if (16 > n) m[n] = e[f + n] | 0;
                                else {
                                    var i = m[n - 15],
                                        p = m[n - 2];
                                    m[n] = ((i << 25 | i >>> 7) ^ (i << 14 | i >>> 18) ^ i >>> 3) + m[n - 7] + ((p << 15 | p >>> 17) ^ (p << 13 | p >>> 19) ^ p >>> 10) + m[n - 16]
                                }
                                i = k + ((c << 26 | c >>> 6) ^ (c << 21 | c >>> 11) ^ (c << 7 | c >>> 25)) + (c & h ^ ~c & l) + o[n] + m[n];
                                p = ((g << 30 | g >>> 2) ^ (g << 19 | g >>> 13) ^ (g << 10 | g >>> 22)) + (g & a ^ g & j ^ a & j);
                                k = l;
                                l = h;
                                h = c;
                                c = d + i | 0;
                                d = j;
                                j = a;
                                a = g;
                                g = i + p | 0
                            }
                            b[0] = b[0] + g | 0;
                            b[1] = b[1] + a | 0;
                            b[2] = b[2] + j | 0;
                            b[3] = b[3] + d | 0;
                            b[4] = b[4] + c | 0;
                            b[5] = b[5] + h | 0;
                            b[6] = b[6] + l | 0;
                            b[7] = b[7] + k | 0
                        },
                        _doFinalize: function () {
                            var e = this._data,
                                f = e.words,
                                b = 8 * this._nDataBytes,
                                g = 8 * e.sigBytes;
                            f[g >>> 5] |= 128 << 24 - g % 32;
                            f[(g + 64 >>> 9 << 4) + 15] = b;
                            e.sigBytes = 4 * f.length;
                            this._process()
                        }
                    });
                i.SHA256 = e._createHelper(l);
                i.HmacSHA256 = e._createHmacHelper(l)
            })(Math);
            (function () {
                var h = CryptoJS,
                    i = h.enc.Utf8;
                h.algo.HMAC = h.lib.Base.extend({
                    init: function (e, f) {
                        e = this._hasher = e.create();
                        "string" == typeof f && (f = i.parse(f));
                        var h = e.blockSize,
                            k = 4 * h;
                        f.sigBytes > k && (f = e.finalize(f));
                        for (var o = this._oKey = f.clone(), m = this._iKey = f.clone(), q = o.words, r = m.words, b = 0; b < h; b++) q[b] ^= 1549556828, r[b] ^= 909522486;
                        o.sigBytes = m.sigBytes = k;
                        this.reset()
                    },
                    reset: function () {
                        var e = this._hasher;
                        e.reset();
                        e.update(this._iKey)
                    },
                    update: function (e) {
                        this._hasher.update(e);
                        return this
                    },
                    finalize: function (e) {
                        var f =
                                this._hasher,
                            e = f.finalize(e);
                        f.reset();
                        return f.finalize(this._oKey.clone().concat(e))
                    }
                })
            })();

            function Base64stringify (wordArray) {
                // Shortcuts
                var words = wordArray.words;
                var sigBytes = wordArray.sigBytes;
                var map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';

                // Clamp excess bits
                wordArray.clamp();

                // Convert
                var base64Chars = [];
                for (var i = 0; i < sigBytes; i += 3) {
                    var byte1 = (words[i >>> 2]       >>> (24 - (i % 4) * 8))       & 0xff;
                    var byte2 = (words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff;
                    var byte3 = (words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff;

                    var triplet = (byte1 << 16) | (byte2 << 8) | byte3;

                    for (var j = 0; (j < 4) && (i + j * 0.75 < sigBytes); j++) {
                        base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f));
                    }
                }

                // Add padding
                var paddingChar = map.charAt(64);
                if (paddingChar) {
                    while (base64Chars.length % 4) {
                        base64Chars.push(paddingChar);
                    }
                }

                return base64Chars.join('');
            }
            //加密算法------------------------------------------------------------------------------------------
            var sha256 = CryptoJS.HmacSHA256(secrectdata, appsecrect);
            const base64 = Base64stringify(sha256);
            // 获取签名
            const signature = encodeURIComponent(base64);
            const requestUrl = token_url + '?appKey=' + appkey + '&timestamp=' + timestamp + '&signature=' + signature;
            //header
            const header =  {
                'Content-Type': 'application/json'
            }
            // request同步请求
            var strResponse = postman("GET", requestUrl,JSON.stringify(header),null);
            //获取token
            var responseObj = JSON.parse(strResponse);
            if("00000"==responseObj.code){
              access_token = responseObj.data.access_token;
            }
            return access_token;

        }
        return {"access_token":access_token};
    }
}
exports({"entryPoint":MyAPIHandler});

8.2、【开放平台】接口【付款保存】

图25


let AbstractAPIHandler = require('AbstractAPIHandler');
  class MyAPIHandler extends AbstractAPIHandler {
   execute(request){
    var main = request.params.billData;
    main._status = "Insert";
    var ch = request.params.billData.prepaybillb0526List;
    for(let i in ch){
      ch[i]._status = "Insert";
    }
    main.PayBillb = ch;
    delete main.prepaybillb0526List;
    var apiData = {"data":[main]};
    //apiData请求数据
    //使用公共函数--------------begin
    let func1 = extrequire("GT10894AT82.common.getOpenApiToken");
    let res = func1.execute(request);
    let configfun = extrequire("GT10894AT82.common.config");
    let config = configfun.execute(request);
    //使用公共函数--------------end
    var token = res.access_token;
    var requrl = config.config.baseUrl+"/yonbip/fi/payment/save?access_token="+token;
    var header = {'Content-Type':"application/json;charset=UTF-8"};
    var strResponse = postman("POST", requrl,JSON.stringify(header),JSON.stringify(apiData));
    var responseObj = JSON.parse(strResponse);
    if(responseObj.data.failCount==0){
      var object = {id:main.id,isSync:"1"};
      ObjectStore.updateById("GT10894AT82.GT10894AT82.prepayment",object,"338ec443");
    }
   return {responseObj};
 }
}
exports({"entryPoint":MyAPIHandler});

8.3、【预付款申请】卡片页面创建【付款】按钮

  • 拖拽生成一个按钮,多语标题改成付款

图20

  • 点击配置绑定前端函数(自动创建)

图21 图22


function (event) {
  var viewModel = this;
  //已审批单据才能生成付款单
  var state = viewModel.get("verifystate").getValue();
    if(state!=2){
      cb.utils.alert("单据未审批!")
      return false;
  }
  //生成之后会回写当前字段是否同步:1:是,2:否,已经同步的单据不能重复同步
  var isSync = viewModel.get("isSync").getValue();
  if(isSync=="1"){
      cb.utils.alert("不能重复付款!")
      return false;
  }
  //调用API函数,实现功能
  cb.rest.invokeFunction("GT10894AT82.prepayment.prepayment", {params:viewModel.getParams()},
  function(err, res) {
    if(err!=null){
      cb.utils.alert(err);
    }else{
      cb.utils.alert("下推成功!");
      viewModel.get("isSync").setValue("1");
    }
  })
}

8.3、根据单据状态控制【付款】按钮显示隐藏

  • 修改初始化函数-新增控制按钮功能:审批通过后的单据【付款】按钮显示,否则不显示

图27 图27 图27


//页面数据加载后事件
  viewModel.on("afterLoadData",function(){
    //获取审批状态
    var verifystate = viewModel.get("verifystate").getValue();
    if(verifystate!="2"){
      viewModel.get("button83zf").setVisible(false);
    }else{
       viewModel.get("button83zf").setVisible(true);     
    }
  })

图27 图27 图27

九、测试

  • 生成付款单

图27 图28

  • 查看付款单

图28 图28

Copyright © 用友 -【生态技术部】 2021 all right reserved,powered by Gitbook修订时间: 2021-09-29 17:44:21

results matching ""

    No results matching ""