--使用sp_addlinkedserver增加链接
EXEC sys.sp_addlinkedserver 
     @server='127.0.0.1', --被访问的服务器别名(习惯上直接使用目标服务器IP,或者取别名)
	 @srvproduct='',
	 @provider='SQLOLEDB',
	 @datasrc='127.0.0.1'--要访问的服务器IP(SQL Server实例名)

--使用sp_addlinkedsrvlogin来增加用户登录链接
EXEC sys.sp_addlinkedsrvlogin
    '127.0.0.1',--被访问的服务器别名(即上面sp_addlinkedserver中@server的值)
	'false',
	NULL,
	'sa',--用户名
	'123456'--密码


前言

随着互联网技术的发展,基于B/S结构的软件架构,呈现出多样化,所涉及到的富客户端、Webservice、WEB 2.0、HTML5等技术也粉墨登场,本文将介绍基于OpenText Cordys产品的SOA体系架构和相关开发技术,为开发人员分享另一种软件系统开发实践。


其中,富客户模型将界面分解成许多的既可以和用户直接交互又可以和服务器进行通信的小单元模块,所涉及到开发语言是JavaScript,以及围绕HTML DOM开发动态网页。


关于SOA

SOA是面向服务的体系结构,它将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接口和契约联系起来。接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台、操作系统和编程语言。这使得构建在各种各样的系统中的服务可以使用一种统一和通用的方式进行交互。


面向服务架构,它可以根据需求通过网络对松散耦合的粗粒度应用组件进行分布式部署、组合和使用。服务层是SOA的基础,可以直接被应用调用,从而有效控制系统中与软件代理交互的人为依赖性。


SOA是一种粗粒度、松耦合服务架构,服务之间通过简单、精确定义接口进行通讯,不涉及底层编程接口和通讯模型。SOA可以看作是B/S模型、XML(标准通用标记语言的子集)/Web Service技术之后的自然延伸。


SOA的核心要素

要准确全面理解SOA,首先必须理解SOA的核心要素:




SOA的目标就是实现灵活可变的IT系统。要达到灵活性,通过三个途径来解决:标准化封装、复用、松耦合可编排。


SOA实践

在工程上,SOA的重点是服务建模和基于SOA的设计原则进行架构决策和设计。


从建模和设计的角度来说,SOA更多地侧重在业务层次上,也就是通过服务建模将业务组件化为服务模型,它是业务架构的底层,是技术架构的顶层,承上启下,是灵活的业务模型和IT之间的桥梁,保证二者之间的”可追溯性”。从这里往下,是基于已有的方法,比如OO/CBD来进行的。从架构的层次上,SOA更多地侧重于如何将企业范围内多个分布的系统(包括已有系统/遗留系统)连接起来(ESB,Adapter/Connector),如何将它们的功能、数据转化为服务,如何通过服务中介机制(ESB,Service Registry)保证服务之间以松散耦合的方式交互,如何组装(集成)服务为流程,如何管理服务和流程等。


SOA的架构特性,使得敏捷过程非常适合SOA项目的实施。在SOA架构中,服务的独立性,使得每个服务可以被单独地开发、测试和集成。一个企业中的IT系统,如果是基于SOA的计算环境,那么这个环境就是一个服务的生态系统,每开发一个服务,马上就可以独立部署,成为这个生态系统中的一部分。这样既很好地支持了持续集成、持续质量保证,又很好地使得这个服务马上产生业务价值,而不是苦等其他服务的到位。


服务的松耦合与无状态

松耦合——服务的粗细粒度:

一般主张使用粗粒度

显然,为了实现最大程度重用,创建细粒度的服务也是必要的。

无状态

服务应该是独立的、自包含的请求,在实现时它不需要获取从一个请求到另一个请求的信息或状态。服务不应该依赖于其他服务的上下文和状态。


服务设计简单化

提升了运算能力和效率

重用性

Web service

Web service是一个平台独立的,低耦合的,自包含的、基于可编程的web的应用程序,可使用开放的XML(标准通用标记语言下的一个子集)标准来描述、发布、发现、协调和配置这些应用程序,用于开发分布式的互操作的应用程序。


WebService三要素:

SOAP用来描述传递信息的格式;

WSDL(WebServicesDescriptionLanguage) 用来描述如何访问具体的接口;

UDDI(UniversalDescriptionDiscovery andIntegration)用来管理,分发,查询。


Web Services 的理解及SOAP、WSDL、UDDI的关系

Web Services 是一个可以将应用程序变为web应用程序,将自己本地的应用程序信息通过网络,发布到网络当中,让别人通过浏览器等访问本地的信息。


SOAP 是定义访问Web Services 的协议,也就是哪些是可以访问的,怎样的格式才能够访问,返回的格式又是什么样的,这些都是SOAP定义的。

WSDL 是描述SOAP协议的具体语言,用WSDL实现SOAP协议,把它写成文件,直接访问。

UDDI,是把这些web services 收集和存储起来,这样当别人访问这些信息的时候就从UDDI中查找,看有没有这个信息存在。

简单对象访问协议 (SOAP)

简单对象访问协议是交换数据的一种协议规范,是一种轻量的、简单的、基于XML的协议,它被设计成在WEB上交换结构化的和固化的信息。通常,使用 HTTP 作为传输协议。


SOAP 消息包含以下元素:


Envelope:必需的元素,用于将文档标识为 SOAP 消息

Header:包含应用程序特定的信息

Body:必需的元素,定义调用和响应信息

Fault:包含有关出现的错误的信息

SOAP 内容可由 WSDL 文件确定。

关于WEB服务和网页

WEB服务

Apache HTTP Server(简称Apache),是Apache软件基金会的一个开放源代码的网页服务器,可以在大多数电脑操作系统中运行,由于其具有的跨平台性和安全性,被广泛使用,是最流行的Web服务器端软件之一。


HTML网页

HTML DOM

HTML DOM是HTML Document Object Model(文档对象模型)的缩写,HTML DOM则是专门适用于HTML/XHTML的文档对象模型。熟悉软件开发的人员可以将HTML DOM理解为网页的API。它将网页中的各个元素都看作一个个对象,从而使网页中的元素也可以被计算机语言获取或者编辑。 例如Javascript就可以利用HTML DOM动态的修改网页。


HTML网页与WEB服务

HTML网页部署在Apache HTTP Server上,并可以携带JavaScript编写的文件(.JS),以及样式文件(.CSS)、图片等。


OpenText Cordys BOP 4 产品架构

Cordys BOP是完全构建在标准、前瞻的架构之上,从一开始就设计成业务运营的支撑平台。客户可以利用BOP平台的SOA架构和完整的BPM能力,并与现有或新建的共享服务组合在一起,具有优异的兼容性,构建完整的业务运营环境。Cordys BOP不仅满足面向业务层面的流程建模与设计,还包括将业务流程真正落地实现,从而驱动企业内部的创新、敏捷性。




产品重要构成:


SOA Grid:包含通常意义上的ESB模块,以及一些额外的功能,如安全文档传输与数据同步。由于Cordys是一个彻底的SOA体系架构,采用标准的Web服务,因此上层的BPM和CAF也可以不用Cordys自身的ESB,而是和第三方的ESB无缝集成,对第三方ESB开放出来的Web服务,进行组合与流转;

BPM:一体化的建模、执行、监控环境,也就是通常意义上的业务流程管理工具。

产品主要特点:


一体化平台,包含了集成、业务流程管理以及复合应用开发

通过协同工作工间真正实现了业务与IT人员的协同

支持任何类型的流程,包括工作流、集成流程以及混合流程,并提供端到端的监控

完全支持开放标准,提供了高可用和可线性扩展的架构

Web访问原理

了解基于OpenText Cordys产品的Web访问原理,有助于设计、开发、运维等工作,也是建立另一种思维体系——简洁的SOA架构体系。




如上图Web访问过程图所示,简单描述如下:


客户端使用浏览器向Web服务器(Apache HTTP)发Soap请求(SOAP Request);

Web服务器通过Web Gateway,到LDap服务中查询Webservice接口(Webservice interface)查询提交是命名空间(Namespace)和操作方法(operation);

