using System.Globalization; using System.Reflection; using Oms.Application.Dtos.Orders; using Oms.Domain.OrdersAggregate.Consts; using Oms.Domain.OrdersAggregate.Entities; namespace Oms.Application.Services.Orders.Issues; public static class OrderFormulaFieldResolver { public static decimal GetNumericValue(FormulaFieldRef fieldRef, OrderRuleEvaluationContext context) { var value = GetRawValue(fieldRef, context); return ToDecimal(value); } public static bool StringContains(FormulaFieldRef fieldRef, OrderRuleEvaluationContext context, string needle) { if (string.IsNullOrEmpty(needle)) { return true; } if (fieldRef.Table == "t_order_declaration") { return context.Declarations.Any(d => ContainsValue(GetDeclarationValue(d, fieldRef.Field), needle)) || context.CreateInput?.Items.Any(i => ContainsValue(GetDeclarationInputValue(i, fieldRef.Field), needle)) == true; } if (fieldRef.Table == "t_order_fee") { return context.Fees.Any(f => ContainsValue(GetPropertyValue(f, fieldRef.Field), needle)); } var raw = GetRawValue(fieldRef, context); return ContainsValue(raw, needle); } private static object? GetRawValue(FormulaFieldRef fieldRef, OrderRuleEvaluationContext context) { if (fieldRef.Virtual) { return ResolveVirtual(fieldRef.Field, context); } return fieldRef.Table switch { "t_order" => ResolveOrderField(fieldRef.Field, context), "t_order_recipient" => ResolveRecipientField(fieldRef.Field, context), "t_order_sender" => ResolveSenderField(fieldRef.Field, context), "t_order_declaration" => ResolveDeclarationField(fieldRef.Field, context), "t_order_weight" => ResolveWeightField(fieldRef.Field, context), "t_order_fee" => ResolveFeeField(fieldRef.Field, context), "t_customer" => GetPropertyValue(context.Customer, fieldRef.Field), _ => null }; } private static object? ResolveVirtual(string field, OrderRuleEvaluationContext context) { var (length, width, height) = GetPieceDimensions(context); return field switch { "PieceMaxLength" => length, "PieceMaxWidth" => width, "PieceMaxHeight" => height, "PieceSumLwh" => length + width + height, _ => null }; } private static (decimal Length, decimal Width, decimal Height) GetPieceDimensions(OrderRuleEvaluationContext context) { var forecast = GetForecastWeight(context); if (forecast != null) { return (forecast.Length ?? 0, forecast.Width ?? 0, forecast.Height ?? 0); } var input = context.CreateInput; if (input != null) { return (input.Length ?? 0, input.Width ?? 0, input.Height ?? 0); } return (0, 0, 0); } private static OrderWeight? GetForecastWeight(OrderRuleEvaluationContext context) => context.Weights.FirstOrDefault(x => x.Type == OrderWeightTypeCodes.Forecast) ?? context.Weights.FirstOrDefault(); private static object? ResolveOrderField(string field, OrderRuleEvaluationContext context) { if (context.Order != null) { return GetPropertyValue(context.Order, field); } var input = context.CreateInput; if (input == null) { return null; } return field switch { "RequestId" => input.RequestId, "ClientNo" => input.ClientNo, "TrackNo" => input.TrackNo, "ServiceNo" => input.ServiceNo, "ProductCode" => input.ProductCode, "ChannelCode" => input.ChannelCode, "ConsigneeCountry" => context.ConsigneeCountryCode, "ConsigneeCountryName" => context.ConsigneeCountryName, "CargoAttribute" => input.CargoAttribute, "ExportCustomsMode" => input.ExportCustomsMode, "WarehouseId" => input.WarehouseId, "CustomerId" => input.CustomerId, "IsCod" => input.IsCod ? (byte)1 : (byte)0, "CodCurrency" => input.CodCurrency, "CodSum" => input.CodSum, "DeclaredCurrency" => input.DeclaredCurrency, "DeclaredSum" => input.DeclaredSum, "Quantity" => input.Quantity, "IsRemote" => input.IsRemote, "AdditionalServices" => SerializeAdditionalServices(input.AdditionalServices), _ => null }; } private static string? SerializeAdditionalServices(List? codes) { if (codes == null || codes.Count == 0) { return null; } var normalized = codes .Where(x => !string.IsNullOrWhiteSpace(x)) .Select(x => x.Trim()) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); return normalized.Count == 0 ? null : global::System.Text.Json.JsonSerializer.Serialize(normalized); } private static object? ResolveRecipientField(string field, OrderRuleEvaluationContext context) { if (context.Recipient != null) { return GetPropertyValue(context.Recipient, field); } var receiver = context.CreateInput?.Receiver; if (receiver == null) { return null; } return field switch { "RecipientsName" => receiver.RecipientsName, "CompanyName" => receiver.CompanyName, "Phone" => receiver.Phone, "TelePhone" => receiver.TelePhone, "Email" => receiver.Email, "Province" => receiver.Province, "City" => receiver.City, "StreetNumber" => receiver.StreetNumber, "StreetAddress1" => receiver.StreetAddress1, "StreetAddress2" => receiver.StreetAddress2, "StreetAddress3" => receiver.StreetAddress3, "ZipCode" => receiver.ZipCode, "TaxType" => receiver.TaxType, "TaxNo" => receiver.TaxNo, "CertificateType" => receiver.CertificateType, "CertificateNo" => receiver.CertificateNo, "Eori" => receiver.Eori, "Vat" => receiver.Vat, _ => null }; } private static object? ResolveSenderField(string field, OrderRuleEvaluationContext context) { if (context.Sender != null) { return GetPropertyValue(context.Sender, field); } var sender = context.CreateInput?.Sender; if (sender == null) { return null; } return field switch { "SenderName" => sender.SenderName, "Company" => sender.Company, "Phone" => sender.Phone, "TelePhone" => sender.TelePhone, "Email" => sender.Email, "Province" => sender.Province, "City" => sender.City, "ZipCode" => sender.ZipCode, "StreetAddress1" => sender.StreetAddress1, "StreetAddress2" => sender.StreetAddress2, "StreetAddress3" => sender.StreetAddress3, "HouseNumber" => sender.HouseNumber, _ => null }; } private static object? ResolveDeclarationField(string field, OrderRuleEvaluationContext context) { var declaration = context.Declarations.FirstOrDefault(); if (declaration != null) { return GetDeclarationValue(declaration, field); } var item = context.CreateInput?.Items.FirstOrDefault(); return item == null ? null : GetDeclarationInputValue(item, field); } private static object? ResolveWeightField(string field, OrderRuleEvaluationContext context) { var weight = GetForecastWeight(context); if (weight != null) { return GetPropertyValue(weight, field); } var input = context.CreateInput; if (input == null) { return null; } return field switch { "Length" => input.Length, "Width" => input.Width, "Height" => input.Height, "ActualWeight" => input.ActualWeight, "VolumetricWeight" => input.VolumetricWeight, "ChargeableWeight" => input.ChargeableWeight, "Cubic" => input.Cubic, _ => null }; } private static object? ResolveFeeField(string field, OrderRuleEvaluationContext context) { var fee = context.Fees.FirstOrDefault(); return fee == null ? null : GetPropertyValue(fee, field); } private static object? GetDeclarationValue(OrderDeclaration declaration, string field) => field switch { "HsCode" => declaration.HsCode ?? declaration.ImHsCode ?? declaration.ExHsCode, _ => GetPropertyValue(declaration, field) }; private static object? GetDeclarationInputValue(OrderParcelItemInput item, string field) => field switch { "HsCode" => item.ImHsCode ?? item.ExHsCode, "IsMagnetic" => item.IsMagnetic ? (byte)1 : (byte)0, _ => GetPropertyValue(item, field) }; private static object? GetPropertyValue(object? target, string fieldName) { if (target == null) { return null; } var prop = target.GetType().GetProperty(fieldName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); return prop?.GetValue(target); } private static decimal ToDecimal(object? value) { if (value == null) { return 0; } return value switch { bool b => b ? 1m : 0m, byte bt => bt, sbyte sb => sb, short s => s, ushort us => us, int i => i, uint ui => ui, long l => l, ulong ul => ul, float f => (decimal)f, double d => (decimal)d, decimal m => m, DateTime => 0, Enum e => Convert.ToDecimal(e, CultureInfo.InvariantCulture), _ => decimal.TryParse(Convert.ToString(value, CultureInfo.InvariantCulture), NumberStyles.Any, CultureInfo.InvariantCulture, out var parsed) ? parsed : 0 }; } private static bool ContainsValue(object? source, string needle) => !string.IsNullOrWhiteSpace(Convert.ToString(source, CultureInfo.InvariantCulture)) && Convert.ToString(source, CultureInfo.InvariantCulture)! .Contains(needle, StringComparison.OrdinalIgnoreCase); }