如何逐步构建采购订单Web API保存接口参数
金蝶云社区-LAW先生
LAW先生
16人赞赏了该文章 2785次浏览 未经作者许可,禁止转载编辑于2018年05月11日 18:19:33

需求背景:
K/3 Cloud提供了Web API接口,供第三方程序调用,以实现与第三方系统之间的数据集成。
调用Web API接口虽然简单,但是构建参数却非常复杂,与各单据上的字段密切相关。

K/3 Cloud内置了一个Web API接口说明功能,提供了示例代码,演示如何调用各单据的各种操作。
遗憾的是,示例代码中的参数,包含了全部单据头字段,但没有包括单据体字段,完全按照此示例代码调用接口,往往会保存失败。

案例说明:
本案例,基于采购订单的保存接口,演示如何构建各种字段参数,单据体字段参数。
以便读者在K/3 Cloud内置的Web API接口示例代码基础上,据此类推,自行构建出各种单据,Web API保存接口需要的,包含了单据体字段的完整参数。

为方便阅读,本案例仅构建了采购订单必须的字段参数,其他类似的字段,直接忽略。

示例代码:
//*********************************************************
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using Newtonsoft.Json.Linq;
using Kingdee.BOS.WebApi.Client; 
//*********************************************************

        /// <summary>
        /// 采购订单
        /// </summary>
        private string Sample2015121401()
        {
            try
            {
                // K/3 Cloud业务站点URL
                // using Kingdee.BOS.WebApi.Client; // (需引用Kingdee.BOS.WebApi.Client.dll)
                ApiClient client = new ApiClient(this.txtWebSite.Text);

                // 调用登录接口:
                // 参数说明:
                // dbid     : 数据中心id。到管理中心数据库搜索:
                //            select FDataCenterId, * from T_BAS_DataCenter
                // userName : 用户名
                // password :原始密码(未加密)
                // loid     : 语言id,中文为2052,中文繁体为3076,英文为1033
                var loginResult = client.Login(
                    this.txtDbId.Text,
                    this.txtUser.Text,
                    this.txtPassword.Text,
                    2052);
                string result = "登录失败,请检查与站点地址、数据中心Id,用户名及密码!";

                // 登陆成功,开始保存数据
                if (loginResult == true)
                {
                    // 开始构建Web API参数对象
                    // 参数根对象:包含Creator、NeedUpDateFields、Model这三个子参数
                    // using Newtonsoft.Json.Linq;  // (需引用Newtonsoft.Json.dll)
                     JObject jsonRoot = new JObject();

                    // Creator: 创建用户
                    jsonRoot.Add("Creator", "Demo");

                    // NeedUpDateFields: 哪些字段需要更新?为空则表示参数中全部字段,均需要更新
                    jsonRoot.Add("NeedUpDateFields", new JArray(""));

                    // Model: 单据详细数据参数
                    JObject model = new JObject();
                    jsonRoot.Add("Model", model);

                    // 开始设置单据字段值
                    // 必须设置的字段:主键、单据类型、主业务组织,各必录且没有设置默认值的字段
                    // 特别注意:字段Key大小写是敏感的,建议从BOS设计器中,直接复制字段的标识属性过来

                    // 单据主键:必须填写,系统据此判断是新增还是修改单据;新增单据,填0
                    model.Add("FID", 0);

                    // 采购组织:必须填写,是基础资料字段
                    JObject basedata = new JObject();
                    basedata.Add("FNumber", "101.2");
                    model.Add("FPurchaseOrgId", basedata);

                    // 单据类型:必须填写,是基础资料字段
                    // 基础资料类型字段填值,必须再构建一个JObject对象,设置基础资料编码
                    basedata = new JObject();
                    basedata.Add("FNumber", "CGDD01_SYS");
                    model.Add("FBillTypeID", basedata);

                    // 单据编号:可以忽略,由系统根据编码规则自动生成
                    model.Add("FBillNo", "JDCGDD1605190001");

                    //采购日期(FDate)
                    model.Add("FDate", DateTime.Today);

                   // 录入业务上要求必须提前设置的字段
                    // 这些字段通常无需由用户手工录入,提前设好默认值,如汇率类型、本位币等


                    // 汇率类型(FExchangeTypeId):基础资料
                    basedata = new JObject();
                    basedata.Add("FNumber", "HLTX01_SYS");
                    model.Add("FExchangeTypeId", basedata);

                    // 本位币(FLocalCurrId):基础资料
                    basedata = new JObject();
                    basedata.Add("FNumber", "PRE001");
                    model.Add("FLocalCurrId", basedata);

                    // 按手工录入顺序,录入其他关键业务字段

                    // 供应商(FSupplierId):基础资料
                    basedata = new JObject();
                    basedata.Add("FNumber", "VEN00002");
                    model.Add("FSupplierId", basedata);


                    // 其他单据头字段,非必录,本示例代码忽略


                    // 开始构建单据体参数:集合参数JArray

                    JArray entryRows = new JArray();
                    // 把单据体行集合,添加到model中,以单据体Key为标识
                    string entityKey = "FPOOrderEntry";
                    model.Add(entityKey, entryRows);


                   // 通过循环创建单据体行:示例代码仅创建一行
                    for (int i = 0; i <= 0; i++)
                    {
                        // 添加新行,把新行加入到单据体行集合
                        JObject entryRow = new JObject();
                        entryRows.Add(entryRow);


                        // 给新行,设置关键字段值
                        // 单据体主键:必须填写,系统据此判断是新增还是修改行
                        entryRow.Add("FEntryID", 0);

                        //物料(FMaterialId):基础资料,填写编码
                        basedata = new JObject();
                        basedata.Add("FNumber", "1.01.001.0070");
                        entryRow.Add("FMaterialId", basedata);

                        // 单位(FUnitId):基础资料,填写编码
                        basedata = new JObject();
                        basedata.Add("FNumber", "Pcs");
                        entryRow.Add("FUnitId", basedata);

                        // 数量(FQty):数量字段
                        entryRow.Add("FQty", 10);

                        // 单价(FPrice):单价字段
                        entryRow.Add("FPrice", 11.7);

                        // 其他单据体字段,非必录,本示例代码忽略

                        // 创建与源单之间的关联关系,以支持上查与反写源单
                        // 本示例演示创建与采购申请单之间的关联关系
                        // 源单类型、源单编号
                        entryRow.Add("FSrcBillTypeId", "PUR_Requisition");
                        entryRow.Add("FSrcBillNo", "CGSQ000009");

                        // 创建Link行集合
                        JArray linkRows = new JArray();

                        // 添加到单据体行中:Link子单据体标识 = 关联主单据体标识(POOrderEntry) + _Link
                        string linkEntityKey = string.Format("{0}_Link", entityKey);
                        entryRow.Add(linkEntityKey, linkRows);

                        // 创建Link行:
                        // 如有多条源单行,则分别创建Link行记录各条源单行信息
                        JObject linkRow = new JObject();
                        linkRows.Add(linkRow);

                        // 填写Link行上的字段值
                        // 特别说明:Link子单据体上字段的标识,必须在前面增加子单据体标识

                        // FFlowId : 业务流程图,可选
                        string fldFlowIdKey = string.Format("{0}_FFlowId", linkEntityKey);
                        linkRow.Add(fldFlowIdKey, "");

                        // FFlowLineId :业务流程图路线,可选
                        string fldFlowLineIdKey = string.Format("{0}_FFlowLineId", linkEntityKey);
                        linkRow.Add(fldFlowLineIdKey, "");

                        // FRuleId :两单之间的转换规则内码,必填
                        // 可以通过如下SQL语句到数据库获取
                        // select FID, *
                        //   from T_META_CONVERTRULE 
                        //  where FSOURCEFORMID = 'PUR_Requisition' 
                        //    and FTARGETFORMID = 'PUR_PurchaseOrder' 
                        //    and FDEVTYPE = 0;
                        string fldRuleIdKey = string.Format("{0}_FRuleId", linkEntityKey);
                        linkRow.Add(fldRuleIdKey, "PUR_Requisition-PUR_PurchaseOrder");

                        // FSTableName :必填,源单单据体表格编码,通过如下语句获取:
                        // SELECT FTableNumber 
                        //   FROM t_bf_tabledefine 
                        //  WHERE fformid = 'PUR_Requisition' 
                        //    AND fentitykey = 'FEntity'
                        // 如果如上语句未返回结果,请到K/3 Cloud中,手工选单一次,后台会自动产生表格编码
                        string fldSTableNameKey = string.Format("{0}_FSTableName", linkEntityKey);
                        linkRow.Add(fldSTableNameKey, "T_PUR_ReqEntry");

                        // FSBillId :必填,源单单据内码
                        string fldSBillIdKey = string.Format("{0}_FSBillId", linkEntityKey);
                        linkRow.Add(fldSBillIdKey, 100011);

                        // FSId : 必填,源单单据体行内码。如果源单主关联实体是单据头,则此属性也填写源单单据内码
                        string fldSIdKey = string.Format("{0}_FSId", linkEntityKey);
                        linkRow.Add(fldSIdKey, 100029);

                        // FEntity_Link_FBaseQtyOld :数量原始携带值,下推时,从源单带了多少下来                        // 在合并下推时,系统会把多个源行的数量,合并后填写在单据体数量字段上的;
                        // 合并后对各源单行的反写,需要使用合并前的数量进行反写,也就是本字段上记录的数量
                        // 因此,如果有合并下推,本字段就必须填写,否则反写不准确
                        // 如果没有合并下推,单据体数量会直接反写到唯一的源行上,本字段就不需填写
                        string fldBaseQtyOldKey = string.Format("{0}_FBaseUnitQtyOld", linkEntityKey);
                        linkRow.Add(fldBaseQtyOldKey, 10);

                        // FEntity_Link_FBaseQty :数量实际携带值,下推后,用户可以手工修改数量值;此字段存储最终的数量值
                        // 可选字段:
                        // 在保存时,系统会自动把单据体上数量值,更新到此字段;因此,这个字段可以不用填写(即使填写了,也会被覆盖)
                        string fldBaseQtyKey = string.Format("{0}_FBaseUnitQty", linkEntityKey);
                        linkRow.Add(fldBaseQtyKey, 10);

                    }

                   // 调用Web API接口服务,保存采购订单
                    result = client.Execute<string>(
                        "Kingdee.BOS.WebApi.ServicesStub.DynamicFormService.Save",
                        new object[] {"PUR_PurchaseOrder", jsonRoot.ToString() });
                }

                return result;
            }
            catch(Exception exp)
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine("程序运行遇到了未知的错误:");
                sb.Append("错误提示:").AppendLine(exp.Message);
                sb.Append("错误堆栈:").AppendLine(exp.StackTrace);
                return sb.ToString();
            }
        }