通过Webservice接口获取服务组(Service Group);

再获取服务连接点(Connection Point);

再通过连接点连接到服务容器(Service Container);

服务容器内包括应用连接器(Application Connector);

应用连接器连接到后台应用服务;

最后,返回Soap响应(SOAP Response)到客户端。

开发技术与系统架构层次

基于OpenText Cordys产品的SOA技术架构,相对来说是比较简洁方案,笔者所了解的SOA方案中,这个Cordys SOA方案是最简洁者之一。




Less Coding

在CORDYS上进行开发,与传统的基于J2EE或者.Net这样纯粹的代码开发环境有很大不同。一个最大的不同点就在于Less Coding这样的理念。传统的开发方式是基于三层架构体系完成的,即数据层、逻辑层、与展现层。中间的逻辑层需要且只能通过大量编码的形式完成。而CORDYS采用的是基于SOA与BPM的多层体系,除了部分Web Service的编写和页面Javascript需要涉及代码编程之外,其他各个部分的实现和开发均以建模为主。


CORDYS还原生提供多租户的支持,无需开发人员特意写代码,只要在CORDYS上开发的应用,本质上都是SaaS应用。


关于云计算支持

Gartner划分多租户模型如下图所示:




第7种多租户模型必须要在单个应用内部加入区分租户的业务逻辑,只能提供单一应用内部的多租户,而无法提供一个能开发各种多租户应用的开发和运行平台。CORDYS提供的是开发和运行平台,可以提供从第1-6种的多租户平台部署和技术支撑。


WS-AppServer数据模型

WS-AppServer数据模型有如下三种:标准数据模型、自定义类数据模型、复合类数据模型。


标准数据模型(Standard Class)

标准的数据模型直接映射数据库的表结构,在创建WS-AppServer Package时直接从数据库导入自动生成,每一个表创建一个Class并且存储在WS-AppServer Package中,表中每个字段对应Class中的一个属性。



自定义类数据模型(Custom Class)

自定义类模型是在标准模型无法满足特定业务逻辑需求的时候自定义的灵活类,既可以基于数据库,也可以不依赖于数据库,用户可以任意添加,修改,扩展业务逻辑来满足当前的需求。




复合类数据模型(Composite Class)

复合类是在标准类的基础上由多个标准类组合而成的复合类,在1:1,1:N,N:1的特定场合下特别适合使用。


生产运维及开发生态环境

生产运维环境

基于SOA架构体系,最小生产运维环境由生产环境、测试环境、运维开发环境构成。




开发环境

开发环境依赖配置管理服务器,控制代码在Cordys平台上和本地客户端(Eclipse)间传输,并保障代码版本一致。




参考:


1.部分资料来源于原Cordys培训材料。

————————————————

版权声明:本文为CSDN博主「肖永威」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/xiaoyw71/article/details/46484743


Entity, EntryEntity, SubEntryEntity 这三个对象具有继承关系:
Entity 是实体基类,用于定义各种实体的公共属性;
EntryEntity 是单据体实体类,从Entity派生,增加了单据体的一些特性;
SubEntryEntity 是子单据体实体类,从EntryEntity派生,增加了子单据体的一些特性;

BusinessInfo.GetEntity()方法,返回的是Entity类型,此类已经包含了实体的ORM属性定义,利用此类,完全就可以到单据数据包中获取到数据,无需转换为EntryEntity。

通常情况下,没有Model时,可以使用Entity.DynamicProperty.GetValue(obj)方法,获取单据体的DynamicObjectCollection;

单据转换插件各事件中,生成的下推结果,为了方便插件取数,提供了另外一种方式获取单据体行。

如下演示代码,介绍了在单据转换插件中,两种获取单据体行数据的方法:
//**********************************************
public override void AfterConvert(AfterConvertEventArgs e)
{
    // 方法一:直接获取生成的全部单据体行,不区分单据

    // 此方法特别适用单据转换插件,非常方便
    ExtendedDataEntity[] allEntryRows = e.Result.FindByEntityKey("FEntity");
    foreach (var entryRowDataEntity in allEntryRows)
    {
        DynamicObject row = entryRowDataEntity.DataEntity;
        // TODO: xxxx
    }


    // 方法二:逐个单据循环,获取其单据体行
    // 此方法比较通用
    Entity entity = e.TargetBusinessInfo.GetEntity("FEntity");
    // 获取生成的全部单据
    ExtendedDataEntity[] billDataEntities = e.Result.FindByEntityKey("FBillHead");

    // 对单据做循环,逐单读取单据体
    foreach (var billDataEntity in billDataEntities)
    {

        // 如下是比较通用的方法,利用entity读取单据体
        DynamicObjectCollection entryRows = entity.DynamicProperty.GetValue(
                    billDataEntity.DataEntity) as DynamicObjectCollection;
        foreach (DynamicObject row in entryRows)
        {
            // TODO: xxxx
        }
    }
}

原贴地址 http://club.kisdee.com/forum.php?mod=viewthread&tid=967037&source=solr_search&word=ENTITY


单据转换插件事件执行顺序:
1、下推事件及顺序
//初始化变量
OnInitVariable(InitVariableEventArgs e)
//解析字段映射关系,并构建查询参数。这里可以加入你想要的额外的字段
OnQueryBuilderParemeter(QueryBuilderParemeterEventArgs e)
//构建列表中选择数据行的In语句
OnInSelectedRow(InSelectedRowEventArgs e)
//解析选单条件策略
OnParseFilter(ParseFilterEventArgs e)
//获取源单数据
OnGetSourceData(GetSourceDataEventArgs e) (下推执行)
//执行分组前
OnBeforeGroupBy(BeforeGroupByEventArgs e)
//创建目标单
OnCreateTarget(CreateTargetEventArgs e) (下推执行)
//字段映射开始
OnBeforeFieldMapping(BeforeFieldMappingEventArgs e)
//单个字段映射
OnFieldMapping(FieldMappingEventArgs e)
//计算公式映射
OnFieldCalculate(FieldCalculateEventArgs e)
//所有字段映射完成
OnAfterFieldMapping(AfterFieldMappingEventArgs e)
//关联关系(Link表)创建前
OnCreateLink(CreateLinkEventArgs e)
//关联关系(Link表)创建完成
OnAfterCreateLink(CreateLinkEventArgs e)
//单据转换后事件
AfterConvert(AfterConvertEventArgs e)
2、选单前事件及顺序(弹出选单列表前)
OnInitVariable(InitVariableEventArgs e)
//解析映射关系中的过滤选项
OnParseFilterOptions(ParseFilterOptionsEventArgs e)(选单执行)
OnParseFilter(ParseFilterEventArgs e)
3、选单事件及顺序
OnInitVariable(InitVariableEventArgs e)
OnQueryBuilderParemeter(QueryBuilderParemeterEventArgs e)
OnInSelectedRow(InSelectedRowEventArgs e)
//获取源单数据
OnGetDrawSourceData(GetDrawSourceDataEventArgs e)(选单执行)
OnBeforeGroupBy(BeforeGroupByEventArgs e)
//目标单创建后
OnCreateDrawTarget(CreateDrawTargetEventArgs e)(选单执行)
OnBeforeFieldMapping(BeforeFieldMappingEventArgs e)
OnFieldMapping(FieldMappingEventArgs e)
OnFieldCalculate(FieldCalculateEventArgs e)
OnAfterFieldMapping(AfterFieldMappingEventArgs e)
OnCreateLink(CreateLinkEventArgs e)
OnAfterCreateLink(CreateLinkEventArgs e)
//单据转换后事件
AfterConvert(AfterConvertEventArgs e)

 

// 单据查看过程插件事件顺序
1 PreOpenForm 动态表单打开前事件
2 OnInitializeService 服务初始化
3 OnSetBusinessInfo 处理逻辑元数据
4 OnSetLayoutInfo 处理外观元数据
5 OnCreateDataBinder 创建数据绑定器事件
6 OnInitialize 页面初始化
7 OnBillInitialize
8 LoadData
9 AfterLoadData
10 BeforeBindData
11 AfterBindData

