WMS主数据结构升级,支持仓库/物料类型等新实体
后端:重构WmsController/WmsService,新增仓库、库区、货位、物料类型等实体及其增删改查接口,支持批量生成网格货位、货位/容器锁定与启用、物料归档与批量清理。数据库模型新增Warehouse、MaterialType、StockEvent等表,调整索引和字段,支持SQLite自动补表/补列及数据迁移,接口兼容旧语义。 前端:wms.ts/types/wms.ts全面适配新结构,升级API路由和类型定义。WarehouseManagementView.vue重构,支持仓库、物料类型管理、货位网格可视化、库存事件展示等。WmsEntityDialog.vue支持新字段编辑及联动选择。新增AreaFacadePanel.vue实现库区网格立面可视化。wmsOptions.ts新增相关常量及UI优化。 兼容性:后端自动迁移旧数据,接口兼容旧绑定语义,提升灵活性、可维护性和可视化能力。
This commit is contained in:
@@ -19,11 +19,23 @@ public abstract class WarehouseHistoryBase
|
||||
public string Extend { get; set; } = "{}";
|
||||
}
|
||||
|
||||
public sealed class WarehouseArea : EntityBase
|
||||
public sealed class Warehouse : EntityBase
|
||||
{
|
||||
[MaxLength(64)] public string Code { get; set; } = "";
|
||||
[MaxLength(128)] public string Name { get; set; } = "";
|
||||
[MaxLength(64)] public string Type { get; set; } = "Default";
|
||||
public bool Enabled { get; set; } = true;
|
||||
public int SortOrder { get; set; }
|
||||
}
|
||||
|
||||
public sealed class WarehouseArea : EntityBase
|
||||
{
|
||||
public Guid WarehouseId { get; set; }
|
||||
[MaxLength(64)] public string Code { get; set; } = "";
|
||||
[MaxLength(128)] public string Name { get; set; } = "";
|
||||
[MaxLength(64)] public string Type { get; set; } = "Storage";
|
||||
[MaxLength(32)] public string LayoutMode { get; set; } = AreaLayoutModes.Flat;
|
||||
[MaxLength(32)] public string State { get; set; } = "Default";
|
||||
public bool Enabled { get; set; } = true;
|
||||
public int SortOrder { get; set; }
|
||||
}
|
||||
@@ -34,9 +46,16 @@ public sealed class Storage : EntityBase
|
||||
[MaxLength(64)] public string Code { get; set; } = "";
|
||||
[MaxLength(128)] public string Name { get; set; } = "";
|
||||
[MaxLength(64)] public string StorageType { get; set; } = "Storage";
|
||||
[MaxLength(32)] public string LocationKind { get; set; } = LocationKinds.Station;
|
||||
public int ColumnNo { get; set; }
|
||||
public int LevelNo { get; set; } = 1;
|
||||
public int DepthNo { get; set; } = 1;
|
||||
[MaxLength(64)] public string SiteId { get; set; } = "";
|
||||
[MaxLength(64)] public string SiteCode { get; set; } = "";
|
||||
[MaxLength(128)] public string Barcode { get; set; } = "";
|
||||
/// <summary>已废弃:一货位一容器,保留列兼容旧库。</summary>
|
||||
public int Capacity { get; set; }
|
||||
[MaxLength(32)] public string Status { get; set; } = StorageStatuses.Available;
|
||||
[MaxLength(32)] public string Status { get; set; } = StorageStatuses.Empty;
|
||||
[MaxLength(64)] public string Usage { get; set; } = "";
|
||||
public int Priority { get; set; }
|
||||
[MaxLength(64)] public string ZoneCode { get; set; } = "";
|
||||
@@ -47,10 +66,26 @@ public sealed class Storage : EntityBase
|
||||
|
||||
public sealed class Container : EntityBase
|
||||
{
|
||||
public Guid? AreaId { get; set; }
|
||||
[MaxLength(64)] public string Code { get; set; } = "";
|
||||
[MaxLength(128)] public string Name { get; set; } = "";
|
||||
[MaxLength(64)] public string ContainerType { get; set; } = "Box";
|
||||
[MaxLength(64)] public string Status { get; set; } = "Idle";
|
||||
[MaxLength(64)] public string Status { get; set; } = ContainerStatuses.EmptyMaterial;
|
||||
[MaxLength(128)] public string Barcode { get; set; } = "";
|
||||
public double Length { get; set; }
|
||||
public double Width { get; set; }
|
||||
public double Height { get; set; }
|
||||
public bool Enabled { get; set; } = true;
|
||||
}
|
||||
|
||||
public sealed class MaterialType : EntityBase
|
||||
{
|
||||
[MaxLength(64)] public string Code { get; set; } = "";
|
||||
[MaxLength(128)] public string Name { get; set; } = "";
|
||||
[MaxLength(128)] public string Spec { get; set; } = "";
|
||||
[MaxLength(32)] public string Unit { get; set; } = "pcs";
|
||||
[MaxLength(64)] public string Category { get; set; } = "";
|
||||
[MaxLength(64)] public string BarcodePrefix { get; set; } = "";
|
||||
public bool Enabled { get; set; } = true;
|
||||
}
|
||||
|
||||
@@ -58,9 +93,13 @@ public sealed class Material : EntityBase
|
||||
{
|
||||
[MaxLength(64)] public string Code { get; set; } = "";
|
||||
[MaxLength(128)] public string Name { get; set; } = "";
|
||||
[MaxLength(64)] public string TypeCode { get; set; } = "";
|
||||
[MaxLength(128)] public string Barcode { get; set; } = "";
|
||||
[MaxLength(128)] public string Spec { get; set; } = "";
|
||||
[MaxLength(32)] public string Unit { get; set; } = "pcs";
|
||||
[MaxLength(64)] public string Category { get; set; } = "";
|
||||
[MaxLength(32)] public string LifecycleStatus { get; set; } = MaterialLifecycles.Active;
|
||||
public DateTimeOffset? UnboundAt { get; set; }
|
||||
public bool Enabled { get; set; } = true;
|
||||
}
|
||||
|
||||
@@ -75,18 +114,50 @@ public sealed class ContainerLocation : EntityBase
|
||||
public DateTimeOffset EnteredAt { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>容器-物料绑定(无数量语义;Quantity 列仅兼容旧库,固定为 1)。</summary>
|
||||
public sealed class ContainerMaterial : EntityBase
|
||||
{
|
||||
public Guid ContainerId { get; set; }
|
||||
public Guid MaterialId { get; set; }
|
||||
public decimal Quantity { get; set; }
|
||||
public decimal Quantity { get; set; } = 1;
|
||||
[MaxLength(64)] public string BatchNo { get; set; } = "";
|
||||
[MaxLength(64)] public string SerialNo { get; set; } = "";
|
||||
[MaxLength(32)] public string Status { get; set; } = ContainerMaterialStatuses.Loaded;
|
||||
[MaxLength(32)] public string Status { get; set; } = ContainerMaterialStatuses.Bound;
|
||||
public DateTimeOffset BoundAt { get; set; }
|
||||
public DateTimeOffset LoadedAt { get; set; }
|
||||
public DateTimeOffset? UnloadedAt { get; set; }
|
||||
}
|
||||
|
||||
public sealed class StockEvent
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(32)] public string EventType { get; set; } = "";
|
||||
public Guid? MaterialId { get; set; }
|
||||
[MaxLength(64)] public string MaterialCode { get; set; } = "";
|
||||
[MaxLength(128)] public string MaterialName { get; set; } = "";
|
||||
[MaxLength(128)] public string MaterialBarcode { get; set; } = "";
|
||||
[MaxLength(64)] public string MaterialTypeCode { get; set; } = "";
|
||||
public Guid? ContainerId { get; set; }
|
||||
[MaxLength(64)] public string ContainerCode { get; set; } = "";
|
||||
[MaxLength(128)] public string ContainerName { get; set; } = "";
|
||||
public Guid? StorageId { get; set; }
|
||||
[MaxLength(64)] public string StorageCode { get; set; } = "";
|
||||
[MaxLength(128)] public string StorageName { get; set; } = "";
|
||||
[MaxLength(64)] public string AreaCode { get; set; } = "";
|
||||
public Guid? FromStorageId { get; set; }
|
||||
[MaxLength(64)] public string FromStorageCode { get; set; } = "";
|
||||
[MaxLength(128)] public string FromStorageName { get; set; } = "";
|
||||
public Guid? ToStorageId { get; set; }
|
||||
[MaxLength(64)] public string ToStorageCode { get; set; } = "";
|
||||
[MaxLength(128)] public string ToStorageName { get; set; } = "";
|
||||
[MaxLength(64)] public string RefType { get; set; } = "Manual";
|
||||
public Guid? RefId { get; set; }
|
||||
[MaxLength(64)] public string RefCode { get; set; } = "";
|
||||
[MaxLength(128)] public string Operator { get; set; } = "";
|
||||
public DateTimeOffset OperatedAt { get; set; }
|
||||
[MaxLength(500)] public string Reason { get; set; } = "";
|
||||
}
|
||||
|
||||
public sealed class ContainerLocationHistory : WarehouseHistoryBase
|
||||
{
|
||||
[MaxLength(32)] public string FromLocationType { get; set; } = "";
|
||||
@@ -101,6 +172,20 @@ public sealed class ContainerMaterialHistory : WarehouseHistoryBase
|
||||
public decimal QuantityDelta { get; set; }
|
||||
}
|
||||
|
||||
public static class AreaLayoutModes
|
||||
{
|
||||
public const string Flat = "Flat";
|
||||
public const string Grid = "Grid";
|
||||
public static readonly HashSet<string> All = new(StringComparer.OrdinalIgnoreCase) { Flat, Grid };
|
||||
}
|
||||
|
||||
public static class LocationKinds
|
||||
{
|
||||
public const string Grid = "Grid";
|
||||
public const string Station = "Station";
|
||||
public static readonly HashSet<string> All = new(StringComparer.OrdinalIgnoreCase) { Grid, Station };
|
||||
}
|
||||
|
||||
public static class ContainerLocationTypes
|
||||
{
|
||||
public const string Storage = "Storage";
|
||||
@@ -118,20 +203,43 @@ public static class ContainerLocationStatuses
|
||||
|
||||
public static class ContainerMaterialStatuses
|
||||
{
|
||||
public const string Bound = "Bound";
|
||||
// 兼容旧数据
|
||||
public const string Loaded = "Loaded";
|
||||
public const string Unloaded = "Unloaded";
|
||||
public const string Adjusted = "Adjusted";
|
||||
public const string Frozen = "Frozen";
|
||||
public static readonly HashSet<string> All = new(StringComparer.OrdinalIgnoreCase) { Loaded, Unloaded, Adjusted, Frozen };
|
||||
public static readonly HashSet<string> ActiveBind = new(StringComparer.OrdinalIgnoreCase) { Bound, Loaded, Adjusted, Frozen };
|
||||
}
|
||||
|
||||
public static class ContainerStatuses
|
||||
{
|
||||
public const string EmptyMaterial = "EmptyMaterial";
|
||||
public const string FullMaterial = "FullMaterial";
|
||||
public static readonly HashSet<string> All = new(StringComparer.OrdinalIgnoreCase) { EmptyMaterial, FullMaterial };
|
||||
}
|
||||
|
||||
public static class StorageStatuses
|
||||
{
|
||||
public const string Empty = "Empty";
|
||||
public const string EmptyContainer = "EmptyContainer";
|
||||
public const string FullContainer = "FullContainer";
|
||||
public const string Disabled = "Disabled";
|
||||
// 兼容旧值(读时映射,写时用新值)
|
||||
public const string Available = "Available";
|
||||
public const string Idle = "Idle";
|
||||
public const string Occupied = "Occupied";
|
||||
public const string Disabled = "Disabled";
|
||||
public static readonly HashSet<string> All = new(StringComparer.OrdinalIgnoreCase) { Available, Idle, Occupied, Disabled };
|
||||
|
||||
public static readonly HashSet<string> All = new(StringComparer.OrdinalIgnoreCase)
|
||||
{ Empty, EmptyContainer, FullContainer, Disabled };
|
||||
|
||||
public static string Normalize(string? status) => status switch
|
||||
{
|
||||
Available or Idle => Empty,
|
||||
Occupied => FullContainer,
|
||||
_ when string.IsNullOrWhiteSpace(status) => Empty,
|
||||
_ => status
|
||||
};
|
||||
}
|
||||
|
||||
public static class StorageTypes
|
||||
@@ -143,6 +251,20 @@ public static class StorageTypes
|
||||
public const string FinishedGoods = "FinishedGoods";
|
||||
}
|
||||
|
||||
public static class MaterialLifecycles
|
||||
{
|
||||
public const string Active = "Active";
|
||||
public const string Archived = "Archived";
|
||||
public static readonly HashSet<string> All = new(StringComparer.OrdinalIgnoreCase) { Active, Archived };
|
||||
}
|
||||
|
||||
public static class StockEventTypes
|
||||
{
|
||||
public const string Bind = "Bind";
|
||||
public const string Unbind = "Unbind";
|
||||
public const string ContainerMove = "ContainerMove";
|
||||
}
|
||||
|
||||
public static class WmsTransportTriggerTypes
|
||||
{
|
||||
public const string MaterialCall = "MaterialCall";
|
||||
@@ -194,6 +316,7 @@ public sealed class WmsTransportTask : EntityBase
|
||||
public Guid TargetStorageId { get; set; }
|
||||
public Guid ContainerId { get; set; }
|
||||
public Guid? MaterialId { get; set; }
|
||||
/// <summary>已废弃:单物料实体模型不再使用数量。</summary>
|
||||
public decimal? Quantity { get; set; }
|
||||
[MaxLength(32)] public string Status { get; set; } = WmsTransportTaskStatuses.Pending;
|
||||
[MaxLength(64)] public string DispatchMissionId { get; set; } = "";
|
||||
@@ -227,3 +350,9 @@ public sealed class WmsTransportTaskHistory
|
||||
[MaxLength(1000)] public string ErrorMessage { get; set; } = "";
|
||||
public string SnapshotJson { get; set; } = "{}";
|
||||
}
|
||||
|
||||
public static class WmsDefaults
|
||||
{
|
||||
public const string DefaultWarehouseCode = "DEFAULT";
|
||||
public const string DefaultWarehouseName = "默认仓库";
|
||||
}
|
||||
|
||||
@@ -12,6 +12,12 @@ public sealed class WmsReferenceValidator
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public async Task EnsureWarehouseAsync(Guid id)
|
||||
{
|
||||
if (!await _db.Warehouses.AnyAsync(x => x.Id == id))
|
||||
throw new InvalidOperationException("仓库不存在");
|
||||
}
|
||||
|
||||
public async Task EnsureAreaAsync(Guid id)
|
||||
{
|
||||
if (!await _db.WarehouseAreas.AnyAsync(x => x.Id == id))
|
||||
@@ -36,6 +42,12 @@ public sealed class WmsReferenceValidator
|
||||
throw new InvalidOperationException("物料不存在");
|
||||
}
|
||||
|
||||
public async Task EnsureMaterialTypeCodeAsync(string typeCode)
|
||||
{
|
||||
if (!await _db.MaterialTypes.AnyAsync(x => x.Code == typeCode))
|
||||
throw new InvalidOperationException("物料类型不存在");
|
||||
}
|
||||
|
||||
public async Task<(string Code, string Name)> ResolveLocationSnapshotAsync(string locationType, string locationId)
|
||||
{
|
||||
if (!ContainerLocationTypes.All.Contains(locationType))
|
||||
|
||||
+621
-166
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user