using System.Text.Json; using System.Text.Json.Serialization; namespace MiGu.Server.Wms; public sealed class TransportSelector { public int SchemaVersion { get; set; } = 1; public StorageSelectorFilter? Storage { get; set; } public ContainerSelectorFilter? Container { get; set; } public MaterialSelectorFilter? Material { get; set; } public List Ranking { get; set; } = []; } public sealed class StorageSelectorFilter { public List AreaIds { get; set; } = []; public List StorageTypes { get; set; } = []; public List ZoneCodes { get; set; } = []; public List StatusIn { get; set; } = []; public List SiteIds { get; set; } = []; public bool? AllowInbound { get; set; } public bool? AllowOutbound { get; set; } public bool RequireSiteId { get; set; } public bool RequireEmpty { get; set; } public bool RequireOccupied { get; set; } public List ExcludeStorageIds { get; set; } = []; } public sealed class ContainerSelectorFilter { public List ContainerTypes { get; set; } = []; public List StatusIn { get; set; } = []; public bool ExcludeReserved { get; set; } = true; } public sealed class MaterialSelectorFilter { public List MaterialIds { get; set; } = []; public List Categories { get; set; } = []; public List StatusIn { get; set; } = []; public decimal? MinQuantity { get; set; } public string BatchNoMode { get; set; } = "Any"; } public sealed class RankingRule { public string Field { get; set; } = ""; public string Direction { get; set; } = "asc"; } public sealed class TransportTaskOptions { public bool AutoReserve { get; set; } = true; public bool RequireManualConfirm { get; set; } public bool AllowSameStorage { get; set; } public int ReservationTtlSeconds { get; set; } = 600; public string DispatchMode { get; set; } = "Manual"; public int MaxCandidateCount { get; set; } = 20; } public static class TransportSelectorParser { private static readonly JsonSerializerOptions Json = new(JsonSerializerDefaults.Web) { PropertyNameCaseInsensitive = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; public static TransportSelector ParseSelector(string? json) { if (string.IsNullOrWhiteSpace(json) || json.Trim() == "{}") return new TransportSelector(); try { return JsonSerializer.Deserialize(json, Json) ?? new TransportSelector(); } catch (JsonException ex) { throw new InvalidOperationException($"Selector JSON 无效: {ex.Message}"); } } public static TransportTaskOptions ParseTaskOptions(string? json) { if (string.IsNullOrWhiteSpace(json) || json.Trim() == "{}") return new TransportTaskOptions(); try { return JsonSerializer.Deserialize(json, Json) ?? new TransportTaskOptions(); } catch (JsonException ex) { throw new InvalidOperationException($"TaskOptions JSON 无效: {ex.Message}"); } } public static string NormalizeSelectorJson(string? json) => JsonSerializer.Serialize(ParseSelector(json), Json); public static string NormalizeTaskOptionsJson(string? json) => JsonSerializer.Serialize(ParseTaskOptions(json), Json); } public sealed record WmsTransportRequest( string TriggerType, Guid? RuleId, string? RequestSiteId, Guid? MaterialId, decimal? Quantity, Guid? ContainerId, Guid? SourceStorageId, Guid? TargetStorageId, string? BatchNo, int Priority, string Reason, Dictionary? Tags); public sealed record TransportCandidatePair( Guid RuleId, string RuleCode, Guid SourceStorageId, string SourceStorageCode, Guid TargetStorageId, string TargetStorageCode, Guid ContainerId, string ContainerCode, Guid? MaterialId, string? MaterialCode, decimal? Quantity, int Score, List RejectReasons); public sealed record TransportCandidatePreview( WmsTransportRequest Request, List Candidates, List RuleResults); public sealed record RulePreviewResult( Guid RuleId, string RuleCode, string RuleName, int CandidateCount, List RejectReasons); public sealed record DispatchTransportCommand( Guid TaskId, string SourceSiteId, string TargetSiteId, string ContainerCode, int Priority, string BusinessType, string Reason);