Enterprise Solution 客户化二次开发

详细介绍Enterprise Solution 二次开发的流程步骤,主要包括数据输入窗体(Entry Form),查询(Query/Enquiry),报表(Report)三个重要的二次开发项目。

数据输入窗体开发 Entry Form

当涉及到字段的增加或增加数据库表时,开发新功能界面有以下几个主要步骤。

1  修改数据库增加表或字段,用LLBL Gen 生成新的实体映射类型定义文件。

LLBL Gen 可检测到字段的变化,增加字段或减少字段可自动更新实体映射定义文件。需要设定参数选项,如下图所示:

LLBL Gen 在生成实体的属性时,对于数据库字段可为空值的类型,会生成.NET的可空类型(nullable)类型。在我的实际项目中常常会取消这一特性,LLBL Gen会智能的知道实体哪些属性值发生变化,对于可空类型映射的属性不会生成UPDATE语句块,让它保持数据库中的空值即可(NULL)。

 

2  增加功能编码,增加权限,增加菜单项。

以销售合同功能(SLSOCE)为例,通过下面的脚本可增加系统功能(ADFUNC), 增加菜单项(ADMNUD)和增加权限(ADAUTD)。


DECLARE
@LastLineNo INTEGER,
@ModuleCode NVARCHAR(4),
@FunctionCode NVARCHAR(8),
@FunctionDesc NVARCHAR(40),
@SeriesCode NVARCHAR(8),
@SeriesOption NVARCHAR(1)

SET @ModuleCode = ‘SLSO‘
SET @FunctionCode = ‘SLSOCE‘
SET @FunctionDesc = N‘Sales Contract Entry‘
SET @SeriesCode = ‘‘
SET @SeriesOption = N‘N‘

-- Check for Function
IF NOT EXISTS ( SELECT * FROM [ADFUNC] WHERE Module_Code = @ModuleCode AND Function_Code = @FunctionCode)
BEGIN

 UPDATE [ADMODU] SET [Last_Line_No] = [Last_Line_No] + 1
 WHERE [Module_Code] = @ModuleCode

 SELECT @LastLineNo = Last_Line_No FROM ADMODU WHERE Module_Code = @ModuleCode

 IF @LastLineNo IS NOT NULL
 BEGIN
  INSERT [ADFUNC]
   ([Module_Code], [Function_No], [Function_Code], [Description],
   [Suspended], [Series_Option], [Series_Code],
   [Created_Date], [Created_By], [Revised_Date], [Revised_By],
   [OWNER_BRANCH],[SOURCE_BRANCH],[Icon])
  VALUES
   (@ModuleCode, @LastLineNo, @FunctionCode, @FunctionDesc, N‘N‘, @SeriesOption, @SeriesCode,
   GETDATE(), ‘MIS‘, GETDATE(), ‘MIS‘, N‘‘, N‘‘,‘16‘)

  IF ‘EMP‘=‘EMP‘ OR ‘%SYS%‘=‘EMPT‘ BEGIN
   INSERT [ADAUTD]
    ([User_Group], [Module_Code], [Function_No], [Function_Code], [Description],
    [Suspended], [Allow_Read], [Allow_Create], [Allow_Update], [Allow_Delete], [Allow_Print],
    [Allow_Post], [Allow_All_Tran])
   VALUES
    (‘SYSADM‘, @ModuleCode, @LastLineNo, @FunctionCode, @FunctionDesc,
    ‘N‘ ,‘Y‘, ‘Y‘, ‘Y‘, ‘Y‘, ‘Y‘,
    ‘Y‘, ‘Y‘)

   INSERT [ADMNUD]
    ([User_Group], [Process_Code], [Function_Code], [Description], [Menu_Type], [Menu_Code],
    [Response_Type], [Suspended])
   VALUES
    (‘SYSADM‘, ‘M3_REP‘, @FunctionCode, @FunctionDesc, ‘DOC‘, ‘921‘,
    ‘STDFUNC‘, ‘N‘)
  END

 END
END
GO

使用SQL语句的好处是可支持自动化更新部署,当系统检查到有版本更新时可自动跑SQL语句,当客户数量比较多时这个方法可提高效率。

 

3 生成接口文件与接口实现类。

Code Smith模板化的代码生成极大的方便了代码生成功能的开发,参考如下的接口模板文件Interface.cst文件,源文件如下所示:

<%@ CodeTemplate Language="C#" TargetLanguage="C#" Src="" Inherits="" Debug="True" Description="Template description here." %>
<%@ Property Name="EntityPicker" Type="ISL.Extension.EntityPickerProperty" Optional="False" Category="Project" Description="This property uses a custom modal dialog editor." %>
<%@ Property Name="AssemblyFile" Type="System.String" Default="" Optional="False" Category="Project" Description=""
 Editor="System.Windows.Forms.Design.FileNameEditor"%>

