namespace Admin.NET.Plugin.AiDOP.Rule; /// /// 规则配置只读查询接口。研发阶段首版只开放 S3 / DELIVERY_GENERATE。 /// [ApiDescriptionSettings(Order = 330, Description = "规则配置查询")] [Route("api/RuleConfig")] [AllowAnonymous] [NonUnify] public class RuleConfigQueryService : IDynamicApiController, ITransient { private readonly RuleConfigService _ruleConfigService; private readonly UserManager _userManager; private readonly ISqlSugarClient _db; public RuleConfigQueryService(RuleConfigService ruleConfigService, UserManager userManager, ISqlSugarClient db) { _ruleConfigService = ruleConfigService; _userManager = userManager; _db = db; } [DisplayName("查询当前生效规则")] [HttpGet("effective-options")] public async Task GetEffectiveOptions([FromQuery] RuleConfigQueryInput input) { var moduleCode = string.IsNullOrWhiteSpace(input.ModuleCode) ? "S3" : input.ModuleCode.Trim(); var scenarioCode = string.IsNullOrWhiteSpace(input.ScenarioCode) ? "DELIVERY_GENERATE" : input.ScenarioCode.Trim(); if (!string.Equals(moduleCode, "S3", StringComparison.OrdinalIgnoreCase) || !string.Equals(scenarioCode, "DELIVERY_GENERATE", StringComparison.OrdinalIgnoreCase)) { throw Oops.Oh("当前只支持查询 S3 / DELIVERY_GENERATE 规则配置"); } var tenantId = input.TenantId > 0 ? input.TenantId : _userManager.TenantId; var factoryId = input.FactoryId > 0 ? input.FactoryId : null; return await _ruleConfigService.ResolveS3DeliveryGenerateSnapshotAsync(tenantId, factoryId); } [DisplayName("保存S3交货单生成规则")] [HttpPost("s3-delivery-generate/save")] public async Task SaveS3DeliveryGenerate([FromBody] S3DeliveryGenerateRuleSaveInput input) { var tenantId = input.TenantId > 0 ? input.TenantId : 0; var factoryId = input.FactoryId > 0 ? input.FactoryId.Value : 0; var profile = (await _db.Ado.SqlQueryAsync( """ SELECT id AS Id FROM ado_rule_profile WHERE module_code='S3' AND scenario_code='DELIVERY_GENERATE' AND tenant_id=@TenantId AND factory_id=@FactoryId AND is_enabled=1 ORDER BY version DESC, id DESC LIMIT 1 """, new SugarParameter("@TenantId", tenantId), new SugarParameter("@FactoryId", factoryId))).FirstOrDefault(); if (profile == null || profile.Id <= 0) { throw Oops.Oh($"未找到 S3 / DELIVERY_GENERATE 规则方案:tenantId={tenantId},factoryId={factoryId}"); } var values = BuildS3RuleValues(input.Options); if (values.Count == 0) throw Oops.Oh("没有可保存的规则项"); var now = DateTime.Now; var account = _userManager.Account ?? "system"; try { _db.Ado.BeginTran(); foreach (var item in values) { var affected = await _db.Ado.ExecuteCommandAsync( """ UPDATE ado_rule_item SET rule_value=@RuleValue, updated_at=@UpdatedAt, updated_by=@UpdatedBy WHERE profile_id=@ProfileId AND rule_code=@RuleCode AND is_enabled=1 """, new SugarParameter("@RuleValue", item.Value), new SugarParameter("@UpdatedAt", now), new SugarParameter("@UpdatedBy", account), new SugarParameter("@ProfileId", profile.Id), new SugarParameter("@RuleCode", item.Key)); if (affected <= 0) throw Oops.Oh($"规则项不存在或未启用:{item.Key}"); } _db.Ado.CommitTran(); } catch { _db.Ado.RollbackTran(); throw; } return await _ruleConfigService.ResolveS3DeliveryGenerateSnapshotAsync(tenantId, factoryId == 0 ? null : factoryId); } private static Dictionary BuildS3RuleValues(S3DeliveryGenerateRuleOptionsInput options) { if (options == null) throw Oops.Oh("规则参数不能为空"); var values = new Dictionary(); if (options.QuotaRequiredTotal.HasValue) { if (options.QuotaRequiredTotal.Value < 0 || options.QuotaRequiredTotal.Value > 100) throw Oops.Oh("配额合计要求必须在 0-100 之间"); values["quotaRequiredTotal"] = options.QuotaRequiredTotal.Value.ToString("0.####", System.Globalization.CultureInfo.InvariantCulture); } if (options.QuotaTolerance.HasValue) { if (options.QuotaTolerance.Value < 0) throw Oops.Oh("配额误差容忍不能小于 0"); values["quotaTolerance"] = options.QuotaTolerance.Value.ToString("0.########", System.Globalization.CultureInfo.InvariantCulture); } if (options.PoBuyerCodes != null) values["poBuyerCodes"] = SerializeStringArray(NormalizeStringArray(options.PoBuyerCodes, "普通 PO 采购组")); if (options.DoUsages != null) values["doUsages"] = SerializeStringArray(NormalizeStringArray(options.DoUsages, "DO 可用供应类别")); if (options.DefaultLeadDays.HasValue) { if (options.DefaultLeadDays.Value < 0 || options.DefaultLeadDays.Value > 365) throw Oops.Oh("默认提前期必须在 0-365 天之间"); values["defaultLeadDays"] = options.DefaultLeadDays.Value.ToString(); } if (options.EnablePackagingRoundUp.HasValue) values["enablePackagingRoundUp"] = ToRuleBool(options.EnablePackagingRoundUp.Value); if (options.ShortageCreatePr.HasValue) values["shortageCreatePr"] = ToRuleBool(options.ShortageCreatePr.Value); if (options.EnablePrMerge.HasValue) values["enablePrMerge"] = ToRuleBool(options.EnablePrMerge.Value); if (options.EnableRequireGoodsToPo.HasValue) values["enableRequireGoodsToPo"] = ToRuleBool(options.EnableRequireGoodsToPo.Value); if (options.EnableExternalPushTracking.HasValue) values["enableExternalPushTracking"] = ToRuleBool(options.EnableExternalPushTracking.Value); return values; } private static List NormalizeStringArray(IEnumerable value, string label) { var list = value.Select(x => (x ?? string.Empty).Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).Distinct().ToList(); if (list.Count == 0) throw Oops.Oh($"{label}不能为空"); return list; } private static string SerializeStringArray(List value) { return System.Text.Json.JsonSerializer.Serialize(value); } private static string ToRuleBool(bool value) { return value ? "true" : "false"; } private sealed class RuleProfileIdRow { public long Id { get; set; } } } public sealed class RuleConfigQueryInput { public string? ModuleCode { get; set; } public string? ScenarioCode { get; set; } public long TenantId { get; set; } public long? FactoryId { get; set; } } public sealed class S3DeliveryGenerateRuleSaveInput { public long TenantId { get; set; } public long? FactoryId { get; set; } public S3DeliveryGenerateRuleOptionsInput Options { get; set; } = new(); } public sealed class S3DeliveryGenerateRuleOptionsInput { public decimal? QuotaRequiredTotal { get; set; } public decimal? QuotaTolerance { get; set; } public List? PoBuyerCodes { get; set; } public List? DoUsages { get; set; } public int? DefaultLeadDays { get; set; } public bool? EnablePackagingRoundUp { get; set; } public bool? ShortageCreatePr { get; set; } public bool? EnablePrMerge { get; set; } public bool? EnableRequireGoodsToPo { get; set; } public bool? EnableExternalPushTracking { get; set; } }