Support optional services and executable billing conditions on sell/buy prices, percent-of-value and percent-of-freight formulas, place-order/inbound payable on chargeable weight, outbound receivable freeze, channel remote areas, billing simulate, and insurance-aware claims. Co-authored-by: Cursor <cursoragent@cursor.com>
322 lines
11 KiB
C#
322 lines
11 KiB
C#
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<string>? 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);
|
|
}
|