如何分析调用Web API,字段值没有赋值成功?

需求背景:
调用Web API保存单据时,常常出现在参数中,明明已经设置了字段值,但是却没有效果,返回结果还是提示该字段必录。

发生此问题的原因,通常有如下几个方面:
1. 字段Key值弄错:字段Key是大小写敏感的,必须严格按照BOS设计器中设定的字段标识属性设置参数;
2. 字段值不被认可:如给基础资料字段赋值,填写的编码不存在,或未分配到目标组织,或未审核、已禁用等;
3. 字段赋值顺序不对:某些字段有依赖字段,必须在依赖字段赋值后,才能赋值,需要调整Web API中字段参数顺序;
4. 字段值被其他字段的值更新事件覆盖:此点最难确认,本帖提供一个插件代码来解决。

案例说明:
本帖提供一个插件代码,挂到单据表单插件上,捕获字段改变DataChanged事件,输出字段及其字段值。
最终效果,会按顺序输出全部字段的赋值过程,据此可以追查出,整个Web API调用过程:
1. 字段有没有被赋值?
2. 字段赋值是否正确?
3. 字段有没有多次赋值,值由正确值变为无效值,即被其他字段覆盖了?

此插件也适合调用WebService接口保存单据。

日志输出效果:
 
8.png示例代码:
本插件代码,需要编译后,挂到单据的表单插件上,本地验证OK。
//*********************************************

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

