在Winform應用中增加通用的業務編碼規則生成

2023-12-04 12:01:22

在我們很多應用系統中,往往都需要根據實際情況生成一些編碼規則,如訂單號、入庫單號、出庫單號、退貨單號等等,我們有時候根據規則自行增加一個函數來生成處理,不過我們仔細觀察後,發現它們的編碼規則有很大的共通性,因此可以考慮使用一些通用的業務編碼規則生成,從而在系統中統一維護即可,本篇隨筆介紹如何在WInform介面中實現通用的業務編碼規則生成。

1、常見單號的業務編碼規則

剛才我們提到一些編碼規則,如訂單號、入庫單號、出庫單號、退貨單號等等,它們都是有大同小異的規則,有字首、有日期的編碼、有一些流水號,還有一些特殊的規則處理,往往就是這些,需要協調好流水號的增加處理即可。

例如,原來在我的CRM業務模組中,增加了一個函數,用來生成訂單號的,如下所示。

        /// <summary>
        /// 生成單據號碼,編碼為XS-{userId}-{yyyyMMdd}-流水號
        /// </summary>
        /// <returns></returns>
        public async Task<string> GetOrderNo()
        {
            string prefix = string.Format("XS-{0}-{1}", CurrentApiUser.Id, DateTime.Now.ToString("yyyyMMdd"));

            //獲取當天的記錄數量+1
            DateTime currentDate = DateTime.Now.ToString("yyyy-MM-dd").ToDateTime(); //當前日期
            //計算條件數量+1
            int count = this.EntityDb.Count(s => s.OrderDate >= currentDate && s.OrderDate <= currentDate.AddDays(1)) + 1;

            //迴圈檢索,直到不重複的編號
            string number = string.Format("{0}-{1}", prefix, count);
            while (true)
            {
                var result = await CheckNumberExist(number);
                if (result)
                {
                    //存在增加1再判斷
                    number = string.Format("{0}-{1}", prefix, count++);
                }
                else
                {
                    break;
                }
            }

            return number;
        }

這裡為了增加對流水號的迴圈判斷,直到沒有重複的即可輸出來作為訂單號。

大多數的編碼規則大同小異,因此我們可以考慮使用共同的規則進行處理,類似我們通用字典的模組處理。訂單編碼,可以在新建訂單的時候生成,也可以提供使用者手動生成【生成編號】的操作,如下介面所示。

 

2、設計通用的業務編碼規則

我們歸納了一些編碼規則,基本上也就是字首,日期分隔,分隔符,字尾,流水號這些元素的組合,如果需要更加複雜的也可以自行調整介面,我們這裡設計一個通用的編碼規則,對這些元素進行組合設定,資料庫設計如下所示。

根據這些內容,我們使用手工編碼或者程式碼生成工具生成相關的基礎程式碼 (可以基於EnterpriseLibrary的框架程式碼或者基於SqlSugar開發框架的程式碼),最終我們都用於WInform的介面呼叫。

這裡以我們基於SqlSugar開發框架的程式碼生成為例。

生成後,會生成一個相關的業務類,實現相關的CRUD介面,如下程式碼定義所示,如果你有自己的基礎框架實現,那麼也可以忽略具體的程式碼生成,關注業務編碼的生成的的規則即可。

   /// <summary>
   /// 業務表編碼規則 應用層服務介面實現
   /// </summary>
   public class TableNumberService : MyCrudService<TableNumberInfo, string, TableNumberPagedDto>, ITableNumberService

為了控制編碼的規則生成,我們增加一個同步鎖來實現衝突處理。

        /// <summary>
        /// 同步鎖
        /// </summary>
        private static SemaphoreSlim syncRoot = new SemaphoreSlim(1);