// 单据新增过程插件事件顺序
1 PreOpenForm 动态表单打开前事件
2 OnInitializeService
3 OnSetBusinessInfo
4 OnSetLayoutInfo
5 OnCreateDataBinder 创建数据绑定器事件
6 OnInitialize 页面初始化
7 OnBillInitialize
8 CreateNewData 动态表单数据包创建
9 AfterCreateNewData 模型层数据包创建
10 AfterCreateModelData 模型层数据包创建完毕
11 OnLoad 页面加载
12 BeforeBindData 绑定数据前事件
13 AfterBindData 绑定数据及控件状态


开发工具

       Visual studio 2012

       IE插件Silverlight5

       SQLServer 2008R2 或 Oracle 11G R2

       跟踪工具(HttpWatchPro6.0)

       插件Building路径(K3Cloud\K3CloudServer\Bin\)

注意事项:

使用SQLServer2008排序规则为Chinese_PRC_CI_AS

使用Oracle时,数据库字符集必须是:AL32UTF8,国家字符集必须是:AL16UTF16

开环境搭建

公共环境:

1.    配置一台数据库服务器,安装SQLServer2008R2;

2.    配置一台web服务器,安装K/3Cloud产品,配置为管理中心站点;

个人环境:

1.    根据环境配置要求,安装visual studio,安装K/3Cloud产品(不需要配置管理中心)。

2.    检查并更改管理中心地址,

打开K/3Cloud产品安装目录K3Cloud\K3CloudServer\App_Data下Common.config文件,查找managementSiteUrl,把地址更改为公共环境下建立的管理中心ip。

<addkey="managementSiteUrl"value="http://192.168.73.40:8000/" />

开发插件的步骤

插件开发的步骤

1.    定义插件类

打开Visual studio 2012,新建工程:

MyDev.K3.SCM.Stock.Business.PlugIn;

添加引用组件:

Kingdee.BOS

Kingdee.BOS.Core

新建类:

ReceiptEdit,继承自Kingdee.BOS.Core.Bill.PlugIn.AbstractBillPlugIn

2.    分析业务定义重载方法;

这里,我们先简单实现一个Hello World:

点击菜单HelloWorld,弹出一个Hello World对话框。

点击菜单要重载BarItemClick方法;

3.    引用相关组件(参照组件引用规则);

增加using:

C#


using Kingdee.BOS.Core.Bill.PlugIn;using Kingdee.BOS.Core.DynamicForm;using Kingdee.BOS.Core.DynamicForm.PlugIn.Args;

4.    重载方法编码;

重载BarItemClick方法,输入以下代码:

C#


public override void BarItemClick(BarItemClickEventArgs e){base.BarItemClick(e);if (e.BarItemKey == "HelloWorld"){this.View.ShowMessage("Hello world!",MessageBoxType.Notice);}}

保存;

5.    设置编译路径,编译组件;

编译路径:K3Cloud\K3CloudServer\Bin;

6.    打开IDE设计器,配置插件;

先找到单据属性窗口,编辑“采购收料单-_Bill”单据属性:

在插件列表界面,点击注册插件:

(注意该列表中可能已注册有其他插件,这些插件在运行时会动态加载,删除插件可能会导致业务数据错误)

选择插件界面点击浏览:

选择编译好的组件:

勾选插件,确定返回

确定并保存单据。

7.    运行测试;


2、动态表单插件

2. 动态表单插件

动态表单插件提供了丰富的接口,通过这些接口可以在插件中对表单编辑和列表界面样式、操作进行控制,也可以对显示数据进行各种处理。

再来回顾一下动态表单元数据结构和继承关系:

动态表单模型包含表单外观和表单业务逻辑,表单外观管理界面控件外观及样式,在模型中由视图(View)来控制,表单业务逻辑管理包括服务、校验器、操作和业务规则等,由模型(Model)来控制。

动态表单外观和逻辑都是在IDE中设置的,设置的数据保存在动态表单模型元数据中,具体由布局元数据(LayoutInfo)记录表单外观数据,由业务元数据(BusinessInfo)记录表单逻辑数据,这2个类分别由View和Model持有。

(图 10 – 2 动态表单元模型)

为了方便使用和提高开发效率,我们将动态表单模型分解为各种表单领域模型,同时为各种模型提供了相应插件:

(图 10 – 3 领域模型-动态表单模型关系)

动态表单插件分为5大类:

1.      单据插件

2.      列表插件

3.      过滤条件插件

4.      账表插件

5.      动态表单插件

继承关系如下:

(图 10 – 4 插件继承关系)


动态表单视图

动态表单视图

前面已经介绍,外观是由视图来管理,我们先看看动态表单视图模型。

根据BOS架构图可以看到,客户端首先向服务发起HTTP请求,服务端由控制器服务接受请求并转送到动态表单模型控制器,再有动态表单控制器访问动态表单视图。动态表单视图加载外观模型,并从动态表单模型获取数据模型。

动态表单视图提供2个视图接口,IDynamicFormView和IDynamicFormViewService。

IDynamicFormView是视图接口,包含领域模型元数据、多视图模型接口、操作转发指令和通用属性方法。该接口可由插件直接访问。

IDynamicFormViewService是动态表单内部使用的接口定义,包含Controller消息路由方法,插件开发不需关注。

IDynamicFromView有2个重要属性,BusinessInfo和LayoutInfo,分别表示业务对象逻辑元数据和布局元数据。包含在IDE中设置的表单的所有信息。在运行时,客户端发出访问表单请求后,首先读取元数据初始化BusinessInfo和LayoutInfo,View和Model根据元数据定义的界面数据和布局信息展示出表单。

IDynamicFromView接口提供了访问BusinessInfo和LayoutInfo的一些方法,供插件调用以实现业务,例如:访问菜单,修改控件样式,设置标题,更新界面等。

IDynamicFromView接口同时提供操作控制和调用Model的方法,如:调用表单服务,执行操作,发送客户端指令,刷新界面,打开表单,动态注册插件等。

本章节通过一些示例做详细介绍。

先看看界面元素的访问。在实际业务中,经常需要对单据扩展,增加功能,那么就需要访问菜单、字段显示隐藏锁定等。


动态表单模型

动态表单模型

动态表单模型接口:IDynamicFormModel和IDynamicFormModelService。

设计思想同动态表单视图一样,将逻辑和插件模型分开。

IDynamicFormModel是模型接口,包含领域模型元数据、数据操作方法。该接口可由插件直接访问。

IDynamicFormModelService是动态表单内部使用的接口定义,插件开发不需关注。

IDynamicFormModel也有BusinessInfo,和IDynamicFromView一样,表示业务对象逻辑元数据。这里BusinessInfo的意义是根据元数据定义绑定数据。

另外一个重要属性DataObject是当前表单的数据对象。该数据是个DynamicObject,包含单据头和单据体数据,其中单据体是集合对象DynamicObjectCollection,并且可以有多个.

K/3Cloud BOS动态实体类型,默认使用DynamicObject作为数据承载类,可以通过DynamicObjectType.ClrType属性指定自定义类。但我们要求指定的类型必须派生自DynamicObject。

IDynamicFormModel提供的主要是针对数据进行操作的系列方法,包括:初始化、新增表单数据、复制数据、删除数据、定位当前分录数据行、设置值等方法。


动态表单插件

动态表单模型是通过插件代理实现业务逻辑,对外部的接口主要是插件,这些接口可以提供给二次开发使用。

命名空间

命名空间

Kingdee.BOS.Core.DynamicForm.PlugIn

主要类及说明:


Class

Description


AbstractDynamicFormDataBinder

动态表单数据绑定器抽象类


AbstractDynamicFormPlugIn

动态表单插件抽象基类


AbstractDynamicWebFormBuilderPlugIn

动态表单页面元数据构建插件


AbstractOperationServicePlugIn

操作服务插件抽象类

主要接口:


Interface

Description


IDynamicFormModelPlugIn

动态表单Model层插件控制接口;实现本接口的插件,可以接收Model层的事件