<%@ Assembly Name="System.Data" %>
<%@ Import Namespace="System.Data" %>
<%@ Assembly Name="ISL.Empower.Extension" %>
<%@ Import Namespace="ISL.Extension" %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Assembly Name="SD.LLBLGen.Pro.ORMSupportClasses.NET20" %>
<%@ Import Namespace="SD.LLBLGen.Pro.ORMSupportClasses" %>

<script runat="template">

public string EntityName
{
    get
    {
	   return EntityPicker.EntityName;
    }
}

public string ShortEntityName
{
    get
    {
             return EntityName.Substring(0,EntityName.Length-6);
    }
}

public string FullEntityName
{
    get
    {
      return string.Format("{0}.EntityClasses.{1}", BusinessLogicProjectName, EntityName);
    }
}

private string _businessLogicProjectName;

public string BusinessLogicProjectName
{
    get
    {
        if(string.IsNullOrWhiteSpace(_businessLogicProjectName))
              _businessLogicProjectName=EntityClassHelper.PrefixProjectName(AssemblyFile);
        return _businessLogicProjectName;
    }
}

public string EntityParamerList
{
    get
    {
        IEntity2 policy = EntityClassHelper.GetEntityObject(AssemblyFile, EntityPicker.EntityName);
        string parm = string.Empty;
        List<string> parms=new List<string>();
        foreach (IEntityField2 field in policy.PrimaryKeyFields)
        {
             parm = string.Format("{0} {1}", field.DataType.Name, field.Name);
             parms.Add(parm);
        }
        return string.Join(",", parms.ToArray());
    }
}

  public  string EntityLowercaseName
  {
    get
        {
            return  EntityPicker.EntityName.Substring(0, 1).ToLower() + EntityPicker.EntityName.Substring(1);
        }
  }

</script>

using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
using SD.LLBLGen.Pro.ORMSupportClasses;

using <%=BusinessLogicProjectName%>;
using <%=BusinessLogicProjectName%>.FactoryClasses;
using <%=BusinessLogicProjectName%>.EntityClasses;
using <%=BusinessLogicProjectName%>.HelperClasses;
using <%=BusinessLogicProjectName%>.InterfaceClasses;
using <%=BusinessLogicProjectName%>.DatabaseSpecific;

namespace <%=BusinessLogicProjectName%>.InterfaceClasses
{
    public interface I<%=ShortEntityName%>Manager
	{
	     <%=EntityName%> Get<%=ShortEntityName%>(Guid sessionId,<%=EntityParamerList %>);
	     <%=EntityName%> Get<%=ShortEntityName%>(Guid sessionId,<%=EntityParamerList %>,IPrefetchPath2 prefetchPath);
              <%=EntityName%> Get<%=ShortEntityName%>(Guid sessionId,<%=EntityParamerList %>,IPrefetchPath2 prefetchPath,ExcludeIncludeFieldsList fieldList);

	     EntityCollection Get<%=ShortEntityName%>Collection(Guid sessionId,IRelationPredicateBucket filterBucket);
              EntityCollection Get<%=ShortEntityName%>Collection(Guid sessionId,IRelationPredicateBucket filterBucket,ISortExpression sortExpression);
	     EntityCollection Get<%=ShortEntityName%>Collection(Guid sessionId,IRelationPredicateBucket filterBucket,ISortExpression sortExpression, IPrefetchPath2 prefetchPath);
              EntityCollection Get<%=ShortEntityName%>Collection(Guid sessionId,IRelationPredicateBucket filterBucket, ISortExpression sortExpression, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList fieldList);

	     <%=EntityName%>  Save<%=ShortEntityName%>(Guid sessionId,<%=EntityName%>  <%=EntityLowercaseName%>);
	     <%=EntityName%>  Save<%=ShortEntityName%>(Guid sessionId,<%=EntityName%>  <%=EntityLowercaseName%> ,EntityCollection entitiesToDelete);
	     <%=EntityName%>  Save<%=ShortEntityName%>(Guid sessionId,<%=EntityName%> <%=EntityLowercaseName%>, EntityCollection entitiesToDelete, string seriesCode);

	     void Delete<%=ShortEntityName%>(Guid sessionId,<%=EntityName%>  <%=EntityLowercaseName%>);

	     bool Is<%=ShortEntityName%>Exist(Guid sessionId,<%=EntityParamerList %>);
	     bool Is<%=ShortEntityName%>Exist(Guid sessionId,IRelationPredicateBucket filterBucket);
	     int  Get<%=ShortEntityName%>Count(Guid sessionId,IRelationPredicateBucket filterBucket);

