admin/src/Oms.Application/Services/Orders/OrderOperationLogWriter.cs
zzh 64737284f3
All checks were successful
Build and Deploy / deploy (push) Successful in 1m3s
feat: 运单操作日志改为列表摘要,补齐费用/支付/预报/打印埋点
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-10 11:46:33 +08:00

194 lines
6.5 KiB
C#

using Fake.DependencyInjection;
using Fake.Security.Claims;
using Fake.Users;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Oms.Application.Dtos.Orders;
using Oms.Application.Shared;
using Oms.Domain.OrdersAggregate.Consts;
using Oms.Domain.OrdersAggregate.Entities;
using Oms.Domain.OrdersAggregate.Repositories;
namespace Oms.Application.Services.Orders;
/// <summary>
/// 运单操作日志写入器:字段 diff 落库;失败不影响主业务。
/// </summary>
public class OrderOperationLogWriter : IScopedDependency
{
private readonly IOrderOperationLogRepository _repository;
private readonly ICurrentUser _currentUser;
private readonly OmsRbacUserDisplayNameResolver _userDisplayNameResolver;
private readonly ILogger<OrderOperationLogWriter> _logger;
public OrderOperationLogWriter(
IOrderOperationLogRepository repository,
ICurrentUser currentUser,
OmsRbacUserDisplayNameResolver userDisplayNameResolver,
ILogger<OrderOperationLogWriter> logger)
{
_repository = repository;
_currentUser = currentUser;
_userDisplayNameResolver = userDisplayNameResolver;
_logger = logger;
}
public Task TryWriteAsync(
Order order,
string action,
IReadOnlyDictionary<string, string?> before,
IReadOnlyDictionary<string, string?> after,
string? remark = null)
=> TryWriteAsync(order, action, before, after, remark, operatorId: null, operatorName: null);
public async Task TryWriteAsync(
Order order,
string action,
IReadOnlyDictionary<string, string?> before,
IReadOnlyDictionary<string, string?> after,
string? remark,
int? operatorId,
string? operatorName,
string operatorType = "staff",
string source = "pc")
{
try
{
var changes = OrderOperationLogSnapshot.Diff(before, after);
var (resolvedId, resolvedName) = await ResolveOperatorAsync(operatorId, operatorName);
var entity = new OrderOperationLog
{
OrderId = order.Id,
ServiceNo = order.ServiceNo,
Action = action,
ActionName = OrderOperationActionCodes.GetActionName(action),
OperatorId = resolvedId,
OperatorName = resolvedName,
OperatorType = string.IsNullOrWhiteSpace(operatorType) ? "staff" : operatorType.Trim(),
Source = string.IsNullOrWhiteSpace(source) ? "pc" : source.Trim(),
ChangesJson = JsonConvert.SerializeObject(
changes.Select(x => new OrderOperationLogChangeItem
{
Path = x.Path,
Label = x.Label,
Before = x.Before,
After = x.After
}).ToList()),
Remark = string.IsNullOrWhiteSpace(remark) ? null : remark.Trim(),
CreateUserId = resolvedId,
CreateTime = DateTime.Now
};
await _repository.InsertAsync(entity);
}
catch (Exception ex)
{
_logger.LogError(
ex,
"写入运单操作日志失败 OrderId={OrderId} Action={Action}",
order.Id,
action);
}
}
/// <summary>
/// 无字段 diff 时写入叙事日志(打印、下游预报成功等)。
/// </summary>
public Task TryWriteSimpleAsync(
Order order,
string action,
string? remark,
int? operatorId = null,
string? operatorName = null,
string operatorType = "staff",
string source = "pc")
=> TryWriteAsync(
order,
action,
new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase),
new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase),
remark,
operatorId,
operatorName,
operatorType,
source);
public async Task<OrderOperationLogListOutput> ListByOrderIdAsync(int orderId)
{
var rows = await _repository.GetListAsync(x => x.OrderId == orderId);
var items = rows
.OrderByDescending(x => x.CreateTime)
.ThenByDescending(x => x.Id)
.Select(MapOutput)
.ToList();
return new OrderOperationLogListOutput { Items = items };
}
private async Task<(int OperatorId, string? OperatorName)> ResolveOperatorAsync(
int? operatorId,
string? operatorName)
{
if (operatorId is > 0)
{
var name = string.IsNullOrWhiteSpace(operatorName)
? await _userDisplayNameResolver.ResolveAsync(operatorId.Value)
: operatorName.Trim();
return (operatorId.Value, name);
}
if (_currentUser.IsAuthenticated)
{
var id = _currentUser.GetId<int>();
var name = string.IsNullOrWhiteSpace(operatorName)
? await _userDisplayNameResolver.ResolveAsync(id)
: operatorName.Trim();
return (id, name);
}
return (0, string.IsNullOrWhiteSpace(operatorName) ? null : operatorName.Trim());
}
private static OrderOperationLogOutput MapOutput(OrderOperationLog row)
{
var changes = new List<OrderOperationLogChangeItem>();
if (!string.IsNullOrWhiteSpace(row.ChangesJson))
{
try
{
changes = JsonConvert.DeserializeObject<List<OrderOperationLogChangeItem>>(row.ChangesJson)
?? [];
}
catch
{
changes = [];
}
}
var views = changes
.Select(x => new OrderOperationLogSnapshot.OrderOperationLogChangeView(
x.Path,
x.Label,
x.Before,
x.After))
.ToList();
return new OrderOperationLogOutput
{
Id = row.Id,
OrderId = row.OrderId,
ServiceNo = row.ServiceNo,
Action = row.Action,
ActionName = row.ActionName,
OperatorId = row.OperatorId,
OperatorName = row.OperatorName,
OperatorType = row.OperatorType,
Source = row.Source,
Changes = changes,
Remark = row.Remark,
Summary = OrderOperationLogSnapshot.BuildSummary(views, row.Remark),
CreateTime = row.CreateTime
};
}
}