feat(order): 支持订单详情手动添加费用项

新增 OrderFeeAddInput 并在 OrderEdit 服务中实现费用追加逻辑,配合前端订单详情抽屉添加费用能力。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zzh 2026-07-07 12:36:18 +08:00
parent 74151b0738
commit dd5c82e523
2 changed files with 129 additions and 0 deletions

View File

@ -0,0 +1,66 @@
using System.ComponentModel.DataAnnotations;
namespace Oms.Application.Dtos.Orders;
/// <summary>
/// 手动添加订单费用入参。
/// </summary>
public class OrderFeeAddInput
{
/// <summary>
/// 订单主键。
/// </summary>
[Required]
public int Id { get; set; }
/// <summary>
/// 费用类型:receivable / payable。
/// </summary>
[Required]
[StringLength(16)]
public string Type { get; set; } = string.Empty;
/// <summary>
/// 费用项编码。
/// </summary>
[Required]
[StringLength(64)]
public string CostItem { get; set; } = string.Empty;
/// <summary>
/// 金额。
/// </summary>
[Required]
public decimal Amount { get; set; }
/// <summary>
/// 币种。
/// </summary>
[Required]
[StringLength(16)]
public string Currency { get; set; } = string.Empty;
/// <summary>
/// 计费阶段:forecast / inbound / outbound。
/// </summary>
[Required]
[StringLength(16)]
public string ChargingStage { get; set; } = string.Empty;
/// <summary>
/// 下游服务商(应付可选)。
/// </summary>
[StringLength(128)]
public string? DownstreamProvider { get; set; }
/// <summary>
/// 价格编号(可选)。
/// </summary>
[StringLength(64)]
public string? PriceNo { get; set; }
/// <summary>
/// 价格生效时间(可选)。
/// </summary>
public DateTime? PriceEffectiveTime { get; set; }
}

View File

@ -233,6 +233,69 @@ public partial class OrderService
return (await MapOrderOutputsAsync([order])).First();
}
/// <summary>
/// 手动添加订单费用(应收或应付)。
/// </summary>
[HttpPost("/order/fee/add")]
public async Task<OrderOutput> 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))