新增MES库区管理功能,包含免密接口、数据处理逻辑及相关控制器、服务和实体的实现。支持库区的增删改查操作,优化用户体验并增强系统的实时数据同步能力。
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
using Prism.Events;
|
||||
|
||||
namespace YY.Admin.Core.Events;
|
||||
|
||||
public class WarehouseAreaChangedPayload
|
||||
{
|
||||
public string Action { get; set; } = string.Empty;
|
||||
public string? WarehouseAreaId { get; set; }
|
||||
}
|
||||
|
||||
public class WarehouseAreaChangedEvent : PubSubEvent<WarehouseAreaChangedPayload>
|
||||
{
|
||||
}
|
||||
@@ -15,4 +15,12 @@ public interface IRawMaterialCardService
|
||||
Task<bool> DeleteAsync(string id, CancellationToken ct = default);
|
||||
Task<bool> DeleteBatchAsync(string ids, CancellationToken ct = default);
|
||||
Task<bool> UpdatePriorityAsync(string id, string priorityPickup, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// 按拆码明细 ID 列表批量删除原材料卡片。
|
||||
/// </summary>
|
||||
/// <param name="splitDetailIds">拆码明细行的 GUID 集合(自动 distinct,空跳过)</param>
|
||||
/// <param name="dryRun">为 true 时仅返回匹配数量、不真正删除(用于「重新拆码」弹窗预提示)</param>
|
||||
/// <returns>匹配/删除的卡片数量;失败返回 -1</returns>
|
||||
Task<int> DeleteBySplitDetailIdsAsync(IEnumerable<string> splitDetailIds, bool dryRun = false, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
@@ -23,4 +23,10 @@ public interface IRawMaterialEntryService
|
||||
|
||||
/// <summary>调用后端接口生成条码/批次号(格式:QH+物料编码+yyMMdd+序号)</summary>
|
||||
Task<string?> GenerateBarcodeAsync(string materialCode, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// 同步读取本地缓存的「全量入场记录」快照(深拷贝),不会触发远端拉取。
|
||||
/// 主要用于「磅单已入场重量」等跨表实时聚合,且需要保持与后端相同口径的场景。
|
||||
/// </summary>
|
||||
IReadOnlyList<MesXslRawMaterialEntry> GetCachedSnapshot();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
using YY.Admin.Core.Entity;
|
||||
|
||||
namespace YY.Admin.Core.Services;
|
||||
|
||||
public record WarehouseAreaPageResult(List<MesXslWarehouseArea> Records, long Total, int PageNo, int PageSize);
|
||||
|
||||
/// <summary>
|
||||
/// 库区管理服务(CRUD + 状态切换 + 编码校验)。
|
||||
/// 走 jeecg 免密接口 /xslmes/mesXslWarehouseArea/anon/*。
|
||||
///
|
||||
/// 注意:本接口契约不可随意改签名,被以下调用方引用:
|
||||
/// - WarehouseAreaListViewModel(列表/筛选/启停/删除)
|
||||
/// - WarehouseAreaEditDialogViewModel(新增/编辑/校验)
|
||||
/// - WarehouseAreaPickerDialogViewModel(拆码明细库位选择弹窗)
|
||||
/// </summary>
|
||||
public interface IWarehouseAreaService
|
||||
{
|
||||
/// <summary>
|
||||
/// 分页查询。参数顺序与 WarehouseAreaListViewModel 调用一致:
|
||||
/// pageNo, pageSize, areaCode, areaName, warehouseId, status。
|
||||
/// </summary>
|
||||
/// <param name="status">"0" 启用 / "1" 停用 / "" 或 null 表示不限</param>
|
||||
Task<WarehouseAreaPageResult> PageAsync(
|
||||
int pageNo,
|
||||
int pageSize,
|
||||
string? areaCode = null,
|
||||
string? areaName = null,
|
||||
string? warehouseId = null,
|
||||
string? status = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>按 ID 查单条。</summary>
|
||||
Task<MesXslWarehouseArea?> GetByIdAsync(string id, CancellationToken ct = default);
|
||||
|
||||
/// <summary>新增库区。后端会校验 areaCode 重复。</summary>
|
||||
Task<bool> AddAsync(MesXslWarehouseArea area, CancellationToken ct = default);
|
||||
|
||||
/// <summary>编辑库区(按 ID)。</summary>
|
||||
Task<bool> EditAsync(MesXslWarehouseArea area, CancellationToken ct = default);
|
||||
|
||||
/// <summary>按 ID 删除。</summary>
|
||||
Task<bool> DeleteAsync(string id, CancellationToken ct = default);
|
||||
|
||||
/// <summary>切换启停状态:传入目标状态 "0"=启用 / "1"=停用。</summary>
|
||||
Task<bool> UpdateStatusAsync(string id, string newStatus, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// 校验库区编码是否可用(不存在则可用)。
|
||||
/// </summary>
|
||||
/// <param name="areaCode">待校验的库区编码</param>
|
||||
/// <param name="excludeId">编辑时排除自身 ID;新增传 null 或空</param>
|
||||
/// <returns>true=可用(无重复),false=已被占用</returns>
|
||||
Task<bool> CheckAreaCodeAsync(string areaCode, string? excludeId, CancellationToken ct = default);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using YY.Admin.Core.Entity;
|
||||
|
||||
namespace YY.Admin.Core.Services;
|
||||
|
||||
public interface IWarehouseService
|
||||
{
|
||||
/// <summary>获取全部仓库列表(在线时拉取远端并刷新缓存,离线时返回本地缓存)</summary>
|
||||
Task<List<MesXslWarehouse>> GetAllAsync(CancellationToken ct = default);
|
||||
}
|
||||
@@ -4,6 +4,9 @@ public class MesXslRawMaterialCard
|
||||
{
|
||||
public string? Id { get; set; }
|
||||
public string? Barcode { get; set; }
|
||||
// 关联的拆码明细行 ID(GUID);「生成原材料卡片」时由桌面端填入,
|
||||
// 「重新拆码」按入场记录的 PortionDetailIds 批量 IN 删除关联卡片。
|
||||
public string? SplitDetailId { get; set; }
|
||||
public string? BatchNo { get; set; }
|
||||
public DateTime? EntryDate { get; set; }
|
||||
public string? MaterialId { get; set; }
|
||||
|
||||
@@ -23,6 +23,19 @@ public class MesXslRawMaterialEntry
|
||||
public string? TotalPortions { get; set; }
|
||||
public string? PortionWeight { get; set; }
|
||||
public string? PortionPackages { get; set; }
|
||||
// 拆码明细各行库位的拼接(以 / 分隔,末尾带 /,如 1F-A01/1F-A02/)。
|
||||
// 与 WarehouseLocation(基础资料整票级单值)独立,专供明细行回填。
|
||||
public string? PortionWarehouseLocations { get; set; }
|
||||
// 拆码明细每行的 GUID 拼接(以 / 分隔,末尾带 /),与其它 portion 字段行序对齐,
|
||||
// 用于「重新拆码」按拆码明细 ID 反查并清除关联原材料卡片。
|
||||
public string? PortionDetailIds { get; set; }
|
||||
/// <summary>
|
||||
/// 拆码明细行级「已生成卡片」标志拼接(以 / 分隔,末尾带 /,1=已生成 0=未生成)。
|
||||
/// 与 PortionDetailIds 行序对齐,作为「生成原材料卡片」过滤待生成行的唯一依据:
|
||||
/// HasCard==true 的行不再参与生成(避免重复加卡 + 条码冲突);HasCard==false 的行才参与续生成。
|
||||
/// 历史记录留空时桌面端降级用 PrintFlag 推断(持久化 ID 非空 && PrintFlag=1 ⇒ 视为已生成)。
|
||||
/// </summary>
|
||||
public string? PortionCardFlags { get; set; }
|
||||
|
||||
/// <summary>检测结果:0未检 1合格 2不合格</summary>
|
||||
public string? TestResult { get; set; }
|
||||
|
||||
11
yy-admin-master/YY.Admin.Core/Entity/MesXslWarehouse.cs
Normal file
11
yy-admin-master/YY.Admin.Core/Entity/MesXslWarehouse.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace YY.Admin.Core.Entity;
|
||||
|
||||
public class MesXslWarehouse
|
||||
{
|
||||
public string? Id { get; set; }
|
||||
public string? WarehouseCode { get; set; }
|
||||
public string? WarehouseName { get; set; }
|
||||
public string? WarehouseCategory { get; set; }
|
||||
public string? Status { get; set; }
|
||||
public int? TenantId { get; set; }
|
||||
}
|
||||
31
yy-admin-master/YY.Admin.Core/Entity/MesXslWarehouseArea.cs
Normal file
31
yy-admin-master/YY.Admin.Core/Entity/MesXslWarehouseArea.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace YY.Admin.Core.Entity;
|
||||
|
||||
public class MesXslWarehouseArea
|
||||
{
|
||||
public string? Id { get; set; }
|
||||
public string? AreaCode { get; set; }
|
||||
public string? AreaName { get; set; }
|
||||
public string? WarehouseId { get; set; }
|
||||
public string? WarehouseName { get; set; }
|
||||
public string? WarehouseCategory { get; set; }
|
||||
|
||||
/// <summary>仓库分类名称(JeecgBoot @Dict 自动翻译,JSON key = warehouseCategory_dictText)</summary>
|
||||
[JsonPropertyName("warehouseCategory_dictText")]
|
||||
public string? WarehouseCategoryName { get; set; }
|
||||
public int? MaxCapacity { get; set; }
|
||||
public int? ActualCapacity { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
|
||||
/// <summary>状态:0启用 1停用</summary>
|
||||
public string? Status { get; set; }
|
||||
|
||||
public int? TenantId { get; set; }
|
||||
public string? CreateBy { get; set; }
|
||||
public DateTime? CreateTime { get; set; }
|
||||
public string? UpdateBy { get; set; }
|
||||
public DateTime? UpdateTime { get; set; }
|
||||
|
||||
public string StatusText => Status == "1" ? "停用" : "启用";
|
||||
}
|
||||
@@ -34,6 +34,12 @@ public class MesXslWeightRecord
|
||||
/// <summary>净重(KG)=毛重-皮重,自动计算</summary>
|
||||
public double? NetWeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 已入场重量(KG) —— 后端实时计算,不入库。
|
||||
/// 来源:所有引用本榜单(BillNo)的原料入场记录拆码明细的 (份数×每份重量) 累计求和。
|
||||
/// </summary>
|
||||
public double? EnteredWeight { get; set; }
|
||||
|
||||
/// <summary>司机姓名</summary>
|
||||
public string? DriverName { get; set; }
|
||||
|
||||
|
||||
@@ -44,6 +44,8 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
|
||||
new SysMenu{ Id=1300150010801, Pid=1300150000101, Title="新增原料入场记录", Path="/xslmes/rawMaterialEntryOperation", Name="rawMaterialEntryOperation", Component="RawMaterialEntryOperationView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=107 },
|
||||
// 原材料卡片
|
||||
new SysMenu{ Id=1300150010901, Pid=1300150000101, Title="原材料卡片", Path="/xslmes/mesXslRawMaterialCard", Name="mesXslRawMaterialCard", Component="RawMaterialCardListView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=108 },
|
||||
// 库区管理
|
||||
new SysMenu{ Id=1300150011001, Pid=1300150000101, Title="库区管理", Path="/xslmes/mesXslWarehouseArea", Name="mesXslWarehouseArea", Component="WarehouseAreaListView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=109 },
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ public class SysTenantMenuSeedData : ISqlSugarEntitySeedData<SysTenantMenu>
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300150010601},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300150010701},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300150010801},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300150010901},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300150011001},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300200012101},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300200012111},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300200012121},
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
using System.Globalization;
|
||||
using YY.Admin.Core.Entity;
|
||||
|
||||
namespace YY.Admin.Core.Util;
|
||||
|
||||
/// <summary>
|
||||
/// 「已入场重量」桌面端本地累计计算器。
|
||||
/// 与后端 IMesXslRawMaterialEntryService.sumEnteredWeightByBillNos 保持同一口径:
|
||||
/// ① 把 totalPortions / portionWeight 按 "x/y/z/" 拆分
|
||||
/// ② 逐位 (份数 × 每份重量) 累加,得到该 entry 的小计
|
||||
/// ③ 同一榜单(BillNo)下所有 entry 的小计再次累加
|
||||
/// 任一位置解析失败或缺失:静默跳过,保证 UI 降级可用。
|
||||
/// </summary>
|
||||
public static class EnteredWeightCalculator
|
||||
{
|
||||
/// <summary>累计一条入场记录的小计。</summary>
|
||||
public static double SumOneEntry(string? totalPortions, string? portionWeight)
|
||||
{
|
||||
var portions = SplitJoined(totalPortions);
|
||||
var weights = SplitJoined(portionWeight);
|
||||
if (portions.Length == 0 || weights.Length == 0)
|
||||
{
|
||||
return 0d;
|
||||
}
|
||||
var n = Math.Min(portions.Length, weights.Length);
|
||||
double sum = 0d;
|
||||
for (var i = 0; i < n; i++)
|
||||
{
|
||||
if (!TryParse(portions[i], out var p) || !TryParse(weights[i], out var w))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
sum += p * w;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
/// <summary>按 BillNo 分组累计「已入场重量」。返回 Dictionary<BillNo, 总和>。</summary>
|
||||
public static Dictionary<string, double> SumByBillNos(
|
||||
IEnumerable<MesXslRawMaterialEntry> entries,
|
||||
IEnumerable<string?> billNos)
|
||||
{
|
||||
var keys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var b in billNos)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(b)) keys.Add(b!);
|
||||
}
|
||||
var result = new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase);
|
||||
if (keys.Count == 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var e in entries)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(e.BillNo) || !keys.Contains(e.BillNo!))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var sub = SumOneEntry(e.TotalPortions, e.PortionWeight);
|
||||
if (sub == 0d)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (result.TryGetValue(e.BillNo!, out var acc))
|
||||
{
|
||||
result[e.BillNo!] = acc + sub;
|
||||
}
|
||||
else
|
||||
{
|
||||
result[e.BillNo!] = sub;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string[] SplitJoined(string? value)
|
||||
{
|
||||
// 兼容 "x/y/z" 与 "x/y/z/" 两种格式;过滤空段
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
return value.Split('/')
|
||||
.Select(s => s.Trim())
|
||||
.Where(s => s.Length > 0)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static bool TryParse(string s, out double v) =>
|
||||
double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out v);
|
||||
}
|
||||
Reference in New Issue
Block a user