IDynamicFormViewPlugIn

动态表单View层插件接口;实现本接口的插件可以接收动态表单View层事件

继承体系

继承体系

动态表单插件分4类,单据、基础资料、动态表单和列表。

业务模型

类(插件、服务)

继承自抽象类

表单插件

单据插件

Kingdee.BOS.Core.Bill.PlugIn.AbstractBillPlugIn

基础资料插件

Kingdee.BOS.Core.Base.PlugIn.AbstractBasePlugIn

动态表单插件

Kingdee.BOS.Core.DynamicForm.PlugIn.AbstractDynamicFormPlugIn

列表插件

列表插件

Kingdee.BOS.Core.List.PlugIn.AbstractListPlugIn

单据插件

命名空间

Kingdee.BOS.Core.Bill.PlugIn

继承体系

继承体系

System.Object

Kingdee.BOS.Core.DynamicForm.PlugIn.AbstractDynamicFormPlugIn

Kingdee.BOS.Core.Bill.PlugIn.AbstractBillPlugIn

Kingdee.BOS.Core.Base.PlugIn.AbstractBasePlugIn


接口

视图访问接口

接口名:IdynamicFormViewPlugIn

动态表单View层插件接口;实现本接口的插件可以接收动态表单View层事件。


Name

Description


AfterBarItemClick

菜单单击事件完成后处理扩展接口


AfterBindData

绑定数据后事件处理后扩展接口


AfterButtonClick

按钮单击之后调用


AfterCopyRow

分录行拷贝后调用


AfterDoOperation

操作完成后调用


AfterEntryBarItemClick

分录菜单单击事件处理扩展接口


AfterF7Select

基础资料选择返回后调用


AfterToolBarItemClick

工具栏单击事件处理扩展接口


BarItemClick

主菜单单击事件处理扩展接口


BeforeBindData

绑定数据前事件处理后扩展接口,主要用于加载数据到界面前对控件状态进行设置


BeforeClosed

页面准备关闭


BeforeDoOperation

操作开始前调用


BeforeF7Select

基础资料界面调出之前抛出


ButtonClick

按钮单击时调用


EntityRowClick

分录行单击事件


EntityRowDoubleClick

分录行双击事件


EntryBarItemClick

分录菜单单击事件处理扩展接口


EntryButtonCellClick

表格按钮单击时调用


FieldLabelClick

字段标题单击事件


FireEntryCheck

单据体列全选事件


ListViewClick

列表控件单击事件


OnInitialize

页面初始化


TabItemSelectedChange

页签控件的页签选中事件


ToolBarItemClick

工具栏单击事件处理扩展接口


TreeDragDrop

KDTree 拖拽事件


TreeNodeClick

TreeView 节点单击之后调用


TreeNodeDoubleClick

TreeView 节点双击之后调用


模型访问接口

接口名:IdynamicFormModelPlugIn

动态表单Model层插件控制接口;实现本接口的插件,可以接收Model层的事件。主要包括:


Name

Description


AfterCreateNewData

业务对象创建后的扩展接口


AfterCreateNewEntryRow

新增、插入、多行输入后调用


BeforeUpdateValue

值改变更新前的扩展接口


CreateNewData

创建新业务对象扩展接口,插件可以更加需要自己创建对象


DataChanged

字段值改变后扩展接口


加载机制

动态表单元模型包括外观模型和表单逻辑模型,第一次访问时会先加载元数据,初始化视图和模型对象,初始化页面,然后创建数据包并绑定数据。

对于二次开发提供了一系列插件允许二次开发在加载表单时对视图、模型、数据包及界面进行控制,插件在加载过程中的执行顺序如下:

OnInitialize                          页面初始化

CreateNewData                   动态表单数据包创建

AfterCreateNewEntryRow      创建分录行后

AfterCreateNewData        动态表单数据包创建后

BeforeBindData                   绑定数据前事件

AfterBindData                     绑定数据及控件状态

BeforeClosed                页面关闭前

初始化方法

OnInitialize

该插件负责动态表单实例初始化,包括单据Global参数(当然有些参数仅仅在使用时候才获取),动态初始化控件数据源等。

比如,批量修改界面初始化时将允许修改的字段加入到下拉列表。

C#


///<summary>///界面初始化///</summary>///<param name="e"></param>public override void OnInitialize(Core.DynamicForm.PlugIn.Args.InitializeEventArgs e){//根据列表的formid,获取元数据metadata = (FormMetadata)ServiceHelper.MetaDataServiceHelper.Load(this.View.Context, this.View.ParentFormView.BillBusinessInfo.GetForm().Id);//设置标题 - -!string strTitle = string.Format("{1}-[{0}]",metadata.GetLayoutInfo().GetFormAppearance().Caption,this.View.LayoutInfo.GetFormAppearance().Caption);LocaleValue formTitle =new LocaleValue();formTitle.Add(new KeyValuePair<int, string>(this.Context.UserLocale.LCID, strTitle));this.View.SetFormTitle(formTitle);List<EnumItem> list =new List<EnumItem>();//循环检测哪些字段允许批量修改,加入列表foreach (Field fieldin metadata.BusinessInfo.GetFieldList()){if ((field.FunControl &Field.FUNCONTROL_BULK_EDIT) !=Field.FUNCONTROL_BULK_EDIT) continue;//修改时隐藏的字段不予显示Appearance app = metadata.GetLayoutInfo().GetAppearance(field.Key);if (app != null){if ((app.Visible &Appearance.VIS_EDIT) != Appearance.VIS_EDIT)continue;}_lstFields.Add(field);EnumItem item = new EnumItem();item.Value = field.Key;item.Caption = field.Name;list.Add(item);}//排序并将list加入到下拉列表list = list.OrderBy(p => p.Caption[this.View.Context.UserLocale.LCID]).ToList();if (list.Count != 0){selectedFielKey = list.FirstOrDefault().Value;}this.View.GetControl<ComboFieldEditor>("FCombo").SetComboItems(list);}

创建数据包

创建数据包

CreateNewData

动态表单数据包创建,只在新增时触发,打开表单不触发。

我们在IDE里画好单据和基础资料后,不需要编写任何代码,打开界面,可以看到已经创建好一张新的空单据,这是因为新建时候会调用CreateNewRow创建空数据。很多时候,我们需要创建有缺省值或者新增时候从其他服务获取数据显示过来,我们就可以通过该事件来加载数据。

示例:简单的加载动态表单数据。

C#


public override void CreateNewData(BizDataEventArgs e){if (!billFormId.IsNullOrEmptyOrWhiteSpace()){DynamicObject obj =BusinessDataServiceHelper.LoadBillTypePara(context,businessInfo, formId, false);e.BizDataObject = obj;}base.CreateNewData(e);}

示例:操作结束后,在动态表单上显示操作结果。

C#


///<summary>///创建数据包事件处理;由插件处理数据包的创建过程,界面仅展示///</summary>///<param name="e"></param>public override void CreateNewData(BizDataEventArgs e){// 创建本界面需要的数据对象e.BizDataObject = new DynamicObject(this.View.OpenParameter.FormMetaData.BusinessInfo.GetDynamicObjectType());BusinessInfo info = this.View.OpenParameter.FormMetaData.BusinessInfo;// 给角色表格赋值Entity resultEntity = info.GetEntity("FEntity");Field seqField = info.GetField("FSeq");Field nameField = info.GetField("FName");Field statusField = info.GetField("FStatus");Field messageField = info.GetField("FMessage");Field typeField = info.GetField("FType");DynamicObjectCollection resultEntityData = (DynamicObjectCollection)resultEntity.DynamicProperty.GetValue(e.BizDataObject);int row = 0;foreach (OperateResult rowResultin _results){// 添加新行DynamicObject rowData =new DynamicObject(resultEntity.DynamicObjectType);// 给行中的字段赋值seqField.DynamicProperty.SetValue(rowData, row + 1);nameField.DynamicProperty.SetValue(rowData, rowResult.Name);statusField.DynamicProperty.SetValue(rowData, (rowResult.SuccessStatus ?"1" : "0"));messageField.DynamicProperty.SetValue(rowData, rowResult.Message);typeField.DynamicProperty.SetValue(rowData, ((int)rowResult.MessageType).ToString());resultEntityData.Add(rowData);row++;}}

