解析大型.NET ERP系统 单据标准(新增,修改,删除,复制,打印)功能程序设计
ERP系统的单据具备标准的功能,这里的单据可翻译为Bill,Document,Entry,具备相似的工具条操作界面。通过设计可复用的基类,子类只需要继承基类窗体即可完成单据功能的程序设计。先看标准的销售合同单据界面:
本篇通过销售合同单据功能,依次讲解编程要点,供参考。
1 新增 Insert
窗体有二种状态,一种是编辑状态,别一种是数据浏览状态,区别在于编辑状态的窗体数据被修改(dirty),在窗体关闭时需要保存数据。点击工具条的新增(Insert)按钮,窗体进入编辑状态。新增状态需要对窗体所编辑的单据设置默认值。一般我们在实体映射文件中设置默认值,参考下面的例子代码:
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 } } }
也可以考虑在窗体中做默认值设定。当遇到这样一种场景,两个功能对应同一个实体类型,则需要在界面中根据需要初始化值,参考下面的程序片段。
protected override EntityBase2 Add() { base.Add(); this._inventoryMovement = new InventoryMovementEntity(); this._inventoryMovement.TranType = GetStringValue(InventoryTransactionType.Movement); return this._inventoryMovement; }
2 保存 Save
窗体基类检测到界面中的控件值被修改过,窗体状态变为编辑状态,点击保存按钮执行保存方法。保存方法的主要内容是将数据源控件(BindingSource)所绑定的控件值更新到它映射的实体中,再调用窗体保存方法保存实体。
protected override EntityBase2 Save(EntityBase2 entityToSave, EntityCollection entitiesToDelete) { SalesContractEntity SalesContractEntity = (SalesContractEntity)entityToSave; this._salesContractEntity = this._salesContractEntityManager.SaveSalesContract( SalesContractEntity, entitiesToDelete, SeriesCode); return this._salesContractEntity; }
entityToSave是数据源控件绑定的实体类型,在保存完成后,这个值再次绑定到数据源控件中。
3 删除 Delete
窗体只有在浏览状态时,才可以点击删除按钮,删除按钮的内容是获取窗体数据源控件绑定的实体,调用窗体删除方法。
protected override void Delete(EntityBase2 entityToDelete) { base.Delete(entityToDelete); SalesContractEntity SalesContractEntity = (SalesContractEntity)entityToDelete; this._salesContractEntityManager.DeleteSalesContract(SalesContractEntity); }
对实体的任何操作,都会跑实体验证类型,比如在保存时,需要验证主键值是否已经保存过,参考下面的代码。
public override void ValidateEntityBeforeSave(IEntityCore involvedEntity) { base.ValidateEntityBeforeSave(involvedEntity); SalesContractEntity salesContract = (SalesContractEntity)involvedEntity;
if (string.IsNullOrEmpty(salesContract.ContractNo)) throw new EntityValidationException("Contract No. is required"); if (string.IsNullOrEmpty(salesContract.CustomerNo)) throw new EntityValidationException("Customer No. is required"); if (salesContract.IsNew) { ISalesContractManager salesContractManager = CreateProxyInstance<ISalesContractManager>(); if (salesContractManager.IsSalesContractExist(salesContract.ContractNo)) throw new RecordDuplicatedException(salesContract.ContractNo, "Cotract No. is already used"); } }
4 复制 Clone
窗体支持两种复制方法,复制当前加载的值,复制其它对象的值。对象值复制完成后,需用重置新的对象的主键值,让它为空或是为默认值,供用户修改。复制其它对象的值需要弹出对象选择窗体。这两种复制都需要注意复制完后,重置对象初始化默认值。因为复制时采用的是深拷贝,没有跑对象初始化值。
protected override object Clone(Dictionary<string, string> refNo) { base.Clone(refNo); string receiptRefNo; refNo.TryGetValue("ContractNo", out receiptRefNo); if (string.IsNullOrEmpty(receiptRefNo)) { using (ILookupForm lookup = GetLookupForm("SalesContractLookup")) { if (!AllowViewAllTransaction) lookup.PredicateBucket = new RelationPredicateBucket(SalesContractFields.CreatedBy == Shared.CurrentUser.UserId); lookup.SetCurrentValue(CurrentRefNo); if (lookup.ShowDialog() != DialogResult.OK)
return null; receiptRefNo = lookup.GetFirstSelectionValue(); } } if (!string.IsNullOrEmpty(receiptRefNo)) { this._salesContractEntity = this._salesContractEntityManager.CloneSalesContract(receiptRefNo); return this._salesContractEntity; } return null;
}
注意到这些方法全部是以override重写的方式出现,被基类调用。每个方法都运行在后台线程控件BackgroundWorker线程中,所以都不能操作界面控件。
5 记录浏览 Record Navigator
主要用于工具条的前四个按钮,分别对应第一笔数据,前一笔数据,下一笔数据,最后一笔数据。工具条浏览需要设定窗体的NavigateBindingSource属性,传入一个空白的BindingSource控件或是一个装载页面所有数据的BindingSource控件,工具条浏览方法重写参考下面的程序片段。
protected override void InitNavigator(InitNavigatorArgs args) { base.InitNavigator(args); args.SortExpression.Add(SalesContractFields.ContractNo | SortOperator.Ascending); args.PredicateBucket.PredicateExpression.Add(SalesContractFields.Closed == false); }
6 记录过帐 Record Post
主要用于业务单据过帐逻辑,基类的方法为空方法,不同的业务单据有不同的逻辑定义,没有可复用的代码。
protected override void Post(EntityBase2 entityToPost) { base.Post(entityToPost); SalesContractEntity resignEntity = entityToPost as SalesContractEntity; _salesContractEntityManager.PostSalesContract(resignEntity); }
这里的过帐可以理解为确认,批核,不可更改的意思。在有些系统中叫送审,审核。
7 打印 Print
一般在设计视图绑定当前窗体对应的水晶报表文件以及要传入的参数。也可以通过重写打印方法传入传数值。
protected override void Print(ref Dictionary<string, SelectionFormula> selectionFormulas, ref List<FormulaField> formulaFields, ref List<ParameterField> parameterFields) { base.Print(ref selectionFormulas, ref formulaFields, ref parameterFields); }
重写方法常用于动态指定报表文件,或是动态的参数值。不推荐在代码中这样写,这样做导致每次都需要重新编译和分发程序,推荐在水晶报表中做公式,在分发时只需要拷贝文件即可。