diff --git a/src/Oms.Application/Dtos/Orders/OrderFeeAddInput.cs b/src/Oms.Application/Dtos/Orders/OrderFeeAddInput.cs
new file mode 100644
index 0000000..dd7e173
--- /dev/null
+++ b/src/Oms.Application/Dtos/Orders/OrderFeeAddInput.cs
@@ -0,0 +1,66 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Oms.Application.Dtos.Orders;
+
+///
+/// 手动添加订单费用入参。
+///
+public class OrderFeeAddInput
+{
+ ///
+ /// 订单主键。
+ ///
+ [Required]
+ public int Id { get; set; }
+
+ ///
+ /// 费用类型:receivable / payable。
+ ///
+ [Required]
+ [StringLength(16)]
+ public string Type { get; set; } = string.Empty;
+
+ ///
+ /// 费用项编码。
+ ///
+ [Required]
+ [StringLength(64)]
+ public string CostItem { get; set; } = string.Empty;
+
+ ///
+ /// 金额。
+ ///
+ [Required]
+ public decimal Amount { get; set; }
+
+ ///
+ /// 币种。
+ ///
+ [Required]
+ [StringLength(16)]
+ public string Currency { get; set; } = string.Empty;
+
+ ///
+ /// 计费阶段:forecast / inbound / outbound。
+ ///
+ [Required]
+ [StringLength(16)]
+ public string ChargingStage { get; set; } = string.Empty;
+
+ ///
+ /// 下游服务商(应付可选)。
+ ///
+ [StringLength(128)]
+ public string? DownstreamProvider { get; set; }
+
+ ///
+ /// 价格编号(可选)。
+ ///
+ [StringLength(64)]
+ public string? PriceNo { get; set; }
+
+ ///
+ /// 价格生效时间(可选)。
+ ///
+ public DateTime? PriceEffectiveTime { get; set; }
+}
diff --git a/src/Oms.Application/Services/Orders/OrderService.OrderEdit.cs b/src/Oms.Application/Services/Orders/OrderService.OrderEdit.cs
index 1474bbb..deeacca 100644
--- a/src/Oms.Application/Services/Orders/OrderService.OrderEdit.cs
+++ b/src/Oms.Application/Services/Orders/OrderService.OrderEdit.cs
@@ -233,6 +233,69 @@ public partial class OrderService
return (await MapOrderOutputsAsync([order])).First();
}
+ ///
+ /// 手动添加订单费用(应收或应付)。
+ ///
+ [HttpPost("/order/fee/add")]
+ public async Task AddOrderFeeAsync(OrderFeeAddInput input)
+ {
+ var order = await GetOrderOrThrowAsync(input.Id);
+
+ var type = input.Type?.Trim().ToLowerInvariant() ?? string.Empty;
+ if (type is not (OrderFeeTypeCodes.Receivable or OrderFeeTypeCodes.Payable))
+ {
+ throw new BusinessException("费用类型无效");
+ }
+
+ var costItem = input.CostItem?.Trim() ?? string.Empty;
+ if (string.IsNullOrWhiteSpace(costItem))
+ {
+ throw new BusinessException("费用项不能为空");
+ }
+
+ if (input.Amount <= 0)
+ {
+ throw new BusinessException("金额必须大于0");
+ }
+
+ var currency = input.Currency?.Trim() ?? string.Empty;
+ if (string.IsNullOrWhiteSpace(currency))
+ {
+ throw new BusinessException("币种不能为空");
+ }
+
+ var chargingStage = input.ChargingStage?.Trim() ?? string.Empty;
+ if (chargingStage is not (
+ OrderFeeChargingStageCodes.Forecast
+ or OrderFeeChargingStageCodes.Inbound
+ or OrderFeeChargingStageCodes.Outbound))
+ {
+ throw new BusinessException("计费阶段无效");
+ }
+
+ var fee = new OrderFee
+ {
+ Type = type,
+ CostItem = costItem,
+ Amount = input.Amount,
+ Currency = currency,
+ ChargingStage = chargingStage,
+ DownstreamProvider = string.IsNullOrWhiteSpace(input.DownstreamProvider)
+ ? null
+ : input.DownstreamProvider.Trim(),
+ PriceNo = string.IsNullOrWhiteSpace(input.PriceNo) ? null : input.PriceNo.Trim(),
+ PriceEffectiveTime = input.PriceEffectiveTime,
+ CreateTime = DateTime.Now,
+ UpdateTime = DateTime.Now,
+ };
+
+ await OrderRepository.UpsertFeesAsync(order.Id, [fee]);
+ order.UpdateTime = DateTime.Now;
+ await OrderRepository.UpdateAsync(order);
+
+ return (await MapOrderOutputsAsync([order])).First();
+ }
+
private async Task RecalculateReceivableCoreAsync(Order order)
{
if (string.IsNullOrWhiteSpace(order.ProductCode))