RuleConfigQueryService.cs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. namespace Admin.NET.Plugin.AiDOP.Rule;
  2. /// <summary>
  3. /// 规则配置只读查询接口。研发阶段首版只开放 S3 / DELIVERY_GENERATE。
  4. /// </summary>
  5. [ApiDescriptionSettings(Order = 330, Description = "规则配置查询")]
  6. [Route("api/RuleConfig")]
  7. [AllowAnonymous]
  8. [NonUnify]
  9. public class RuleConfigQueryService : IDynamicApiController, ITransient
  10. {
  11. private readonly RuleConfigService _ruleConfigService;
  12. private readonly UserManager _userManager;
  13. private readonly ISqlSugarClient _db;
  14. public RuleConfigQueryService(RuleConfigService ruleConfigService, UserManager userManager, ISqlSugarClient db)
  15. {
  16. _ruleConfigService = ruleConfigService;
  17. _userManager = userManager;
  18. _db = db;
  19. }
  20. [DisplayName("查询当前生效规则")]
  21. [HttpGet("effective-options")]
  22. public async Task<object> GetEffectiveOptions([FromQuery] RuleConfigQueryInput input)
  23. {
  24. var moduleCode = string.IsNullOrWhiteSpace(input.ModuleCode) ? "S3" : input.ModuleCode.Trim();
  25. var scenarioCode = string.IsNullOrWhiteSpace(input.ScenarioCode) ? "DELIVERY_GENERATE" : input.ScenarioCode.Trim();
  26. if (!string.Equals(moduleCode, "S3", StringComparison.OrdinalIgnoreCase)
  27. || !string.Equals(scenarioCode, "DELIVERY_GENERATE", StringComparison.OrdinalIgnoreCase))
  28. {
  29. throw Oops.Oh("当前只支持查询 S3 / DELIVERY_GENERATE 规则配置");
  30. }
  31. var tenantId = input.TenantId > 0 ? input.TenantId : _userManager.TenantId;
  32. var factoryId = input.FactoryId > 0 ? input.FactoryId : null;
  33. return await _ruleConfigService.ResolveS3DeliveryGenerateSnapshotAsync(tenantId, factoryId);
  34. }
  35. [DisplayName("保存S3交货单生成规则")]
  36. [HttpPost("s3-delivery-generate/save")]
  37. public async Task<object> SaveS3DeliveryGenerate([FromBody] S3DeliveryGenerateRuleSaveInput input)
  38. {
  39. var tenantId = input.TenantId > 0 ? input.TenantId : 0;
  40. var factoryId = input.FactoryId > 0 ? input.FactoryId.Value : 0;
  41. var profile = (await _db.Ado.SqlQueryAsync<RuleProfileIdRow>(
  42. """
  43. SELECT id AS Id
  44. FROM ado_rule_profile
  45. WHERE module_code='S3'
  46. AND scenario_code='DELIVERY_GENERATE'
  47. AND tenant_id=@TenantId
  48. AND factory_id=@FactoryId
  49. AND is_enabled=1
  50. ORDER BY version DESC, id DESC
  51. LIMIT 1
  52. """,
  53. new SugarParameter("@TenantId", tenantId),
  54. new SugarParameter("@FactoryId", factoryId))).FirstOrDefault();
  55. if (profile == null || profile.Id <= 0)
  56. {
  57. throw Oops.Oh($"未找到 S3 / DELIVERY_GENERATE 规则方案:tenantId={tenantId},factoryId={factoryId}");
  58. }
  59. var values = BuildS3RuleValues(input.Options);
  60. if (values.Count == 0) throw Oops.Oh("没有可保存的规则项");
  61. var now = DateTime.Now;
  62. var account = _userManager.Account ?? "system";
  63. try
  64. {
  65. _db.Ado.BeginTran();
  66. foreach (var item in values)
  67. {
  68. var affected = await _db.Ado.ExecuteCommandAsync(
  69. """
  70. UPDATE ado_rule_item
  71. SET rule_value=@RuleValue,
  72. updated_at=@UpdatedAt,
  73. updated_by=@UpdatedBy
  74. WHERE profile_id=@ProfileId
  75. AND rule_code=@RuleCode
  76. AND is_enabled=1
  77. """,
  78. new SugarParameter("@RuleValue", item.Value),
  79. new SugarParameter("@UpdatedAt", now),
  80. new SugarParameter("@UpdatedBy", account),
  81. new SugarParameter("@ProfileId", profile.Id),
  82. new SugarParameter("@RuleCode", item.Key));
  83. if (affected <= 0) throw Oops.Oh($"规则项不存在或未启用:{item.Key}");
  84. }
  85. _db.Ado.CommitTran();
  86. }
  87. catch
  88. {
  89. _db.Ado.RollbackTran();
  90. throw;
  91. }
  92. return await _ruleConfigService.ResolveS3DeliveryGenerateSnapshotAsync(tenantId, factoryId == 0 ? null : factoryId);
  93. }
  94. private static Dictionary<string, string> BuildS3RuleValues(S3DeliveryGenerateRuleOptionsInput options)
  95. {
  96. if (options == null) throw Oops.Oh("规则参数不能为空");
  97. var values = new Dictionary<string, string>();
  98. if (options.QuotaRequiredTotal.HasValue)
  99. {
  100. if (options.QuotaRequiredTotal.Value < 0 || options.QuotaRequiredTotal.Value > 100) throw Oops.Oh("配额合计要求必须在 0-100 之间");
  101. values["quotaRequiredTotal"] = options.QuotaRequiredTotal.Value.ToString("0.####", System.Globalization.CultureInfo.InvariantCulture);
  102. }
  103. if (options.QuotaTolerance.HasValue)
  104. {
  105. if (options.QuotaTolerance.Value < 0) throw Oops.Oh("配额误差容忍不能小于 0");
  106. values["quotaTolerance"] = options.QuotaTolerance.Value.ToString("0.########", System.Globalization.CultureInfo.InvariantCulture);
  107. }
  108. if (options.PoBuyerCodes != null) values["poBuyerCodes"] = SerializeStringArray(NormalizeStringArray(options.PoBuyerCodes, "普通 PO 采购组"));
  109. if (options.DoUsages != null) values["doUsages"] = SerializeStringArray(NormalizeStringArray(options.DoUsages, "DO 可用供应类别"));
  110. if (options.DefaultLeadDays.HasValue)
  111. {
  112. if (options.DefaultLeadDays.Value < 0 || options.DefaultLeadDays.Value > 365) throw Oops.Oh("默认提前期必须在 0-365 天之间");
  113. values["defaultLeadDays"] = options.DefaultLeadDays.Value.ToString();
  114. }
  115. if (options.EnablePackagingRoundUp.HasValue) values["enablePackagingRoundUp"] = ToRuleBool(options.EnablePackagingRoundUp.Value);
  116. if (options.ShortageCreatePr.HasValue) values["shortageCreatePr"] = ToRuleBool(options.ShortageCreatePr.Value);
  117. if (options.EnablePrMerge.HasValue) values["enablePrMerge"] = ToRuleBool(options.EnablePrMerge.Value);
  118. if (options.EnableRequireGoodsToPo.HasValue) values["enableRequireGoodsToPo"] = ToRuleBool(options.EnableRequireGoodsToPo.Value);
  119. if (options.EnableExternalPushTracking.HasValue) values["enableExternalPushTracking"] = ToRuleBool(options.EnableExternalPushTracking.Value);
  120. return values;
  121. }
  122. private static List<string> NormalizeStringArray(IEnumerable<string> value, string label)
  123. {
  124. var list = value.Select(x => (x ?? string.Empty).Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).Distinct().ToList();
  125. if (list.Count == 0) throw Oops.Oh($"{label}不能为空");
  126. return list;
  127. }
  128. private static string SerializeStringArray(List<string> value)
  129. {
  130. return System.Text.Json.JsonSerializer.Serialize(value);
  131. }
  132. private static string ToRuleBool(bool value)
  133. {
  134. return value ? "true" : "false";
  135. }
  136. private sealed class RuleProfileIdRow
  137. {
  138. public long Id { get; set; }
  139. }
  140. }
  141. public sealed class RuleConfigQueryInput
  142. {
  143. public string? ModuleCode { get; set; }
  144. public string? ScenarioCode { get; set; }
  145. public long TenantId { get; set; }
  146. public long? FactoryId { get; set; }
  147. }
  148. public sealed class S3DeliveryGenerateRuleSaveInput
  149. {
  150. public long TenantId { get; set; }
  151. public long? FactoryId { get; set; }
  152. public S3DeliveryGenerateRuleOptionsInput Options { get; set; } = new();
  153. }
  154. public sealed class S3DeliveryGenerateRuleOptionsInput
  155. {
  156. public decimal? QuotaRequiredTotal { get; set; }
  157. public decimal? QuotaTolerance { get; set; }
  158. public List<string>? PoBuyerCodes { get; set; }
  159. public List<string>? DoUsages { get; set; }
  160. public int? DefaultLeadDays { get; set; }
  161. public bool? EnablePackagingRoundUp { get; set; }
  162. public bool? ShortageCreatePr { get; set; }
  163. public bool? EnablePrMerge { get; set; }
  164. public bool? EnableRequireGoodsToPo { get; set; }
  165. public bool? EnableExternalPushTracking { get; set; }
  166. }