NumberRuleService.cs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  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 tenantId = Infrastructure.AidopTenantScope.ResolveOrThrow(_userManager);
  37. const string tenantWhere = "\n AND tenant_id = @TenantId";
  38. var rulePars = new List<SugarParameter>
  39. {
  40. new("@RuleCode", normalizedRuleCode),
  41. new("@Domain", normalizedDomain),
  42. new("@TenantId", tenantId)
  43. };
  44. var rule = (await _db.Ado.SqlQueryAsync<NbrControlRuleRow>(
  45. $"""
  46. SELECT
  47. RecID AS Id,
  48. NbrType,
  49. NbrPre1,
  50. NbrPre2,
  51. NbrPre3,
  52. DateType,
  53. IsDateType,
  54. Description,
  55. IniValue,
  56. MinValue,
  57. `MaxValue` AS `MaxValue`,
  58. ResetValue,
  59. AllowReset,
  60. AllowSkip,
  61. AllowManual,
  62. IsActive,
  63. IsConfirm,
  64. Domain,
  65. tenant_id AS TenantId
  66. FROM NbrControl
  67. WHERE LOWER(TRIM(IFNULL(NbrType,''))) = LOWER(@RuleCode)
  68. AND (
  69. IFNULL(TRIM(domain_code),'') = @Domain
  70. OR IFNULL(TRIM(Domain),'') = @Domain
  71. OR IFNULL(TRIM(domain_code),'') = ''
  72. OR IFNULL(TRIM(Domain),'') = ''
  73. )
  74. AND IFNULL(IsActive, 1) = 1{tenantWhere}
  75. ORDER BY
  76. CASE
  77. WHEN IFNULL(TRIM(domain_code),'') = @Domain THEN 0
  78. WHEN IFNULL(TRIM(Domain),'') = @Domain THEN 1
  79. ELSE 2
  80. END,
  81. RecID
  82. LIMIT 1
  83. FOR UPDATE
  84. """,
  85. rulePars)).FirstOrDefault();
  86. if (rule == null)
  87. throw Oops.Oh($"未找到编号规则:{normalizedRuleCode}");
  88. var sequenceDate = NumberRuleFormatter.ResolveSequenceDate(rule.DateType, rule.IsDateType, now);
  89. var dayInfo = await GetDayInfoForUpdateAsync(rule, normalizedRuleCode, normalizedDomain, sequenceDate);
  90. var firstSerial = ResolveFirstSerial(rule, dayInfo);
  91. var lastSerial = firstSerial + count - 1;
  92. if (rule.MaxValue.HasValue && lastSerial > rule.MaxValue.Value)
  93. {
  94. if (rule.AllowReset == true)
  95. {
  96. firstSerial = (rule.ResetValue ?? 0) + 1;
  97. lastSerial = firstSerial + count - 1;
  98. }
  99. if (lastSerial > rule.MaxValue.Value)
  100. throw Oops.Oh($"编号规则 {normalizedRuleCode} 已超过最大值 {rule.MaxValue.Value}。");
  101. }
  102. var numbers = Enumerable.Range(firstSerial, count)
  103. .Select(serial => NumberRuleFormatter.BuildNumber(
  104. rule.NbrPre1,
  105. rule.NbrPre2,
  106. rule.NbrPre3,
  107. rule.DateType,
  108. rule.IsDateType,
  109. serial,
  110. rule.MaxValue,
  111. now))
  112. .ToList();
  113. if (dayInfo == null)
  114. await InsertDayInfoAsync(rule, sequenceDate, lastSerial, operatorNo, now);
  115. else
  116. await UpdateDayInfoAsync(dayInfo.Id, lastSerial, operatorNo, now);
  117. return numbers;
  118. }
  119. private async Task<NbrDayInfoRow?> GetDayInfoForUpdateAsync(NbrControlRuleRow rule, string ruleCode, string domain, DateTime? sequenceDate)
  120. {
  121. var dateFilter = sequenceDate.HasValue ? "AND DATE(Today) = DATE(@Today)" : string.Empty;
  122. var tenantId = Infrastructure.AidopTenantScope.ResolveOrThrow(_userManager);
  123. const string tenantFilter = "AND tenant_id = @TenantId2";
  124. var dayPars = new List<SugarParameter>
  125. {
  126. new("@RuleCode", rule.NbrType ?? ruleCode),
  127. new("@Domain", rule.Domain ?? domain),
  128. new("@Today", sequenceDate ?? DateTime.Today),
  129. new("@TenantId2", tenantId)
  130. };
  131. return (await _db.Ado.SqlQueryAsync<NbrDayInfoRow>(
  132. $"""
  133. SELECT
  134. RecID AS Id,
  135. NextValue
  136. FROM NbrDayInfo
  137. WHERE LOWER(TRIM(IFNULL(NbrType,''))) = LOWER(@RuleCode)
  138. AND IFNULL(TRIM(Domain),'') = @Domain
  139. {dateFilter}
  140. {tenantFilter}
  141. ORDER BY RecID
  142. LIMIT 1
  143. FOR UPDATE
  144. """,
  145. dayPars)).FirstOrDefault();
  146. }
  147. private async Task InsertDayInfoAsync(NbrControlRuleRow rule, DateTime? sequenceDate, int nextValue, string operatorNo, DateTime now)
  148. {
  149. await _db.Ado.ExecuteCommandAsync(
  150. """
  151. INSERT INTO NbrDayInfo
  152. (NbrType, Description, NbrPre1, NbrPre2, NbrPre3, Today,
  153. NextValue, IniValue, MinValue, `MaxValue`, ResetValue,
  154. AllowReset, AllowSkip, AllowManual, DateType, IsDateType,
  155. IsActive, IsConfirm, Domain, CreateUser, CreateTime, UpdateUser, UpdateTime, tenant_id)
  156. VALUES
  157. (@NbrType, @Description, @NbrPre1, @NbrPre2, @NbrPre3, @Today,
  158. @NextValue, @IniValue, @MinValue, @MaxValue, @ResetValue,
  159. @AllowReset, @AllowSkip, @AllowManual, @DateType, @IsDateType,
  160. @IsActive, @IsConfirm, @Domain, @UserNo, @Now, @UserNo, @Now, @TenantId)
  161. """,
  162. new SugarParameter("@NbrType", rule.NbrType ?? string.Empty),
  163. new SugarParameter("@Description", rule.Description ?? string.Empty),
  164. new SugarParameter("@NbrPre1", rule.NbrPre1 ?? string.Empty),
  165. new SugarParameter("@NbrPre2", rule.NbrPre2 ?? string.Empty),
  166. new SugarParameter("@NbrPre3", rule.NbrPre3 ?? string.Empty),
  167. new SugarParameter("@Today", sequenceDate ?? now.Date),
  168. new SugarParameter("@NextValue", nextValue),
  169. new SugarParameter("@IniValue", rule.IniValue),
  170. new SugarParameter("@MinValue", rule.MinValue),
  171. new SugarParameter("@MaxValue", rule.MaxValue),
  172. new SugarParameter("@ResetValue", rule.ResetValue),
  173. new SugarParameter("@AllowReset", rule.AllowReset),
  174. new SugarParameter("@AllowSkip", rule.AllowSkip),
  175. new SugarParameter("@AllowManual", rule.AllowManual),
  176. new SugarParameter("@DateType", rule.DateType ?? string.Empty),
  177. new SugarParameter("@IsDateType", rule.IsDateType),
  178. new SugarParameter("@IsActive", rule.IsActive ?? true),
  179. new SugarParameter("@IsConfirm", rule.IsConfirm ?? true),
  180. new SugarParameter("@Domain", rule.Domain ?? string.Empty),
  181. new SugarParameter("@UserNo", operatorNo ?? string.Empty),
  182. new SugarParameter("@Now", now),
  183. new SugarParameter("@TenantId", rule.TenantId));
  184. }
  185. private async Task UpdateDayInfoAsync(int dayInfoId, int nextValue, string operatorNo, DateTime now)
  186. {
  187. await _db.Ado.ExecuteCommandAsync(
  188. """
  189. UPDATE NbrDayInfo
  190. SET NextValue=@NextValue,
  191. UpdateUser=@UpdateUser,
  192. UpdateTime=@UpdateTime
  193. WHERE RecID=@Id
  194. """,
  195. new SugarParameter("@NextValue", nextValue),
  196. new SugarParameter("@UpdateUser", operatorNo ?? string.Empty),
  197. new SugarParameter("@UpdateTime", now),
  198. new SugarParameter("@Id", dayInfoId));
  199. }
  200. private static int ResolveFirstSerial(NbrControlRuleRow rule, NbrDayInfoRow? dayInfo)
  201. {
  202. if (dayInfo?.NextValue != null) return dayInfo.NextValue.Value + 1;
  203. if (rule.IniValue.HasValue) return rule.IniValue.Value;
  204. if (rule.MinValue.HasValue) return rule.MinValue.Value;
  205. return 1;
  206. }
  207. private sealed class NbrControlRuleRow
  208. {
  209. public int Id { get; set; }
  210. public string? NbrType { get; set; }
  211. public string? NbrPre1 { get; set; }
  212. public string? NbrPre2 { get; set; }
  213. public string? NbrPre3 { get; set; }
  214. public string? DateType { get; set; }
  215. public bool? IsDateType { get; set; }
  216. public string? Description { get; set; }
  217. public int? IniValue { get; set; }
  218. public int? MinValue { get; set; }
  219. public int? MaxValue { get; set; }
  220. public int? ResetValue { get; set; }
  221. public bool? AllowReset { get; set; }
  222. public bool? AllowSkip { get; set; }
  223. public bool? AllowManual { get; set; }
  224. public bool? IsActive { get; set; }
  225. public bool? IsConfirm { get; set; }
  226. public string? Domain { get; set; }
  227. public long? TenantId { get; set; }
  228. }
  229. private sealed class NbrDayInfoRow
  230. {
  231. public int Id { get; set; }
  232. public int? NextValue { get; set; }
  233. }
  234. }