	     <%=EntityName%> Clone<%=ShortEntityName%>(Guid sessionId,<%=EntityParamerList %>);
	     void Post<%=ShortEntityName%>(Guid sessionId,<%=EntityParamerList %>);
	     void Post<%=ShortEntityName%>(Guid sessionId,<%=EntityName%> <%=EntityLowercaseName%>);
	}
}

在Code Smith代码生成器中跑Interface.cst模板,可得到如下的接口文件:


using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
using SD.LLBLGen.Pro.ORMSupportClasses;

using ISL.BusinessLogic;
using ISL.BusinessLogic.FactoryClasses;
using ISL.BusinessLogic.EntityClasses;
using ISL.BusinessLogic.HelperClasses;
using ISL.BusinessLogic.InterfaceClasses;
using ISL.BusinessLogic.DatabaseSpecific;

namespace ISL.BusinessLogic.InterfaceClasses
{
    public interface ISalesContractManager
    {
        SalesContractEntity GetSalesContract(Guid sessionId, String ContractNo);
        SalesContractEntity GetSalesContract(Guid sessionId, String ContractNo, IPrefetchPath2 prefetchPath);
        SalesContractEntity GetSalesContract(Guid sessionId, String ContractNo, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList fieldList);

        EntityCollection GetSalesContractCollection(Guid sessionId, IRelationPredicateBucket filterBucket);
        EntityCollection GetSalesContractCollection(Guid sessionId, IRelationPredicateBucket filterBucket, ISortExpression sortExpression);
        EntityCollection GetSalesContractCollection(Guid sessionId, IRelationPredicateBucket filterBucket, ISortExpression sortExpression, IPrefetchPath2 prefetchPath);
        EntityCollection GetSalesContractCollection(Guid sessionId, IRelationPredicateBucket filterBucket, ISortExpression sortExpression, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList fieldList);

        SalesContractEntity SaveSalesContract(Guid sessionId, SalesContractEntity salesContractEntity);
        SalesContractEntity SaveSalesContract(Guid sessionId, SalesContractEntity salesContractEntity, EntityCollection entitiesToDelete);
        SalesContractEntity SaveSalesContract(Guid sessionId, SalesContractEntity salesContractEntity, EntityCollection entitiesToDelete, string seriesCode);

        void DeleteSalesContract(Guid sessionId, SalesContractEntity salesContractEntity);

        bool IsSalesContractExist(Guid sessionId, String ContractNo);
        bool IsSalesContractExist(Guid sessionId, IRelationPredicateBucket filterBucket);
        int GetSalesContractCount(Guid sessionId, IRelationPredicateBucket filterBucket);

        SalesContractEntity CloneSalesContract(Guid sessionId, String ContractNo);
        void PostSalesContract(Guid sessionId, String ContractNo);
        void PostSalesContract(Guid sessionId, SalesContractEntity salesContractEntity);
    }
}

接口的实现类(Manager.cst) 也只需要借助于Code Smith生成即可。生成完成之后,需要增加主从表的处理,比如主表的保存和删除方法需要做少量代码修改。