最終我們的實現程式碼如下所示。

        /// <summary>
        /// 根據定義表名、單據頭、分割符1、分割符2,生成業務編碼。如果生成錯誤,返回空字串
        /// </summary>
        /// <param name="tableNameOrCode">表名或程式碼</param>
        /// <returns></returns>
        public async Task<string> GenerateNumber(string tableNameOrCode)
        {
            string businessNumber = "";
            await syncRoot.WaitAsync(); //等待鎖
            try
            {
                var info = await base.GetFirstAsync(s => s.TableName == tableNameOrCode || s.Code == tableNameOrCode);
                if (info != null)
                {
                    string currentDate = "";
                    string lastDate = "";
                    int currentNumber = 1; //流水號起始值
                    int serialLength = 3; //流水號長度

                    if(!info.LastGenerateTime.HasValue)
                    {
                        info.LastGenerateTime = DateTime.Now;
                    }

                    if (info.RuleFormat == "年月日")
                    {
                        currentDate = DateTime.Now.ToString("yyyyMMdd");
                        lastDate = info.LastGenerateTime.Value.ToString("yyyyMMdd");
                    }
                    else if (info.RuleFormat == "年月")
                    {
                        currentDate = DateTime.Now.ToString("yyyyMM");
                        lastDate = info.LastGenerateTime.Value.ToString("yyyyMM");
                    }

                    //如果當前日期和最後日期不一致,流水號重置為0
                    if(!currentDate.Equals(lastDate))
                    {
                        info.CurrentValue = 0;
                    }

                    //如果流水號非起始值,那麼累計計算
                    if(info.CurrentValue.HasValue && info.CurrentValue >= 0)
                    {
                        currentNumber = (int)info.CurrentValue + 1;//流水號當前值
                    }

                    //流水號長度
                    if(info.ValueLength.HasValue && info.ValueLength > 3)
                    {
                        serialLength = (int)info.ValueLength;//流水號長度
                    }
                    var SplitString1 = string.IsNullOrEmpty(info.SplitString1) ? "-" : info.SplitString1;
                    var SplitString2 = string.IsNullOrEmpty(info.SplitString2) ? "-" : info.SplitString2;

                    //生成業務編碼
                    businessNumber = $"{info.Prex}{SplitString1}{currentDate}{SplitString2}{currentNumber.ToString().PadLeft(serialLength, '0')}{info.Suffix}";

                    //更新記錄
                    info.CurrentValue = currentNumber;
                    info.SplitString1 = SplitString1;
                    info.SplitString2 = SplitString2;
                    info.CurrentNumberString = businessNumber;
                    info.LastGenerateTime = DateTime.Now;//更新最後生成編碼日期

                    await base.UpdateAsync(info);
                }
            }
            catch (Exception ex)
            {
                var errorText = $"生成單號時出現錯誤:{ex.Message}";
                LogTextHelper.Error(errorText, ex);
            }
            finally
            {
                syncRoot.Release();//釋放鎖
            }

            return businessNumber;
        }

上面主要注意的就是流水號的生成,這個稍微特殊處理一下,如果定義的規則是年月日,那麼和最後的生成日期和當前日期不一致的話(轉換為年月日對比),就認為流水號重新重置為1,否則是同一天的,流水號遞增即可。如果是年月的,也是判斷最後日期和當前日期的年月是否一致,不一致則重置為1,否則遞增。注意流水號的編碼長度,一般為4位元,如果不滿足的可以增加到6位等。

最終我們實際的業務編碼的管理介面和檢視的對應編碼的介面如下所示,供參考設計介面處理。

編輯單個業務編碼規則的介面如下所示。

 為了方便,我們這裡提供一個【測試生成】的按鈕,用於測試具體的編碼生成,我們具體的業務呼叫,就是類似這個呼叫即可。

var handNo = await BLLFactory<ITableNumberService>.Instance.GenerateNumber(tableNameOrCode);

同樣,我們也可以把這個介面搬到WPF框架介面上去,可以重用具體的業務編碼規則處理,如上類似的介面處理。

 單個通用的業務編碼規則的編輯介面如下所示。

 因此,不管對於Winform還是WPF的介面,他們的展示方式都是類似的,我們可以重用業務層對通用編碼規則的定義。