AfterCreateNewEntryRow

创建分录行后事件。字段值设置优先考虑使用IDE进行实体服务规则配置。

该事件通常用于新增分录后对数据进行判断处理。需要注意,这个事件是在每次新增分录都会触发,对于不需要在界面上显示的可以在新建分录后(如AfterCreateNewData事件)一次性处理。

C#


///<summary>///创建新的分录行事件///</summary>///<param name="e"></param>public override void AfterCreateNewEntryRow(CreateNewEntryEventArgs e){base.AfterCreateNewEntryRow(e);if (e.Entity.Key.Equals(CONST_ENG_Route.CONST_FSubEntity.ENTITY_FSubEntity)){IEnumerable<DynamicObject> subEntryDataCol =this.Model.GetEntityDataObject(e.Entity);if (e.Row == subEntryDataCol.Count() - 1)  // 插入行不赋值{// 设置工序号=上取整((MAX(工序号)+1)/10)*10,且不大于9999int maxOperNumber = subEntryDataCol.Select(w => w.GetDynamicObjectItemValue<int>(CONST_ENG_Route.CONST_FSubEntity.ORM_OperNumber)).Max();int newOperNumber = (int)Math.Ceiling(((decimal)maxOperNumber + 1) / 10) * 10;this.Model.SetValue(CONST_ENG_Route.CONST_FSubEntity.KEY_FOperNumber, newOperNumber > 9999 ? 9999 : newOperNumber, e.Row);}}}

AfterCreateNewData

动态表单数据包创建后事件。该方法仅在新增表单后触发。主要用于新建表达根据元数据定义初始化数据包后,根据特殊需求,改变当前数据。

通常我们在IDE里通过配置实体服务规则实现表单字段的缺省值赋值:

但有时需要根据一些参数动态设置值时,就需要用插件实现。下面举一个例子,新增单据时根据当前组织获取邮件的缺省值,赋值到当前数据包。

C#


public override void AfterCreateNewData(EventArgs e){base.AfterCreateNewData(e);OQLFilter ofilter = new OQLFilter();ofilter.Add(new OQLFilterHeadEntityItem { FilterString = string.Format(" FORGID ={0} ",this.Model.Context.CurrentOrganizationInfo.ID) });DynamicObject[] obj =BusinessDataServiceHelper.Load(this.View.Context,"BAS_MAILDEFAULTSET", null, ofilter);if (obj != null && obj.Count() > 0){DynamicObject defaultSet = obj[0];this.View.Model.SetValue("FMessageType", defaultSet["FMessageType"]);this.View.Model.SetValue("FServer", defaultSet["FOutgoingMailServer"]);this.View.Model.SetValue("FSMTPPort", defaultSet["FSMTPPort"]);}}

因为该插件属于创建数据包,在该插件里设置的值不会加到状态管理器中,因此该方法设置的值是整个数据包一起发送到客户端的。客户端数据可以通过Http数据监控查询:

AfterCreateModelData

模型层数据包创建完毕。该事件只在新增表单模型后触发,用于对新增后表单模型进行相关操作。此插件的操作不会引起Model.DataChanged值改变。

例:

订单变更查询中,需要在界面上,根据查询列表中的版本显示订单内容,在打开查询时缺省打开第一行基准版本的订单。

插件代码:

C#


///<summary>///模型数据包创建完毕,显示订单界面///</summary>///<param name="e"></param>public override void AfterCreateModelData(EventArgs e){if (listVersions != null && listVersions.Count() > 0){baseOrderData = SCMCommon.DeserializeJsonStringToDynamicObject(orderBusinessInfo, listVersions[0].JsonData);ShowOrderBillVersion();}}


数据绑定

数据绑定

BeforeBindData

绑定数据前事件。该插件可以在数据绑定前对数据进行处理,对数据修改不会被状态管理器记录。

例如:单据插件中根据类型增加分录行。

C#