保存方法增加单据编码功能代码,如下代码片段所示:

 public SalesContractEntity SaveSalesContract(Guid sessionId, SalesContractEntity SalesContract, EntityCollection entitiesToDelete, string seriesCode)
        {
            string currentRefNo = string.Empty;
            using (DataAccessAdapterBase adapter = GetCompanyDataAccessAdapter(sessionId))
            {
                try
                {
                    adapter.StartTransaction(IsolationLevel.ReadCommitted, "Save SalesContract");
                    if (SalesContract.IsNew && seriesCode != string.Empty)
                    {
                        currentRefNo = SalesContract.ContractNo;
                        IDocumentSerializationManager serializationManager = ClientProxyFactory.CreateProxyInstance<IDocumentSerializationManager>();
                        SalesContract.ContractNo = serializationManager.GetNextSerialNo(sessionId, seriesCode, SalesContract.ContractNo, SalesContract);
                    }

保存方法增加子表的保存方法

 ISalesContractDetailManager contractDetailManager = ClientProxyFactory.CreateProxyInstance<ISalesContractDetailManager>();
                    if (entitiesToDelete != null)
                    {
                        foreach (IEntity2 entity in entitiesToDelete)
                        {
                            if (entity is SalesContractDetailEntity)
                                contractDetailManager.DeleteSalesContractDetail(sessionId, (SalesContractDetailEntity)entity);
                        }
                    }

                    adapter.SaveEntity(SalesContract, true, false);

                    foreach (SalesContractDetailEntity salesContractDetailEntity in SalesContract.SalesContractDetail)
                    {
                        contractDetailManager.SaveSalesContractDetail(sessionId, salesContractDetailEntity);
                    }

遵守数据库表保存的基本原则,保存时先保存主表,再保存子表,删除时是先删除子表,再删除主表。

删除方法增加删除子表的代码:

 public void DeleteSalesContract(Guid sessionId, SalesContractEntity SalesContract)
        {
            using (DataAccessAdapter adapter = GetCompanyDataAccessAdapter(sessionId))
            {
                if (!adapter.IsEntityExist<SalesContractEntity>(SalesContract))
                    return;

                try
                {
                    adapter.StartTransaction(IsolationLevel.ReadCommitted, "Delete SalesContract");

                    ISalesContractDetailManager contractDetailManager = ClientProxyFactory.CreateProxyInstance<ISalesContractDetailManager>();
                    foreach (SalesContractDetailEntity salesContractDetailEntity in SalesContract.SalesContractDetail)
                    {
                        contractDetailManager.DeleteSalesContractDetail(sessionId, salesContractDetailEntity);
                    }

LLBL Gen没有采取级联的方式来做数据的删除,也不推荐这样处理,这样对于数据的验证相对难于处理。数据库在确认删除与应用程序的验证方法方面难于沟通,所以LLBL Gen 推荐实体相关的操作都由应用程序控制,要删除数据,要验证逻辑,都交给应用程序来实现。

 

4 增加界面文件,拖放控件,绑定数据源,用界面代码生成器(EntryForm.cst)生成界面实现文件。

自从用上了Visual Studio 2010, 窗体设计器的性能比之前的版本提升很大,Visual Studio 2012/2013的窗体设计器性能更加卓越,期待着即将发布的Visual Studio 2015能持续改善窗体设计器的性能。Visual Studio在全球有众多的客户,提高性能就意味着加快工作效率,也就是实现了节约能源(电力消耗),Google也有很多程序改善,提高程序性能可环保,节约电能开支。

从Visual Studio 2010迁移到Visual Studio 2012/2013的一个很主要的理由也是因为它的窗体设计器效率高。

再来看一下销售合同界面的主要代码,下面的代码是用Code Smith模板生成的,实现了界面代码自动生成:

    [FunctionCode("SLSOCE")]
    public partial class SalesContractEntry : EntryForm
    {
        private ISalesContractManager _salesContractEntityManager = null;
        private SalesContractEntity _salesContractEntity = null;

        public SalesContractEntry()
        {
            InitializeComponent();
        }

        protected override void OnLoad(EventArgs e)
        {
            if (!DesignMode)
                this._salesContractEntityManager = ClientProxyFactory.CreateProxyInstance<ISalesContractManager>();
            base.OnLoad(e);
        }

        protected override void InitNavigator(InitNavigatorArgs args)
        {
            base.InitNavigator(args);
            args.SortExpression.Add(SalesContractFields.ContractNo | SortOperator.Ascending);
            args.PredicateBucket.PredicateExpression.Add(SalesContractFields.Closed == false);
        }

        protected override EntityBase2 LoadData(Dictionary<string, string> refNo)
        {
            base.LoadData(refNo);
            string ContractNo = string.Empty;
            if (refNo.TryGetValue("ContractNo", out ContractNo))
            {
                IPrefetchPath2 prefetchPath = new PrefetchPath2((int) EntityType.SalesContractEntity);
                prefetchPath.Add(SalesContractEntity.PrefetchPathSalesContractDetail);
                _salesContractEntity = _salesContractEntityManager.GetSalesContract(Shared.CurrentUserSessionId, ContractNo, prefetchPath);
            }
            else
            {
                _salesContractEntity = new SalesContractEntity();
            }
            return _salesContractEntity;
        }

        protected override void InitializeLayout()
        {
            base.InitializeLayout();
            gridSalesOrder.OverrideReadOnlyAppearance(SalesContractDetailFields.OrderDate.Name,true);
            gridSalesOrder.OverrideReadOnlyAppearance(SalesContractDetailFields.DueDate.Name, true);
            gridSalesOrder.OverrideReadOnlyAppearance(SalesContractDetailFields.Closed.Name, true);
            gridSalesOrder.OverrideAllowEditForNewOnlyColumn(SalesContractDetailFields.EntryNo.Name, true);
        }

        protected override void BindControls(EntityBase2 entity)
        {
            base.BindControls(entity);
            this.salesContractBindingSource.DataSource = entity;
        }

        protected override EntityBase2 Add()
        {
            base.Add();
            this._salesContractEntity = new SalesContractEntity();
            return _salesContractEntity;
        }

        protected override EntityBase2 Save(EntityBase2 entityToSave, EntityCollection entitiesToDelete)
        {
            SalesContractEntity SalesContractEntity = (SalesContractEntity)entityToSave;
            this._salesContractEntity = this._salesContractEntityManager.SaveSalesContract(Shared.CurrentUserSessionId, SalesContractEntity, entitiesToDelete, SeriesCode);
            return this._salesContractEntity;
        }

        protected override void Delete(EntityBase2 entityToDelete)
        {
            base.Delete(entityToDelete);
            SalesContractEntity SalesContractEntity = (SalesContractEntity)entityToDelete;
            this._salesContractEntityManager.DeleteSalesContract(Shared.CurrentUserSessionId, SalesContractEntity);
        }

        protected override object Clone(Dictionary<string, string> refNo)
        {
            base.Clone(refNo);
            return null;
        }

        protected override void ReleaseResources()
        {
            base.ReleaseResources();
            try
            {
                _salesContractEntity = null;
                _salesContractEntityManager = null;
            }
            catch
            {

            }
        }

        private void btnPost_Click(object sender, EventArgs e)
        {
            this.PerformPost(true);
        }

        protected override void Post(EntityBase2 entityToPost)
        {
            base.Post(entityToPost);
            SalesContractEntity resignEntity = entityToPost as SalesContractEntity;
            _salesContractEntityManager.PostSalesContract(Shared.CurrentUserSessionId, resignEntity);
            ISL.WinUI.Shared.ShowInfo("Transaction ({0}) is posted successfully", new object[] { resignEntity.ContractNo });
        }
    }

编译运行一下程序,如下所示,一个基本的增删查改的功能就完成了,开发效率非常高,代码自动化程度也高。

 

 

5 增加类型初始化,验证,查找与钻取,自动带值,主从表事件关联

在实体类型定义文件中,增加初始化代码,比如单据默认值(单据的创建日期,单据的默认状态),系统默认值(单据的创建用户和期间):

public partial class SalesContractEntity
	{
		protected override void OnInitialized()
		{
			base.OnInitialized();

			// Assign default value for new entity
			if (Fields.State == EntityState.New)
			{
				#region DefaultValue

				// __LLBLGENPRO_USER_CODE_REGION_START DefaultValue
			    this.Fields[(int) SalesContractFieldIndex.Closed].CurrentValue = false;
			    // __LLBLGENPRO_USER_CODE_REGION_END
				#endregion
			}

			InitEventHandlers();
			this.Validator = Singleton<SalesContractValidator>.Instance;
		}

增加从表默认值,比如主表的主键是参考编号,从表则用两个主键,前一个是参考编号,后一个主键是依次增长的自动序列号(10,20,30…..或1,2,3……):

 private void JobOrderAmendmentRemarks_EntityAdded(object sender, CollectionChangedEventArgs e)
        {
            if (e.InvolvedEntity.IsNew)
            {
                SalesContractDetailEntity remark = (SalesContractDetailEntity)e.InvolvedEntity;
                decimal max = 0;
                foreach (SalesContractDetailEntity jobOrderAmendmentRemark in this.SalesContractDetail)
                {
                    if (jobOrderAmendmentRemark.EntryNo > max)
                    {
                        max = jobOrderAmendmentRemark.EntryNo;
                    }
                }
                remark.EntryNo = max + Shared.CompanySetting.AutoIncBy;
            }
        }

如果你有Enterprise Solution 开发框架的例子代码,这些代码都是可以从原有的文件中直接拷贝过来稍做修改后即可,无任何技巧,唯手熟练。

查找与钻取

给客户编号属性值增加查找,设置Lookup属性即可。

增加查找一般都需要增加验证,增加客户编号验证。

   [Serializable]
    public partial class SalesContractValidator : ValidatorBase
    {
        // Add your own validation code between the two region markers below. You can also use a partial class and add your overrides in that partial class.

        // __LLBLGENPRO_USER_CODE_REGION_START ValidationCode
        public override bool ValidateFieldValue(IEntityCore involvedEntity, int fieldIndex, object value)
        {
            bool result = base.ValidateFieldValue(involvedEntity, fieldIndex, value);
            if (!result) return false;

            switch ((SalesContractFieldIndex) fieldIndex)
            {
                case SalesContractFieldIndex.CustomerNo:
                    return this.ValidateCustomerNo((string) value);
            }

            return true;
        }

        private bool ValidateCustomerNo(string value)
        {
            if (!string.IsNullOrEmpty(value))
            {
                ICustomerManager customerManager = ClientProxyFactory.CreateProxyInstance<ICustomerManager>();
                customerManager.ValidateCustomerNo(Shared.CurrentUserSessionId, value);
            }

            return true;
        }

增加客户编号钻取,在界面中双击此控件可直接跳转到相应的主档功能。

这样就完成了新功能的开发,想要做到基于EntryForm应用程序的高效率开发,必须通晓框架的功能,知道在哪里插入什么样的代码,这在Enterprise Solution技术培训中会详细讲解。

 

查询窗体 Query/Enquiry

有两种类型的查询功能,一个是数据输入窗体查询,这种查询需要继承原有的输入窗体(EntryForm),修改属性即可实现,参考下面的销售合同查询功能的实现。

销售合同查询功能全部的代码如下,只需要重写几个方法即可:

 [FunctionCode("SLSOCQ")]
    public partial class SalesContractEnquiry : SalesContractEntry
    {
        public SalesContractEnquiry()
        {
            InitializeComponent();

            this.SupportAdd = false;
            this.SupportEdit = false;
            this.SupportDelete = false;
        }

        protected override void InitNavigator(FunctionFormBase.InitNavigatorArgs args)
        {
            args.PredicateBucket.PredicateExpression.Add(SalesContractFields.Closed == true);
            if (!AllowViewAllTransaction)
                args.PredicateBucket.PredicateExpression.Add(SalesContractFields.CreatedBy ==ISL.BusinessLogic.Shared.CurrentUserSession.UserId);

            args.SortExpression.Add(SalesContractFields.ContractNo | SortOperator.Ascending);
        }

        protected override void InitializeLayout()
        {
            base.InitializeLayout();
            this.btnClose.Visible = false;
            this.txtContractNo.Lookup.FilterName = "Posted";
        }
    }

第二种查询是自定义查询,可查询任意条件过滤的多个表的数据。

EntryForm没有使用LLBL Gen的Typeed Lists或Typed Views,而是通过自定义的查询(Custom Query)来实现数据查询。

来看一下送货日记的查询界面,这个界面的主要布局是上面是过滤条件,下面是要根据过滤条件查询到的结果数据。

送货日记的全部源代码如下所示,

 [FunctionCode("SQMEGS")]
    public sealed partial class ShipmentJournalEnquiry : EnquiryForm
    {
        private IUserDefinedQueryManager _udqManager;
        private bool _show;

        public ShipmentJournalEnquiry()
        {
            InitializeComponent();
        }

        protected override void InitializeLayout()
        {
            base.InitializeLayout();

            this.grdShipment.DisplayLayout.Bands[0].Columns["Year"].Format = "####";
            this.grdShipment.DisplayLayout.Bands[0].Columns["Month"].Format = "##";
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            if (!DesignMode)
            {
                _udqManager = ClientProxyFactory.CreateProxyInstance<IUserDefinedQueryManager>();
                _show = true;
            }
        }

        protected override void InitNavigator(ISL.WinUI.Forms.FunctionFormBase.InitNavigatorArgs args)
        {
            base.InitNavigator(args);

            if (_show)
                this.FetchShipment();
        }

        private void FetchShipment()
        {
            IPredicateExpression predicateExpression = null;
            IRelationPredicateBucket filterBucket = new RelationPredicateBucket();

            if (Shared.CurrentUserSession.AllowAccessAllCustomers)
                filterBucket.PredicateExpression.Add(Shared.GetAllowedCustomerNoPredicateExpression(ShipmentFields.CustomerNo));

            if (!AllowViewAllTransaction)
                filterBucket.PredicateExpression.Add(ShipmentFields.CreatedBy == Shared.CurrentUser.UserId);

            predicateExpression = this.efcCustomerNo.GetPredicateExpression();
            if (predicateExpression != null)
                filterBucket.PredicateExpression.Add(predicateExpression);

            predicateExpression = this.efcShipFrom.GetPredicateExpression();
            if (predicateExpression != null)
                filterBucket.PredicateExpression.Add(predicateExpression);

            predicateExpression = this.efcPostedFilter.GetPredicateExpression();
            if (predicateExpression != null)
                filterBucket.PredicateExpression.Add(predicateExpression);

            predicateExpression = this.efcReturnedFilter.GetPredicateExpression();
            if (predicateExpression != null)
                filterBucket.PredicateExpression.Add(predicateExpression);

            ResultsetFields fields = new ResultsetFields(16);
            fields.DefineField(ShipmentFields.RefNo, 0);
            fields.DefineField(ShipmentFields.ShipmentDate, 1);

            DbFunctionCall dbYear = new DbFunctionCall("Year", new object[] { ShipmentFields.ShipmentDate });
            EntityField2 Year = new EntityField2("Year", dbYear);
            fields.DefineField(Year, 2);

            DbFunctionCall dbMonth = new DbFunctionCall("Month", new object[] { ShipmentFields.ShipmentDate });
            EntityField2 Month = new EntityField2("Month", dbMonth);
            fields.DefineField(Month, 3);

            fields.DefineField(ShipmentFields.Posted, 4);
            fields.DefineField(ShipmentFields.Returned, 5);
            fields.DefineField(ShipmentFields.CustomerNo, 6);
            fields.DefineField(ShipmentFields.CustomerName, 7);
            fields.DefineField(ShipmentFields.Ccy, 8);
            fields.DefineField(ShipmentFields.TotItemAmt, 9);
            fields.DefineField(ShipmentFields.Etd, 10);
            fields.DefineField(ShipmentFields.ShipFrom, 11);
            fields.DefineField(ShipmentFields.PostedBy, 12);
            fields.DefineField(ShipmentFields.PostedDate, 13);
            fields.DefineField(ShipmentFields.CreatedBy, 14);
            fields.DefineField(ShipmentFields.CreatedDate, 15);

            ISortExpression sortExpression = new SortExpression(ShipmentFields.RefNo | SortOperator.Ascending);

            DataTable table = QueryHelper.GetQueryResult(Shared.CurrentUserSession.CompanyCode, fields, filterBucket, sortExpression, null, true);
            this.shipmentBindingSource.DataSource = table;
            this.grdShipment.SetDataBinding(this.shipmentBindingSource, null, true, true);
        }

        private void btnRefresh_Click(object sender, EventArgs e)
        {
            this.PerformRefresh();

            //clear filter
            this.efcShipFrom.Clear();
            this.efcCustomerNo.Clear();
        }

        private void grdShipment_BeforeDrillDown(object sender, DrillDownEventArgs e)
        {
            if (Shared.StringCompare(this.grdShipment.ActiveCell.Column.Key, ShipmentFields.RefNo.Name) == 0)
            {
                DataRowView row = this.shipmentBindingSource.Current as DataRowView;
                if (row != null)
                {
                    bool isPosted = Convert.ToBoolean(row[ShipmentFields.Posted.Name]);
                    e.DrillDownValue.FunctionCode = (isPosted ? "SQMETS" : "SLSHSE");
                }
            }
        }

        protected override void OnBeforeGridExport(CancelableGridDataExportEventArgs args)
        {
            base.OnBeforeGridExport(args);
            args.Grid = this.grdShipment;
        }

        protected override void OnBeforeGridPrint(CancelableGridDataExportEventArgs args)
        {
            base.OnBeforeGridPrint(args);
            args.Grid = this.grdShipment;
        }

        protected override void ReleaseResources()
        {
            try
            {
                this._udqManager = null;
                this.btnRefresh.Click -= new EventHandler(btnRefresh_Click);
                this.grdShipment.BeforeDrillDown -= new ISL.WinUI.DrillDownEventHandler(this.grdShipment_BeforeDrillDown);
            }
            catch
            {
            }
            base.ReleaseResources();
        }

这个功能的数据读取代码没有封装到BackgroundWorker后台线程组件中,当数据量多时会发生界面死锁,用户体验性不好。

 

报表 Report

水晶报表已经是报表行业的工业标准,完善的功能与强大的客户化开发功能,水晶报表的市场占有率一直很高。微软的后起之秀Reporting Services也相当优秀,Enterprise Solution对这两种类型的报表都有完备的支持。

Enterprise Solution解决了报表开发中令开发人员头疼的界面传参问题,它可以依据一个设定自动生成报表参数界面,通过ReportViewer自动传递到报表文件中。

如下图所示,当开发完成水晶报表之后,需要在报表对话框中增加一个参数设定,用于生成报表的参数:

依据上面的三个参数,报表执行引擎产生一个参数输入界面,如下图所示

用户在界面中输入值,点击查看按钮,报表引擎将用户输入的值传递到报表中,这个过程不需要开发人员的编程或设定。

此外,报表引擎还处理了水晶报表运行库的问题,报表中的标签也会自动翻译。

 

另外,工作流与计划任务的二次开发也相当常见,因相对复杂需要单独成篇,暂不介绍。

工作流(Workflow)

计划任务(Scheduling)

时间: 2024-11-09 00:45:07

Enterprise Solution 客户化二次开发的相关文章

Enterprise Solution 企业管理软件开发框架

Enterprise Solution 开源项目资源汇总 Visual Studio Online 源代码托管 企业管理软件开发框架 Enterprise Solution 是一套管理软件开发框架,在这个框架基础上开发出一套企业资源计划系统Enterprise Edition. 现将Enterprise Solution开发过程中遇到问题时的解决方案资源共享出来,供参考. 项目源代码地址是 https://enterpriseedition.visualstudio.com/ 1  工具软件界面

Enterprise Solution 企业资源计划管理软件 C/S架构,支持64位系统,企业全面应用集成,制造业信息化

Enterprise Solution是一套完整的企业资源计划系统,功能符合众多制造业客户要求.系统以.NET Framework技术作为开发架构,完善的功能可有效地帮助企业进行运营策划,减低成本,如期交付产品,使客户对企业的运作完全在运筹帷幄之中. 主要模块 Modules Enterprise Solution 主要包含以下7大主要模块: 模块 主要功能 销售 Sales 报价,销售订单,送货,退货,客户发票,销售包装 采购 Purchasing 采购申请,采购订单,采购收货,退货,验货,供

Enterprise Solution 开源项目资源汇总 Visual Studio Online 源代码托管 企业管理软件开发框架

Enterprise Solution 是一套管理软件开发框架,在这个框架基础上开发出一套企业资源计划系统Enterprise Edition. 现将Enterprise Solution开发过程中遇到问题时的解决方案资源共享出来,供参考. 项目源代码地址是 https://enterpriseedition.visualstudio.com/ 访问帐户[email protected],密码是abc!12345 1  工具软件界面原型 Management Studio 工具类程序的界面原型,

Enterprise Solution 界面设计规范

Enteprise Solution有一套自己的界面设计规范,也是很多年(10年以上)管理软件界面精华的积累.没有一个软件从一开始就很善于界面设计,许多个小小的改善,比如控件位置的移动,控件摆放顺序的改变,都是经过客户检验或是深思熟虑的. 1 对于必须输入值的项,控件中有小光标表示. 如上图所示,合约编号和 客户编号都是必须输入的,所以该控件的右边有一个小光标显示.当控件中有值时,这个光标不再显示. 国内的管理软件比如金蝶软件,对于必须输入值,它会在标签处标识一个星号,如下图所示,端口号的标签后

Enterprise Solution 3.1 企业应用开发框架 .NET ERP/CRM/MIS 开发框架,C/S架构,SQL Server + ORM(LLBL Gen Pro) + Infragistics WinForms

行业:基于数据库的制造行业管理软件,包含ERP.MRP.CRM.MIS.MES等企业管理软件 数据库平台:SQL Server 2005或以上 系统架构:C/S 开发技术 序号 领域 技术 1 数据库 SQL Server 2008 R2 2 程序语言 C# .NET 4 3 数据访问 LLBL Gen Pro 3.1 https://www.llblgen.com/ 4 界面 Windows Forms  http://www.infragistics.com/ 5 数据通讯 .NET Rem

客户现场封闭式开发

随着项目经理的一声号响,项目团队一行人浩浩荡荡向客户中心进发了, 小伙伴们立誓不把客户拿下,绝不归来.看着小伙伴们一个个的豪情壮志,项目经理很开心,似乎已经看到了胜利的结果.因为在以往这种类似的战斗中,胜利的概率都是99.9%. 在客户现场开发,虽然看似是一件很简单的事情,但对与项目经理来说,有时候却是一件极其艰难的事情.因为项目经理首先要协调整个团队,看看项目组的成员是否能够适应出差.其次要考虑项目出差的成本预算.因为团队成员出差的饮食住宿费用及出差补助对于项目来说也是一项不小的开支,尽管这些

基于ASP.NET WPF技术及MVP模式实战太平人寿客户管理项目开发(Repository模式)

亲爱的网友,我这里有套课程想和大家分享,如果对这个课程有兴趣的,可以加我的QQ2059055336和我联系. 课程背景 本课程是教授使用WPF.ADO.NET.MVVM技术来实现太平人寿保险有限公司保险客户管理系统,是学习WPF开发中的一门主打课程之一. WPF是一个框架,它供程序员开发出媲美Mac程序的酷炫界面. Blend是一种工具,可以在美工板上绘制形状.路径和控件,然后修改其外观和行为,从而直观地设计应用程序 Repository\MVVM\MVP设计模式是WPF常用的系统架构 Auto

Windows 10 部署Enterprise Solution 5.5

Windows 10正式版发布以后,新操作系统带来了许多的变化.现在新购买的电脑安装的系统应该是Windows 10.与当初用户不习惯Windows 7,购买新电脑后第一个想做的事情就是重装成XP,估计现在的Windows 10新用户也有这种冲动(安装Windows 7).界面方面的变化需要一些时间去消化和习惯,程序架构的变化则需要我们猿族去体验. 先看一张Windows 10的界面截图,点击开始菜单后,整个系统呈现的画面. 开始菜单回归到屏幕左下角,右键点击开始菜单弹出的菜单项包含系统管理的常

Enterprise Solution 2.3

1. 登陆窗体和主界面增加语言选项,同时可记住用户登陆的语言和数据库. 2. 主界面的树功能可记住上次打开的模块菜单. 3. 修复主界面菜单生成问题和导航图区上下文菜单生成问题. 4. 增加自动更新功能.可以将最新的程序包部署到HTTP服务器上,主界面的Check Update功能可以自动下载程序文件并解压缩到本地,再重新启动主程序. 因为ERP要考虑各种客户使用的版本不一定相同,因为稳定的原因,不是所有的客户都愿意花费时间升级到最新版本.所以此功能要配合数据库版本控制同时实施为最优方案.设计方