using Kingdee.BOS;
using Kingdee.BOS.Util;
using Kingdee.BOS.Core;
using Kingdee.BOS.Core.DynamicForm.PlugIn;
using Kingdee.BOS.Core.DynamicForm.PlugIn.Args;
using Kingdee.BOS.Core.Bill;
using Kingdee.BOS.Core.Bill.PlugIn;
using Kingdee.BOS.Core.Bill.PlugIn.Args;
using Kingdee.BOS.Core.Metadata.FieldElement;
using Kingdee.BOS.Orm.DataEntity;
namespace JDSample.WatchImport
{
    /// <summary>
    /// 监控调用Web API接口保存单据时,给字段赋值过程
    /// </summary>
    /// <remarks>
    /// 背景说明:
    /// 调用Web API或者WebService接口,保存单据;
    /// 经常发生参数中已经设置了字段值,但是保存结果,却提示字段必录;
    /// 发生此现象时,非常难以排查问题;
    /// 
    /// 解决方案:
    /// 发生此现象时,可以把本插件,挂在单据表单插件上;
    /// 本插件监控到单据被Web API接口,设置单据字段值时,
    /// 会即时把字段的历史数值、新数据输出到K/3 Cloud日志文件中,
    /// 通过日志文件,就可以了解字段赋值的顺序、所填写的值,
    /// 确认出字段为何没有填写上:
    /// 1. 字段未接收到值:字段Key错误,或字段值不合法
    /// 2. 被其他字段覆盖了:字段录入顺序需要调整
    /// </remarks>
    [Description("Web API接口赋值监控")]
    public class Watch : AbstractBillPlugIn
    {
        /// <summary>
        /// 当前进程,是否为WebService、Web API方式调用;
        /// 只有WebService调用,才输出日志
        /// </summary>
        bool _isWebService = false;
        /// <summary>
        /// 界面初始化事件:判断当前进程是否为WebService调用
        /// </summary>
        /// <param name="e"></param>
        public override void OnInitialize(InitializeEventArgs e)
        {
            if (this.Context.ServiceType == WebType.WebService)
            {
                this._isWebService = true;
            }
        }
        /// <summary>
        /// 单据数据包创建、加载完毕:在日志中,输出一个开始语句,以便阅读
        /// </summary>
        /// <param name="e"></param>
        public override void AfterCreateNewData(EventArgs e)
        {
            if (this._isWebService == false)
            {
                return;
            }
            string message = string.Format("现在开始创建单据{0}的新数据包",
                this.View.BillBusinessInfo.GetForm().Name.ToString());
            Kingdee.BOS.Log.Logger.Info("WebService", message);
        }
        /// <summary>
        /// 值改变事件:监控字段值修改顺序及值
        /// </summary>
        /// <param name="e"></param>
        public override void DataChanged(DataChangedEventArgs e)
        {
            if (this._isWebService)
            {
                string message = this.BuildFldChangedLog(e.Field, e.NewValue, e.OldValue);
                Kingdee.BOS.Log.Logger.Info("WebService", message);
            }
        }
        /// <summary>
        /// 保存前调用此事件:可以通过此事件,观察单据数据包中的字段值
        /// </summary>
        /// <param name="e"></param>
        public override void BeforeSave(BeforeSaveEventArgs e)
        {
            base.BeforeSave(e);
        }
        /// <summary>
        /// 构建字段的修改日志
        /// </summary>
        /// <param name="field"></param>
        /// <param name="newValue"></param>
        /// <param name="oldValue"></param>
        /// <returns></returns>
        private string BuildFldChangedLog(Field field, object newValue, object oldValue)
        {
            string newValueString = Convert.ToString(newValue);
            string oldValueString = Convert.ToString(oldValue);
            string logMessage = string.Format("给字段【{0}({1})】赋值为 \"{2}\";旧值为\"{3}\"{4}",
                field.Name.ToString(),
                field.Key,
                string.IsNullOrWhiteSpace(newValueString) ? "(空值)" : newValueString,
                string.IsNullOrWhiteSpace(oldValueString) ? "(空值)" : oldValueString,
                string.IsNullOrWhiteSpace(newValueString) ? "【有异常,清空了字段值!】" : string.Empty);
            return logMessage;
        }
    }
}

赞 16