新增MES库区管理功能,包含免密接口、数据处理逻辑及相关控制器、服务和实体的实现。支持库区的增删改查操作,优化用户体验并增强系统的实时数据同步能力。
This commit is contained in:
@@ -10,6 +10,7 @@ using YY.Admin.Core;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Events;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Core.Util;
|
||||
|
||||
namespace YY.Admin.Services.Service.WeightRecord;
|
||||
|
||||
@@ -20,6 +21,9 @@ public class WeightRecordService : IWeightRecordService, ISingletonDependency
|
||||
private readonly INetworkMonitor _networkMonitor;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ILoggerService _logger;
|
||||
// 用于按 BillNo 实时累计「已入场重量」,从本地入场记录缓存推导,
|
||||
// 与后端 IMesXslRawMaterialEntryService.sumEnteredWeightByBillNos 同一口径。
|
||||
private readonly IRawMaterialEntryService _rawMaterialEntryService;
|
||||
private readonly SemaphoreSlim _syncLock = new(1, 1);
|
||||
private readonly object _cacheLock = new();
|
||||
private readonly string _pendingOpsFilePath;
|
||||
@@ -39,13 +43,15 @@ public class WeightRecordService : IWeightRecordService, ISingletonDependency
|
||||
IConfiguration configuration,
|
||||
INetworkMonitor networkMonitor,
|
||||
IEventAggregator eventAggregator,
|
||||
ILoggerService logger)
|
||||
ILoggerService logger,
|
||||
IRawMaterialEntryService rawMaterialEntryService)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_configuration = configuration;
|
||||
_networkMonitor = networkMonitor;
|
||||
_eventAggregator = eventAggregator;
|
||||
_logger = logger;
|
||||
_rawMaterialEntryService = rawMaterialEntryService;
|
||||
|
||||
var appDataDir = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
@@ -107,11 +113,49 @@ public class WeightRecordService : IWeightRecordService, ISingletonDependency
|
||||
var filtered = ApplyFilters(source, filterBillNo, filterPlateNumber, filterInoutDirection, filterDriverName, filterMixerMaterialName);
|
||||
var total = filtered.Count;
|
||||
var records = filtered.Skip(Math.Max(0, (pageNo - 1) * pageSize)).Take(pageSize).ToList();
|
||||
// 当前页结果按本地入场记录缓存,按 BillNo 实时累计「已入场重量」(与后端口径一致)。
|
||||
// 放在分页之后做:避免对全量 source 做不必要的计算。
|
||||
FillEnteredWeightFromLocalEntries(records);
|
||||
return new WeightRecordPageResult(records, total, pageNo, pageSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 给一批磅单记录批量填充「已入场重量」。
|
||||
/// 数据来源:本地 RawMaterialEntry 缓存的拆码明细字段(totalPortions / portionWeight)。
|
||||
/// 与后端 sumEnteredWeightByBillNos 同口径,确保离线场景也能正确显示。
|
||||
/// </summary>
|
||||
private void FillEnteredWeightFromLocalEntries(List<MesXslWeightRecord> records)
|
||||
{
|
||||
if (records.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var billNos = records
|
||||
.Select(r => r.BillNo)
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
if (billNos.Count == 0)
|
||||
{
|
||||
// 全部磅单都没有 BillNo:保留服务端返回值(若有),避免无端置 0。
|
||||
return;
|
||||
}
|
||||
var entries = _rawMaterialEntryService.GetCachedSnapshot();
|
||||
var sumMap = EnteredWeightCalculator.SumByBillNos(entries, billNos!);
|
||||
foreach (var r in records)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(r.BillNo))
|
||||
{
|
||||
r.EnteredWeight = 0d;
|
||||
continue;
|
||||
}
|
||||
r.EnteredWeight = sumMap.TryGetValue(r.BillNo, out var v) ? v : 0d;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MesXslWeightRecord?> GetByIdAsync(string id, CancellationToken ct = default)
|
||||
{
|
||||
MesXslWeightRecord? record = null;
|
||||
if (_networkMonitor.IsOnline)
|
||||
{
|
||||
try
|
||||
@@ -119,11 +163,15 @@ public class WeightRecordService : IWeightRecordService, ISingletonDependency
|
||||
var url = $"{BaseUrl}/xslmes/mesXslWeightRecord/anon/queryById?id={Uri.EscapeDataString(id)}&tenantId={DefaultTenantId}";
|
||||
using var client = CreateClient();
|
||||
var resp = await client.GetAsync(url, ct).ConfigureAwait(false);
|
||||
if (!resp.IsSuccessStatusCode) return null;
|
||||
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
if (!doc.RootElement.TryGetProperty("result", out var resultEl)) return null;
|
||||
return resultEl.Deserialize<MesXslWeightRecord>(_jsonOpts);
|
||||
if (resp.IsSuccessStatusCode)
|
||||
{
|
||||
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
if (doc.RootElement.TryGetProperty("result", out var resultEl))
|
||||
{
|
||||
record = resultEl.Deserialize<MesXslWeightRecord>(_jsonOpts);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -131,11 +179,20 @@ public class WeightRecordService : IWeightRecordService, ISingletonDependency
|
||||
}
|
||||
}
|
||||
|
||||
lock (_cacheLock)
|
||||
if (record == null)
|
||||
{
|
||||
return _localCache.FirstOrDefault(v => string.Equals(v.Id, id, StringComparison.OrdinalIgnoreCase)) is { } found
|
||||
? Clone(found) : null;
|
||||
lock (_cacheLock)
|
||||
{
|
||||
record = _localCache.FirstOrDefault(v => string.Equals(v.Id, id, StringComparison.OrdinalIgnoreCase)) is { } found
|
||||
? Clone(found) : null;
|
||||
}
|
||||
}
|
||||
|
||||
if (record != null)
|
||||
{
|
||||
FillEnteredWeightFromLocalEntries(new List<MesXslWeightRecord> { record });
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
public async Task<bool> AddAsync(MesXslWeightRecord entity, CancellationToken ct = default)
|
||||
@@ -648,6 +705,8 @@ public class WeightRecordService : IWeightRecordService, ISingletonDependency
|
||||
GrossWeight = input.GrossWeight,
|
||||
TareWeight = input.TareWeight,
|
||||
NetWeight = input.NetWeight,
|
||||
// 「已入场重量」由实时聚合写入,Clone 也要原样传递,避免本地缓存 / Pending 重放后被抹掉
|
||||
EnteredWeight = input.EnteredWeight,
|
||||
DriverName = input.DriverName,
|
||||
DriverPhone = input.DriverPhone,
|
||||
BillType = input.BillType,
|
||||
|
||||
Reference in New Issue
Block a user