NumberRuleService.cs 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. namespace Admin.NET.Plugin.AiDOP.Supply;
  2. /// <summary>
  3. /// 编号规则服务。第一阶段兼容现有 NbrControl 规则,替代业务代码直接调用编号存储过程。
  4. /// </summary>
  5. public class NumberRuleService : ITransient
  6. {
  7. private readonly ISqlSugarClient _db;
  8. private readonly UserManager _userManager;
  9. public NumberRuleService(ISqlSugarClient db, UserManager userManager)
  10. {
  11. _db = db;
  12. _userManager = userManager;
  13. }
  14. public async Task<IReadOnlyList<string>> NextBatchAsync(string ruleCode, string domain, int count, string operatorNo)
  15. {
  16. try
  17. {
  18. _db.Ado.BeginTran();
  19. var numbers = await NextBatchInCurrentTransactionAsync(ruleCode, domain, count, operatorNo);
  20. _db.Ado.CommitTran();
  21. return numbers;
  22. }
  23. catch
  24. {
  25. _db.Ado.RollbackTran();
  26. throw;
  27. }
  28. }
  29. public async Task<IReadOnlyList<string>> NextBatchInCurrentTransactionAsync(string ruleCode, string domain, int count, string operatorNo)
  30. {
  31. if (string.IsNullOrWhiteSpace(ruleCode)) throw Oops.Oh("编号规则不能为空。");
  32. if (count <= 0) throw Oops.Oh("编号数量必须大于 0。");
  33. var normalizedRuleCode = ruleCode.Trim();
  34. var normalizedDomain = domain?.Trim() ?? string.Empty;
  35. var now = DateTime.Now;
  36. var tenantWhere = _userManager.TenantId > 0 ? "\n AND tenant_id = @TenantId" : string.Empty;
  37. var rulePars = new List<SugarParameter>
  38. {
  39. new("@RuleCode", normalizedRuleCode),
  40. new("@Domain", normalizedDomain)
  41. };
  42. if (_userManager.TenantId > 0) rulePars.Add(new SugarParameter("@TenantId", _userManager.TenantId));
  43. var rule = (await _db.Ado.SqlQueryAsync<NbrControlRuleRow>(
  44. $"""
  45. SELECT
  46. RecID AS Id,
  47. NbrType,
  48. NbrPre1,
  49. NbrPre2,
  50. NbrPre3,
  51. DateType,
  52. IsDateType,
  53. Description,
  54. IniValue,
  55. MinValue,
  56. `MaxValue` AS `MaxValue`,
  57. ResetValue,
  58. AllowReset,
  59. AllowSkip,
  60. AllowManual,
  61. IsActive,
  62. IsConfirm,
  63. Domain,
  64. tenant_id AS TenantId
  65. FROM NbrControl
  66. WHERE LOWER(TRIM(IFNULL(NbrType,''))) = LOWER(@RuleCode)
  67. AND (
  68. IFNULL(TRIM(domain_code),'') = @Domain
  69. OR IFNULL(TRIM(Domain),'') = @Domain
  70. OR IFNULL(TRIM(domain_code),'') = ''
  71. OR IFNULL(TRIM(Domain),'') = ''
  72. )
  73. AND IFNULL(IsActive, 1) = 1{tenantWhere}
  74. ORDER BY
  75. CASE
  76. WHEN IFNULL(TRIM(domain_code),'') = @Domain THEN 0
  77. WHEN IFNULL(TRIM(Domain),'') = @Domain THEN 1
  78. ELSE 2
  79. END,
  80. RecID
  81. LIMIT 1
  82. FOR UPDATE
  83. """,
  84. rulePars)).FirstOrDefault();
  85. if (rule == null)
  86. throw Oops.Oh($"未找到编号规则:{normalizedRuleCode}");
  87. var sequenceDate = NumberRuleFormatter.ResolveSequenceDate(rule.DateType, rule.IsDateType, now);
  88. var dayInfo = await GetDayInfoForUpdateAsync(rule, normalizedRuleCode, normalizedDomain, sequenceDate);
  89. var firstSerial = ResolveFirstSerial(rule, dayInfo);
  90. var lastSerial = firstSerial + count - 1;
  91. if (rule.MaxValue.HasValue && lastSerial > rule.MaxValue.Value)
  92. {
  93. if (rule.AllowReset == true)
  94. {
  95. firstSerial = (rule.ResetValue ?? 0) + 1;
  96. lastSerial = firstSerial + count - 1;
  97. }
  98. if (lastSerial > rule.MaxValue.Value)
  99. throw Oops.Oh($"编号规则 {normalizedRuleCode} 已超过最大值 {rule.MaxValue.Value}。");
  100. }
  101. var numbers = Enumerable.Range(firstSerial, count)
  102. .Select(serial => NumberRuleFormatter.BuildNumber(
  103. rule.NbrPre1,
  104. rule.NbrPre2,
  105. rule.NbrPre3,
  106. rule.DateType,
  107. rule.IsDateType,
  108. serial,
  109. rule.MaxValue,
  110. now))
  111. .ToList();
  112. if (dayInfo == null)
  113. await InsertDayInfoAsync(rule, sequenceDate, lastSerial, operatorNo, now);
  114. else
  115. await UpdateDayInfoAsync(dayInfo.Id, lastSerial, operatorNo, now);
  116. return numbers;
  117. }
  118. private async Task<NbrDayInfoRow?> GetDayInfoForUpdateAsync(NbrControlRuleRow rule, string ruleCode, string domain, DateTime? sequenceDate)
  119. {
  120. var dateFilter = sequenceDate.HasValue ? "AND DATE(Today) = DATE(@Today)" : string.Empty;
  121. var tenantFilter = _userManager.TenantId > 0 ? "AND tenant_id = @TenantId2" : string.Empty;
  122. var dayPars = new List<SugarParameter>
  123. {
  124. new("@RuleCode", rule.NbrType ?? ruleCode),
  125. new("@Domain", rule.Domain ?? domain),
  126. new("@Today", sequenceDate ?? DateTime.Today)
  127. };
  128. if (_userManager.TenantId > 0) dayPars.Add(new SugarParameter("@TenantId2", _userManager.TenantId));
  129. return (await _db.Ado.SqlQueryAsync<NbrDayInfoRow>(
  130. $"""
  131. SELECT
  132. RecID AS Id,
  133. NextValue
  134. FROM NbrDayInfo
  135. WHERE LOWER(TRIM(IFNULL(NbrType,''))) = LOWER(@RuleCode)
  136. AND IFNULL(TRIM(Domain),'') = @Domain
  137. {dateFilter}
  138. {tenantFilter}
  139. ORDER BY RecID
  140. LIMIT 1
  141. FOR UPDATE
  142. """,
  143. dayPars)).FirstOrDefault();
  144. }
  145. private async Task InsertDayInfoAsync(NbrControlRuleRow rule, DateTime? sequenceDate, int nextValue, string operatorNo, DateTime now)
  146. {
  147. await _db.Ado.ExecuteCommandAsync(
  148. """
  149. INSERT INTO NbrDayInfo
  150. (NbrType, Description, NbrPre1, NbrPre2, NbrPre3, Today,
  151. NextValue, IniValue, MinValue, `MaxValue`, ResetValue,
  152. AllowReset, AllowSkip, AllowManual, DateType, IsDateType,
  153. IsActive, IsConfirm, Domain, CreateUser, CreateTime, UpdateUser, UpdateTime, tenant_id)
  154. VALUES
  155. (@NbrType, @Description, @NbrPre1, @NbrPre2, @NbrPre3, @Today,
  156. @NextValue, @IniValue, @MinValue, @MaxValue, @ResetValue,
  157. @AllowReset, @AllowSkip, @AllowManual, @DateType, @IsDateType,
  158. @IsActive, @IsConfirm, @Domain, @UserNo, @Now, @UserNo, @Now, @TenantId)
  159. """,
  160. new SugarParameter("@NbrType", rule.NbrType ?? string.Empty),
  161. new SugarParameter("@Description", rule.Description ?? string.Empty),
  162. new SugarParameter("@NbrPre1", rule.NbrPre1 ?? string.Empty),
  163. new SugarParameter("@NbrPre2", rule.NbrPre2 ?? string.Empty),
  164. new SugarParameter("@NbrPre3", rule.NbrPre3 ?? string.Empty),
  165. new SugarParameter("@Today", sequenceDate ?? now.Date),
  166. new SugarParameter("@NextValue", nextValue),
  167. new SugarParameter("@IniValue", rule.IniValue),
  168. new SugarParameter("@MinValue", rule.MinValue),
  169. new SugarParameter("@MaxValue", rule.MaxValue),
  170. new SugarParameter("@ResetValue", rule.ResetValue),
  171. new SugarParameter("@AllowReset", rule.AllowReset),
  172. new SugarParameter("@AllowSkip", rule.AllowSkip),
  173. new SugarParameter("@AllowManual", rule.AllowManual),
  174. new SugarParameter("@DateType", rule.DateType ?? string.Empty),
  175. new SugarParameter("@IsDateType", rule.IsDateType),
  176. new SugarParameter("@IsActive", rule.IsActive ?? true),
  177. new SugarParameter("@IsConfirm", rule.IsConfirm ?? true),
  178. new SugarParameter("@Domain", rule.Domain ?? string.Empty),
  179. new SugarParameter("@UserNo", operatorNo ?? string.Empty),
  180. new SugarParameter("@Now", now),
  181. new SugarParameter("@TenantId", rule.TenantId));
  182. }
  183. private async Task UpdateDayInfoAsync(int dayInfoId, int nextValue, string operatorNo, DateTime now)
  184. {
  185. await _db.Ado.ExecuteCommandAsync(
  186. """
  187. UPDATE NbrDayInfo
  188. SET NextValue=@NextValue,
  189. UpdateUser=@UpdateUser,
  190. UpdateTime=@UpdateTime
  191. WHERE RecID=@Id
  192. """,
  193. new SugarParameter("@NextValue", nextValue),
  194. new SugarParameter("@UpdateUser", operatorNo ?? string.Empty),
  195. new SugarParameter("@UpdateTime", now),
  196. new SugarParameter("@Id", dayInfoId));
  197. }
  198. private static int ResolveFirstSerial(NbrControlRuleRow rule, NbrDayInfoRow? dayInfo)
  199. {
  200. if (dayInfo?.NextValue != null) return dayInfo.NextValue.Value + 1;
  201. if (rule.IniValue.HasValue) return rule.IniValue.Value;
  202. if (rule.MinValue.HasValue) return rule.MinValue.Value;
  203. return 1;
  204. }
  205. private sealed class NbrControlRuleRow
  206. {
  207. public int Id { get; set; }
  208. public string? NbrType { get; set; }
  209. public string? NbrPre1 { get; set; }
  210. public string? NbrPre2 { get; set; }
  211. public string? NbrPre3 { get; set; }
  212. public string? DateType { get; set; }
  213. public bool? IsDateType { get; set; }
  214. public string? Description { get; set; }
  215. public int? IniValue { get; set; }
  216. public int? MinValue { get; set; }
  217. public int? MaxValue { get; set; }
  218. public int? ResetValue { get; set; }
  219. public bool? AllowReset { get; set; }
  220. public bool? AllowSkip { get; set; }
  221. public bool? AllowManual { get; set; }
  222. public bool? IsActive { get; set; }
  223. public bool? IsConfirm { get; set; }
  224. public string? Domain { get; set; }
  225. public long? TenantId { get; set; }
  226. }
  227. private sealed class NbrDayInfoRow
  228. {
  229. public int Id { get; set; }
  230. public int? NextValue { get; set; }
  231. }
  232. }