public override void BeforeBindData(EventArgs e){base.BeforeBindData(e);//基础资料if (_modelTypeId == ElementType.ELEMENTTYPE_BASE.ToString()){this.View.Model.CreateNewEntryRow("FSearchControl");}else if (_modelTypeId == ElementType.ELEMENTTYPE_BILL.ToString())//业务单据{this.View.Model.CreateNewEntryRow("FFieldParamControl");}// 操作参数this.View.Model.CreateNewEntryRow("FPARAMOPERATION");}

注:批量新增行用this.Model.BatchCreateNewEntryRow(stringkey, int rowCount)方法。

AfterBindData

绑定数据及控件状态,该事件较常用,加载和界面刷新都会调用该插件。通常该事件处理数据可见性样式等。

如:单据插件根据类型设置单据字段可见性。

C#


public override void AfterBindData(EventArgs e){base.AfterBindData(e);//隐藏菜单项this.View.GetMainBarItem("tbNew").Visible =false;//显示分录菜单项this.View.GetBarItem("Fentity","tbAdd").Enabled = true;//基础资料if (_modelTypeId == ElementType.ELEMENTTYPE_BASE.ToString()){this.View.StyleManager.SetVisible("FTab_Field",null, false);}else if (_modelTypeId == ElementType.ELEMENTTYPE_BILL.ToString())//业务单据{//单据不含单据类型字段时,字段参数页签屏蔽if (this._metaData.GetLayoutInfo().GetFieldAppearances().Any(f => fis BillTypeFieldAppearance)){this.View.StyleManager.SetVisible("FTab_Field",null, true);}}}

设置背景颜色。

C#


public override void AfterBindData(EventArgs e){//获取单据体表格,参数为单据体Key,示例代码假设为FEntityEntryGrid grid = this.View.GetControl<EntryGrid>("FEntity");//设置第一行的背景色,参数:颜色,6位16进制符号,每2位代表一种基色;从0开始,行序号grid.SetRowBackcolor("#FFC080", 0);//设置第二行F1字段的背景色,参数:字段Key;颜色;行序号grid.SetBackcolor("F1","#FFC080", 1);}


加载和关闭

加载和关闭

OnLoad

页面加载。该事件在BeforeBindData前触发,并且不受StyleManager管理,在此事件设置单据字段的可见性和锁定性无效。

OnLoad时,数据已经获取到,通常我们在此事件处理一些数据设置。

例如:过滤界面插件设置缺省值和页签可见性。

C#


public class SaleCollectFilter : AbstractCommonFilterPlugIn{public override void OnLoad(EventArgs e){base.OnLoad(e);//设置日期缺省值this.View.Model.SetValue("FStartDate", dateFrom.ToString("yyyy-MM-dd"));this.View.Model.SetValue("FEndDate", dateTo.ToString("yyyy-MM-dd"));//隐藏过滤界面排序页签this.View.StyleManager.SetVisible("FTab_P21",null, false);}}

列表界面隐藏分组滑动控件。

C#


public class SPMPromotionPolicyList : AbstractListPlugIn{public override void OnLoad(EventArgs e){base.OnLoad(e);// 隐藏分组滑动控件(默认不展开)this.View.GetControl<SplitContainer>("FSpliter").HideFirstPanel(true);this.View.GetControl("FPanel").SetCustomPropertyValue("BackColor","#FFEEEEEE");}}

注:该事件在每次UpdateView()时候都会调用。

BeforeClosed

页面关闭前插件。对于单个表单关闭,该插件基本不需要处理。对于多个表单交互,或者嵌入式表单,通常需要关闭窗体时,返回数据时,通过该插件实现。

如:关闭时刷新父窗体。

C#


public override void BeforeClosed(BeforeClosedEventArgs e){object isDataChanged = this.View.OpenParameter.GetCustomParameter("Changed");if (isDataChanged != null && (bool)isDataChanged){this.View.ParentFormView.Refresh();this.View.SendDynamicFormAction(this.View.ParentFormView);}base.BeforeClosed(e);}

关闭时传递数据到父窗体。

C#


public override void BeforeClosed(BeforeClosedEventArgs e){this.View.ReturnToParentWindow(_data);base.BeforeClosed(e);}

关闭窗体判断数据是否修改并提示保存。

C#


///<summary>///界面关闭前事件:判断用户是否修改了数据,提示保存///</summary>///<param name="e"></param>public override void BeforeClosed(BeforeClosedEventArgs e){if (this._dataChanged ==true) // 仅关注模型数据发生了改变的情况{e.Cancel = true;string msg = "内容已经修改,是否保存?";this.View.ShowMessage(msg,MessageBoxOptions.YesNoCancel,new Action<MessageBoxResult>((result) =>{if (result == MessageBoxResult.Yes) // 保存{this.View.GetControl("FDesignPanel").InvokeControlMethod("Save");}else if (result == MessageBoxResult.No)// 不要保存{this._dataChanged =false;this.View.Close();}}));}}


本文档由未注册的 Word-2-CHM软件自动从 Word 文件生成。

单据操作

单据操作

BeforeSave

单据保存前插件。单据内置保存操作,自动将修改数据保存到数据库。插件BeforeSave可以在保存前对单据数据进行处理。通常处理有两个:

 数据校验;

 计算和更新数据;

在BOS平台当客户端发起请求,到web服务器后,领域模型框架调用运行时,加载插件运行。用户执行操作时,运行时调用操作服务进行数据模型的操作。而插件中调用服务也是先向服务框架请求服务。

通常应用都是在业务保存前进行数据校验,校验通过后,调用保存服务保存,在大多数系统中都是这样应用。在BOS平台中,架构设计上支持集成服务,所有操作都是设计有服务接口,二次开发可以很容易将所有操作发布成服务供外部系统调用。这样对外部系统来说,调用服务保存将会很容易。但如何保证数据的正确性?大部分设计是由外部系统保证,但对复杂业务系统来说,外部系统很难保证每个业务数据的正确性,甚至用大量访问系统来获取验证数据。为此,BOS平台在操作上提供了校验服务,这样在系统内部通过插件调用服务前会自动执行校验服务。而外部系统访问的是BOS操作发布的服务本身也带有校验。

因此建议将数据校验按业务逻辑分开成两类,一类是界面输入校验,如字符、数字类型、格式化和表达式校验等,可以在插件保存前进行校验;而数据业务的校验,如库存校验信用检查等,通过校验服务校验。

校验方法如下:

1.    优先通过IDE配置校验数据,如输入格式,最大最小值限定;

2.    操作控制类校验在表单的操作前插件检查;

3.    业务控制类校验在表单校验服务校验。

该事件中可以通过设置参数的Cancel终止保存操作。

下面例子是保存前更新数据(信用评分单据保存设置信用等级标准)。

C#


public override void BeforeSave(BeforeSaveEventArgs e){DynamicObject doGradeScheme = this.Model.GetValue("FScheme")as DynamicObject;decimal deSumScore =Convert.ToDecimal(this.Model.GetValue("FSumScore"));int iGradeSchemeId =Convert.ToInt32(doGradeScheme["Id"]);// 保存前判断当前信用评分表的综合得分属于哪一个信用等级标准DynamicObjectCollection docGrades = CreditServiceHelper.GetCreditGrades(this.Context, 0, iGradeSchemeId);for (int i = 0; i < docGrades.Count(); i++){DynamicObject doGrade = docGrades[i];decimal deScoreFrom =Convert.ToDecimal(doGrade["FSCOREFROM"]);decimal deScoreTo = Convert.ToDecimal(doGrade["FSCORETO"]);if(deSumScore >= deScoreFrom && deSumScore <= deScoreTo){this.View.Model.SetValue("FGrade",Convert.ToInt32(doGrade["FID"]));this.View.Model.SetValue("FSuggestion", doGrade["FDESCRIPTION"]);}}}

AfterSave

单据保存后插件。主要用于保存后界面的控制、控件的显示以及不需要事务保证的其他数据更新。


3.服务插件

BOS平台抽象了领域模型,针对领域模型定义各种操作并提供操作服务。但很多时候,内置的操作并不一定满足需要。为此在APP服务层提供服务插件,以方便二次开发扩展应用。

服务插件配置是在BOS IDE中操作编辑里:

服务插件运行在App层,因此,在外部系统调用集成服务接口时,随着操作服务的发布,服务插件也会有效。

和校验器配合使用

运行于App层

命名空间

Kingdee.BOS.Core.DynamicForm.PlugIn

继承体系

所有服务插件都应继承自抽象服务插件类。

插件模型

继承自抽象类

服务插件

Kingdee.BOS.Core.DynamicForm.PlugIn.AbstractOperationServicePlugIn

接口

接口

IOperationServicePlugIn


Name

Description


AfterExecuteOperationTransaction

执行操作事务后的逻辑处理,后续事情不影响当前操作事务的可以放在此处理


BeforeExecuteOperationTransaction

执行操作事务前事件,通知插件对要处理的数据进行排序等预处理


BeginOperationTransaction

调用操作事务前触发


EndOperationTransaction

调用操作事务成功后触发


InitializeOperationResult

操作成功后触发


OnAddValidators

通过此事件,通知插件进行添加自定义数据校验器


OnPrepareOperationServiceOption

通过此事件,通知插件进行选项设置


OnPreparePropertys

准备操作对象实体属性事件,在此事件中可以将校验过程需要的属性对应的Key添加进来以便统一从数据库中加载数据

BeforeExecuteOperationTransaction

执行操作事务前插件。通常用于执行操作前数据处理,该插件在webservice服务调用时也会执行。该事件是操作事务前允许处理数据的最后一个插件,为保证操作事务时间最短,在性能优化时会将不需要事务保护的部分服务逻辑放到这个插件里处理。

该插件中不适合用于数据校验,数据校验方法请参考数据校验章节。

例如:

在直接调拨单中,增加保存服务插件,在保存事务前,计算未结算的关联数量。这个数据在结算业务逻辑中使用,必须保证数据准确有效,不需要调拨界面显示。如果在web插件中计算会有2个问题:

1.    数据操作修改后必须重新计算,多次修改要多次计算,效率低;

2.    外部接口调用保存服务时,需要自己计算好填到数据包,如果涉及到本地化设置(如数据精度)等问题,还要调用方特殊处理;

在保存操作增加服务处理步骤:

1.    定义服务插件类StockTransferDirect.SaveService,插件继承AbstractOprerationService;

2.    重载BeforeExecuteOperationTransaction方法,示例代码:

C#


// 保存操作事务前,计算单据上的“未结算关联数量”public override void BeforeExecuteOperationTransaction(BeforeExecuteOperationTransaction e){if (e.SelectedRows ==null || e.SelectedRows.Count() == 0){// 没有数据,取消操作(通常此类判断应在web端进行,避免不必要的资源消耗,此处仅示例如何取消操作)e.Cancel = true;return;}DynamicObject[] objs = (from pin e.SelectedRows select p.DataEntity).ToArray();foreach (DynamicObject datain objs){DynamicObjectCollection dataentrys = data["TransferDirectEntry"]as DynamicObjectCollection;foreach (DynamicObject entryin dataentrys){//“未结算关联数量”=“调拨数量”-“关联退货数量”-“结算关联数量”。decimal qty = Convert.ToDecimal(entry["Qty"]);decimal baseQty =Convert.ToDecimal(entry["BaseQty"]);decimal receiveQty =Convert.ToDecimal(entry["ReceiveQty"]);decimal baseJoinQty =Convert.ToDecimal(entry["BaseJoinQty"]);decimal baseSettQty =Convert.ToDecimal(entry["JoinBaseSettQty"]);decimal SettQty =Convert.ToDecimal(entry["JoinSettleQty"]);entry["JoinUnSettleQty"] = qty - receiveQty - SettQty;entry["JoinBaseUnSettQty"] = baseQty - baseJoinQty - baseSettQty;}}}

3.    编译后,运行系统,在IDE中配置保存服务插件;

4.    调试测试;

AfterExecuteOperationTransaction

执行操作事务后插件。通常用来处理操作后的相关的数据处理,如生成其他单据、更新状态、运行业务运算等。该插件在操作事务外,执行结果不影响操作,因此该插件要考虑执行失败的逻辑处理。

AfterExecuteOperationTransaction参数:

Name

Description



DataEntitys

本次操作事务处理成功的数据实体集合


SelectedRows

当前操作校验通过的所有行对象

(参数命名空间:BOS.Core.DynamicForm.PlugIn.Args)

审核结束自动生成付款单的代码示例:

C#


public override void AfterExecuteOperationTransaction(AfterExecuteOperationTransaction e){//审核时,如果弹出信用相关提示警示信息时,e.DataEntitys将没有记录。此时直接退出if (e.DataEntitys.IsNullOrEmpty() || e.DataEntitys.Count() == 0){return;}foreach (var dataEntityin e.DataEntitys){if (this.IsCanSoureBillPush(dataEntity))//申请借款,下推付款申请单{DynamicObject[] objs = new DynamicObject[1] { dataEntity };//初始化备用信息this.OnInit(objs);//生成下游单据this.ProduceBill(this.OperationResultas ConvertOperationResult);}}}

BeginOperationTransaction

操作事务开始插件。用于在执行操作前处理数据,该方法与BeforeExecuteOperationTransaction区别主要在于该插件在操作事务内,出错后系统会回滚事务。该插件开发时要特别关注对性能的影响,建议对分录的所有处理考虑批量进行。

参数:

CancelFormService

是否取消执行本操作所关联的表单服务;即终止服务插件,不执行其他表单服务插件。

CancelOperation

是否取消本操作;即终止操作。

简单生产领料单保存前,根据当前单据删除的领料单分录获取关联的源单分录,在保存后,检测简单领料分录是否仍存在该分录ID上拉的行,然后再判断应该更新简单领料分录还是源单分录,重置该分录行的领料标识。

C#


//更新操作前,获取删除的分录数据,在更新后做处理public override void BeginOperationTransaction(BeginOperationTransactionArgs e){//获取删除的行IDbDriver driver = new Kingdee.BOS.App.Data.OLEDbDriver(this.Context);IDataManager manager = DataManagerUtils.GetDataManager(this.BusinessInfo.GetDynamicObjectType(), driver);ISaveDataSet updateData = manager.GetSaveDataSet(e.DataEntitys.ToArray());DeleteRows = updateData.Tables["T_SP_PICKMTRLDATA"].DeleteRows;if (DeleteRows != null && DeleteRows.Length > 0){List<long> lstDeleteIds = DeleteRows.Select(w => w.Oid).ToList();//根据领料单ENTRYID获取关联的源单SRCENTRYIDlistInStockEntryId = ServiceFactory.GetSpInStockService(this.Context).GetInStcokEntryByPickMtrlEntryId(this.Context, lstDeleteIds);base.BeginOperationTransaction(e);}}

C#


//更新操作后,根据更新前获取的删除分录的数据,重新计算领料标识public override void EndOperationTransaction(EndOperationTransactionArgs e){base.EndOperationTransaction(e);ISpInStockService service= ServiceFactory.GetSpInStockService(this.Context);// 检测简单领料分录是否仍存在该分录ID上拉过来的行,否则更新简单入库的分录行领料标识if (listInStockEntryId !=null && listInStockEntryId.Count > 0){// 批量检测service.ResetIsPickInInStcokEntry(this.Context, listInStockEntryId);}foreach (var dataEntityin e.DataEntitys){IEnumerable<long> lstEntryId = dataEntity.GetDynamicObjectItemValue<DynamicObjectCollection>(ENTITY_ORM_Entity).Where(w => w.GetDynamicObjectItemValue<string>( ORM_SrcBillType_Id) == SCMFormIdConst.SpInStockBill).Distinct().Select(w => w.GetDynamicObjectItemValue<long>( ORM_SrcEnteryId));// 根据分录ID重置该分录行的领料标识(批量更新)service.UpdateIsPickInInStcokEntry(this.Context, lstEntryId.ToList());}}

EndOperationTransaction

操作事务结束插件,此插件在事务内运行,出错后系统会回滚事务。

示例参照BeginOperationTransaction


4、应用案例介绍

4. 应用案例介绍

收货单提供以下功能:

1.      增加下拉列表,显示单据头的所有字段;

2.      在分录菜单上增加库存查询(FQueryInventory)菜单项;

3.      点击库存查询时,查询分录上当前焦点所在物料的库存(STK_InvSumQuery);

STK_Inventory

4.      查询库存时按组织隔离,只查询当前组织的库存;

5.      当前分录物料F8时,显示所有组织的物料;

6.      暂存时清空单据类型的值;

7.      物料基础资料增加字段有效期至(F_MCY_ExpiryDate);

8.      F8时只显示有效期〉今天的物料;

9.      保存判断物料的库存,如果〉100则提示“库存〉100,是否入库?”;

10.   保存后锁定“收料部门”、“收料员”;

11.   保存后自动记录收料日志(MCY_stk_ReceiptLog);

操作步骤:

1.      增加下拉列表,显示单据头的所有字段;

a)        新建(打开)收货单插件工程(MyDev.K3.SCM.Stock.Business.PlugIn);

b)        重载OnInitialize方法,定义List<EnumItem>用于存储下拉列表枚举值;

c)        通过this.View.BusinessInfo.GetFieldList()方法获取所有字段;

d)        通过this.View.GetControl<ComboFieldEditor>方法获取界面上的下拉列表控件;

e)        SetComboItems绑定值;

f)         代码如下:

C#


public override void OnInitialize(InitializeEventArgs e){base.OnInitialize(e);List<EnumItem> list =new List<EnumItem>();foreach (Field fieldin this.View.BusinessInfo.GetFieldList()){EnumItem item = new EnumItem();item.Caption = field.Name;item.EnumId = field.Key;item.Value = field.Key;list.Add(item);}this.View.GetControl<ComboFieldEditor>("FCombo").SetComboItems(list);}

2.      在分录菜单上增加库存查询(tbQueryInventory)菜单项;

a)        运行IDE,选择单据体-菜单集合,新增菜单:

b)        保存;

3.      点击库存查询时,查询分录上当前焦点所在物料的库存;

a)        打开插件工程,重载方法EntryBarItemClick

