RuleConfigQueryService.cs 8.5 KB

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