b)        判断BarItemKey==库存查询(tbQueryInventory)

c)        取当前分录行

d)        设置ListShowParameter参数,打开表单

这里介绍2种获取当前分录字段数据的方法:

TryGetEntryCurrentRow:获取单据体当前行,返回是否取到值以及行数据和行号;

另外一种方法:

先获取单据体当前行号,再取指定行数据;

2种方法没什么区别。

示例代码如下:

C#


public override void EntryBarItemClick(BarItemClickEventArgs e){base.EntryBarItemClick(e);if (e.BarItemKey == "tbQueryInventory"){ShowQueryInventory();}}private void ShowQueryInventory(){DynamicObject row;int rowIndex;// 直接获取当前分录行返回的是分录行对象。if (this.Model.TryGetEntryCurrentRow("FEntity",out row, out rowIndex)){ListShowParameter parameter =new ListShowParameter();parameter.FormId = "STK_Inventory";   // 即时库存的FormIdparameter.MultiSelect = false;parameter.ListFilterParameter.Filter = string.Format(" FMaterialId = '{0}' ",Convert.ToString(row["FBase_Id"]));this.View.ShowForm(parameter);}}

取单据体当前行号,再取指定行的字段数据的方法如下:

C#


private void ShowQueryInventory(){// 获取当前行int rowIndex = this.Model.GetEntryCurrentRowIndex("FEntity");if (rowIndex > -1) //判断当前行有数据{// 取指定行的物料(ide中设置key为FBase)字段数据DynamicObject materialObj = (DynamicObject)this.Model.GetValue("FBase", rowIndex);ListShowParameter parameter =new ListShowParameter();parameter.FormId = "STK_Inventory";parameter.MultiSelect = false;parameter.ListFilterParameter.Filter = string.Format(" FMaterialId = '{0}' ", materialObj["Id"].ToString());this.View.ShowForm(parameter);}}

调试状态下,可以屏蔽代码parameter.ListFilterParameter.Filter看看过滤条件的效果。

注意:ListFilterParameter 的Filter属性设置的字段是用IDE中的字段标识。

4.      查询库存时按组织隔离,只查询当前组织的库存:

a)        增加过滤条件,组织=当前组织

b)        parameter.ListFilterParameter.Filter= string.Format(" FORGID ={0}",this.Model.Context.CurrentOrganizationInfo.ID)});

5.      当前分录物料F8时,显示所有组织的物料;

a)   重载AuthPermissionBeforeF7Select方法,设置参数IsIsolationOrg = false;

b)   同样,如果需要F8时控制只显示当前组织的物料,该参数设置为true。

注意:

在BOS系统中,默认是按组织隔离的,即非共享基础资料,在F8时都是只显示当前组织的物料。

代码示例如下:

C#


public override void AuthPermissionBeforeF7Select(AuthPermissionBeforeF7SelectEventArgs e){base.AuthPermissionBeforeF7Select(e);if (e.FieldKey == "FBase"){e.IsIsolationOrg = false;}}

6.      暂存时清空单据类型的值;

C#


public override void BeforeDoOperation(BeforeDoOperationEventArgs e){base.BeforeDoOperation(e);if (e.Operation.FormOperation.Operation.Equals("DRAFT",StringComparison.OrdinalIgnoreCase)){this.Model.SetValue("FBillTypeID",null);}}

7.      F8时只显示审核日期〉2014-03-22的供应商;

a)        重载BeforeF7Select事件;

b)        设置列表过滤参数ListFilterParameter的属性Filter;

C#


public override void BeforeF7Select(BeforeF7SelectEventArgs e){base.BeforeF7Select(e);if (e.FieldKey == "FSupplierId1"){string filter = " FCreateDate > '2014-03-20' ";if (string.IsNullOrEmpty(e.ListFilterParameter.Filter)){e.ListFilterParameter.Filter = filter;}else{e.ListFilterParameter.Filter += " AND " + filter;}}}

8.      保存判断物料的库存,如果〉100则提示“库存〉100,是否入库?”;

a)        新建收货单服务插件工程MyDev.K3.SCM.App.Stock.ServicePlugIn;

b)        定义保存服务类SaveServicePlugIn,继承自AbstractOperationServicePlugIn;

c)        重载OnAddValidators方法;

代码示例如下:

C#


public override void OnAddValidators(AddValidatorsEventArgs e){base.OnAddValidators(e);SaveValidator saveValidator =new SaveValidator();saveValidator.EntityKey = "FBillHead";e.Validators.Add(saveValidator);}

d)        定义保存校验类SaveValidator,继承自AbstractValidator;

e)        重载方法:Validate:

i.             获取单据体分录数据,取到物料Id;

ii.             查询物料库存;

iii.             检查库存是否〉100;

iv.             构造校验结果信息;

代码示例:

C#


public override void Validate(ExtendedDataEntity[] dataEntities,ValidateContext validateContext, Kingdee.BOS.Context ctx){if (dataEntities == null || dataEntities.Length == 0){return;}Dictionary<long,decimal> dictErrMaterialId = new Dictionary<long,decimal>();//取所有物料List<long> listMaterialId =new List<long>();foreach (ExtendedDataEntity entityObjin dataEntities){DynamicObjectCollection collection = (DynamicObjectCollection)entityObj["FEntity"];foreach (DynamicObject rowObjin collection){listMaterialId.Add((long)rowObj["FBase_Id"]);}}string sql = " select a.FMATERIALID, sum(a.FBASEQTY) FQTY from  T_STK_INVENTORY a where exists (select 1 from TABLE(fn_StrSplit(@FMATERIALID, ',',1)) t where t.FID = a. FMATERIALID) group by FMATERIALID ";SqlParam param = new SqlParam("@FMATERIALID",KDDbType.udt_inttable, listMaterialId.Distinct().ToArray());using (IDataReader dr =DBUtils.ExecuteReader(this.Context, sql, param)){while (dr.Read()){decimal qty = Convert.ToDecimal(dr["FQTY"]);if (qty > 100){dictErrMaterialId.Add(Convert.ToInt64(dr["FMATERIALID"]), qty);}}}foreach (ExtendedDataEntity entityObjin dataEntities){DynamicObjectCollection collection = (DynamicObjectCollection)entityObj["FEntity"];foreach (DynamicObject rowObjin collection){if (dictErrMaterialId.ContainsKey((long)rowObj["FBase_Id"])){ValidationErrorInfo errinfo =new ValidationErrorInfo("FMATERIALID",Convert.ToString(entityObj.DataEntity["Id"]), entityObj.DataEntityIndex,Convert.ToInt32(rowObj["Id"]),"SaveValidator", "库存数量大于100","校验失败",ErrorLevel.Error);validateContext.AddError(entityObj, errinfo);}}}}

f)         重载方法:Validate:

9.      保存后锁定“收料部门”、“收料员”;

a)        锁定字段的方法:this.View.LockField;

b)        该锁定与事务无关,只要在客户端保存后事件(AfterBarItemClick)处理即可;

c)        “收料部门”、“收料员”的key可以在IDE设计器中拷贝;

代码如下:

C#


public override void AfterBarItemClick(AfterBarItemClickEventArgs e){base.AfterBarItemClick(e);if (e.BarItemKey == "tbSave"){this.View.LockField("FBase1",true);this.View.LockField("FBase2",true);}}

10.   保存后自动记录收料日志(KDV_stk_ReceiptLog);

根据需求设计收料日志表:

字段

名称

类型

说明

KDV_ID

日志ID

int

自增长

KDV_UserID

操作用户

Int

关联用户表ID

KDV_Date

操作时间

Datetime

缺省getdate

KDV_Content

日志内容

Nvarchar(2000)


保存有2种方法:

方法1:

a)  在IDE中定义收料日志基础资料;

b)  打开收货单服务插件保存服务类SaveServicePlugIn;

c)  根据收料日志基础资料的元数据定义,创建动态实体对象;

d)  设置对象属性值;

e)  调用BusinessDataService服务的保存方法保存动态实体对象;

代码如下:

C#


public override void AfterExecuteOperationTransaction(AfterExecuteOperationTransaction e){base.AfterExecuteOperationTransaction(e);MetaDataService metaService =new MetaDataService();FormMetadata formMetaData = (FormMetadata)metaService.Load(this.Context,"1823871d-b9cf-4d8b-93af-39c0c37011a5");DynamicObjectType dt = formMetaData.BusinessInfo.GetDynamicObjectType();DynamicObject obj = new DynamicObject(dt);dt.Properties["KDV_UserID_Id"].SetValueFast(obj,this.Context.UserId);dt.Properties["KDV_Content"].SetValueFast(obj,"保存");ISaveService saveService =ServiceHelper.GetService<ISaveService>();saveService.Save(this.Context,new DynamicObject[] { obj });}

方法2:

a)  自定义收料日志表;

b)  获取日志的自增长(序列)值;

c)  执行insert;

代码如下:

C#


public override void AfterExecuteOperationTransaction(AfterExecuteOperationTransaction e){base.AfterExecuteOperationTransaction(e);SequenceReader sequence =new SequenceReader(this.Context);int[] ids = (int[])sequence.GetSequence("KDV_stk_ReceiptLog", 1);int id = ids[0];string sql = " insert into KDV_stk_ReceiptLog(FID, KDV_UserID, KDV_Content) values (@FID, @KDV_UserID, @KDV_Content) ";SqlParam[] sqlParams =new SqlParam[3];sqlParams[0] = new SqlParam("@FID", KDDbType.Int64, id);sqlParams[1] = new SqlParam("@KDV_UserID",KDDbType.Int64, this.Context.UserId);sqlParams[2] = new SqlParam("@KDV_Content",KDDbType.String, "保存");DBUtils.Execute(this.Context, sql, sqlParams);}