持久层重构为独立 MiGu.DB 工程并优化集成
- 新增 MiGu.DB 项目,迁移所有领域实体与枚举,统一模型约定 - 实现 Entity/Repository/UoW/Provider/Exception 等接口与实现 - 支持数据修补机制,完善 Sqlite 初始迁移与数据库管理 - Server 侧移除 EF Core 相关,依赖 MiGu.DB,PlatformPersistence 适配 - 业务服务注入 UoW/Repository,状态字段统一用 enum 及辅助类 - 统一异常处理,Controller 映射 HTTP 状态码 - 配置项与文档补充数据库启动、SchemaMode、迁移说明 - 新增 GlobalUsings.Db.cs、WmsStatusAliases.cs 简化类型引用 - 新增 HttpActorContextMiddleware 支持操作者上下文一致性 - 新增 MiGuDbContextModelSnapshot 追踪数据库结构 - 优化代码结构,解耦领域与持久层,提升扩展性与安全性
This commit is contained in:
+48
-342
@@ -1,358 +1,64 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using MiGu.Server.Persistence;
|
||||
|
||||
namespace MiGu.Server.Wms;
|
||||
|
||||
public abstract class WarehouseHistoryBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid? RelationId { get; set; }
|
||||
public string EventType { get; set; } = "";
|
||||
public string BeforeJson { get; set; } = "{}";
|
||||
public string AfterJson { get; set; } = "{}";
|
||||
public Guid? ContainerId { get; set; }
|
||||
public string Operator { get; set; } = "";
|
||||
public DateTimeOffset OperatedAt { get; set; }
|
||||
public string Source { get; set; } = "Manual";
|
||||
public string Reason { get; set; } = "";
|
||||
public string Remark { get; set; } = "";
|
||||
public string Extend { get; set; } = "{}";
|
||||
}
|
||||
// API / 读模型 DTO(留在 Server,不进 MiGu.DB)。请求字段中的状态仍为 string,由 Service 解析为枚举后写入实体。
|
||||
|
||||
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 record InventoryMaterialRow(
|
||||
Guid MaterialId, string MaterialCode, string MaterialName, string MaterialBarcode, string MaterialTypeCode,
|
||||
Guid ContainerId, string ContainerCode, string ContainerName,
|
||||
Guid? StorageId, string StorageCode, string StorageName,
|
||||
Guid? AreaId, string AreaCode, string AreaName,
|
||||
string LocationType, DateTimeOffset BoundAt);
|
||||
|
||||
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; }
|
||||
}
|
||||
public sealed record ContainerLocationSnapshot(
|
||||
Guid Id, Guid ContainerId, string LocationType, string LocationId, string LocationCode, string LocationName,
|
||||
string Status, DateTimeOffset EnteredAt, long Version);
|
||||
|
||||
public sealed class Storage : EntityBase
|
||||
{
|
||||
public Guid AreaId { get; set; }
|
||||
[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.Empty;
|
||||
[MaxLength(64)] public string Usage { get; set; } = "";
|
||||
public int Priority { get; set; }
|
||||
[MaxLength(64)] public string ZoneCode { get; set; } = "";
|
||||
public bool AllowInbound { get; set; } = true;
|
||||
public bool AllowOutbound { get; set; } = true;
|
||||
public bool Enabled { get; set; } = true;
|
||||
}
|
||||
public sealed record ContainerMaterialSnapshot(
|
||||
Guid Id, Guid ContainerId, Guid MaterialId, decimal Quantity, string BatchNo, string SerialNo, string Status,
|
||||
DateTimeOffset BoundAt, DateTimeOffset LoadedAt, DateTimeOffset? UnloadedAt, long Version);
|
||||
|
||||
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; } = 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 abstract record CommonRequest(Guid? Id, long? Version, bool IsLock, string Remark, string Extend);
|
||||
|
||||
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;
|
||||
}
|
||||
public sealed record MasterDataRequest(
|
||||
Guid? Id, long? Version, string Code, string Name, string Type, string Status, bool Enabled, int SortOrder,
|
||||
bool IsLock, string Remark, string Extend) : CommonRequest(Id, Version, IsLock, Remark, Extend);
|
||||
|
||||
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;
|
||||
}
|
||||
public sealed record AreaRequest(
|
||||
Guid? Id, long? Version, Guid? WarehouseId, string Code, string Name, string Type, string LayoutMode, string State,
|
||||
bool Enabled, int SortOrder, bool IsLock, string Remark, string Extend) : CommonRequest(Id, Version, IsLock, Remark, Extend);
|
||||
|
||||
public sealed class ContainerLocation : EntityBase
|
||||
{
|
||||
public Guid ContainerId { get; set; }
|
||||
[MaxLength(32)] public string LocationType { get; set; } = ContainerLocationTypes.Storage;
|
||||
[MaxLength(64)] public string LocationId { get; set; } = "";
|
||||
[MaxLength(64)] public string LocationCode { get; set; } = "";
|
||||
[MaxLength(128)] public string LocationName { get; set; } = "";
|
||||
[MaxLength(32)] public string Status { get; set; } = ContainerLocationStatuses.Active;
|
||||
public DateTimeOffset EnteredAt { get; set; }
|
||||
}
|
||||
public sealed record StorageRequest(
|
||||
Guid? Id, long? Version, Guid AreaId, string Code, string Name, string StorageType, string LocationKind,
|
||||
int ColumnNo, int LevelNo, int DepthNo, string SiteId, string SiteCode, string Barcode, int Capacity,
|
||||
string Status, string Usage, int Priority, string ZoneCode, bool AllowInbound, bool AllowOutbound, bool Enabled,
|
||||
bool IsLock, string Remark, string Extend) : CommonRequest(Id, Version, IsLock, Remark, Extend);
|
||||
|
||||
/// <summary>容器-物料绑定(无数量语义;Quantity 列仅兼容旧库,固定为 1)。</summary>
|
||||
public sealed class ContainerMaterial : EntityBase
|
||||
{
|
||||
public Guid ContainerId { get; set; }
|
||||
public Guid MaterialId { 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.Bound;
|
||||
public DateTimeOffset BoundAt { get; set; }
|
||||
public DateTimeOffset LoadedAt { get; set; }
|
||||
public DateTimeOffset? UnloadedAt { get; set; }
|
||||
}
|
||||
public sealed record GenerateBinsRequest(int ColumnFrom, int ColumnTo, int LevelFrom, int LevelTo, int DepthFrom, int DepthTo, string? CodePattern);
|
||||
|
||||
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 record ContainerRequest(
|
||||
Guid? Id, long? Version, Guid? AreaId, string Code, string Name, string Type, string Status, string Barcode,
|
||||
double Length, double Width, double Height, bool Enabled, bool IsLock, string Remark, string Extend)
|
||||
: CommonRequest(Id, Version, IsLock, Remark, Extend);
|
||||
|
||||
public sealed class ContainerLocationHistory : WarehouseHistoryBase
|
||||
{
|
||||
[MaxLength(32)] public string FromLocationType { get; set; } = "";
|
||||
[MaxLength(64)] public string FromLocationId { get; set; } = "";
|
||||
[MaxLength(32)] public string ToLocationType { get; set; } = "";
|
||||
[MaxLength(64)] public string ToLocationId { get; set; } = "";
|
||||
}
|
||||
public sealed record MaterialTypeRequest(
|
||||
Guid? Id, long? Version, string Code, string Name, string Spec, string Unit, string Category, string BarcodePrefix,
|
||||
bool Enabled, bool IsLock, string Remark, string Extend) : CommonRequest(Id, Version, IsLock, Remark, Extend);
|
||||
|
||||
public sealed class ContainerMaterialHistory : WarehouseHistoryBase
|
||||
{
|
||||
public Guid? MaterialId { get; set; }
|
||||
public decimal QuantityDelta { get; set; }
|
||||
}
|
||||
public sealed record MaterialRequest(
|
||||
Guid? Id, long? Version, string Code, string Name, string TypeCode, string Barcode, string Spec, string Unit,
|
||||
string Category, string LifecycleStatus, bool Enabled, bool IsLock, string Remark, string Extend)
|
||||
: CommonRequest(Id, Version, IsLock, Remark, Extend);
|
||||
|
||||
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 sealed record ContainerLocationRequest(
|
||||
Guid? Id, long? Version, Guid ContainerId, string LocationType, string LocationId, string Status,
|
||||
DateTimeOffset? EnteredAt, string Source, string Reason, bool IsLock, string Remark, string Extend)
|
||||
: CommonRequest(Id, Version, IsLock, Remark, Extend);
|
||||
|
||||
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 sealed record BindMaterialRequest(
|
||||
Guid? Id, long? Version, Guid ContainerId, Guid MaterialId, string Source, string Reason,
|
||||
bool IsLock, string Remark, string Extend) : CommonRequest(Id, Version, IsLock, Remark, Extend);
|
||||
|
||||
public static class ContainerLocationTypes
|
||||
{
|
||||
public const string Storage = "Storage";
|
||||
public const string Car = "Car";
|
||||
public static readonly HashSet<string> All = new(StringComparer.OrdinalIgnoreCase) { Storage, Car };
|
||||
}
|
||||
|
||||
public static class ContainerLocationStatuses
|
||||
{
|
||||
public const string Active = "Active";
|
||||
public const string Locked = "Locked";
|
||||
public const string Exception = "Exception";
|
||||
public static readonly HashSet<string> All = new(StringComparer.OrdinalIgnoreCase) { Active, Locked, Exception };
|
||||
}
|
||||
|
||||
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> 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 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
|
||||
{
|
||||
public const string Storage = "Storage";
|
||||
public const string LineSide = "LineSide";
|
||||
public const string OfflinePoint = "OfflinePoint";
|
||||
public const string Buffer = "Buffer";
|
||||
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";
|
||||
public const string FinishedGoodsOffline = "FinishedGoodsOffline";
|
||||
public const string AutoTransfer = "AutoTransfer";
|
||||
public static readonly HashSet<string> All = new(StringComparer.OrdinalIgnoreCase)
|
||||
{ MaterialCall, FinishedGoodsOffline, AutoTransfer };
|
||||
}
|
||||
|
||||
public static class WmsTransportTaskStatuses
|
||||
{
|
||||
public const string Pending = "Pending";
|
||||
public const string Reserved = "Reserved";
|
||||
public const string Dispatched = "Dispatched";
|
||||
public const string InTransit = "InTransit";
|
||||
public const string Completed = "Completed";
|
||||
public const string Failed = "Failed";
|
||||
public const string Cancelled = "Cancelled";
|
||||
public static readonly HashSet<string> Active = new(StringComparer.OrdinalIgnoreCase)
|
||||
{ Pending, Reserved, Dispatched, InTransit };
|
||||
public static readonly HashSet<string> All = new(StringComparer.OrdinalIgnoreCase)
|
||||
{ Pending, Reserved, Dispatched, InTransit, Completed, Failed, Cancelled };
|
||||
}
|
||||
|
||||
public static class WmsReservationStatuses
|
||||
{
|
||||
public const string Active = "Active";
|
||||
public const string Released = "Released";
|
||||
public const string Expired = "Expired";
|
||||
}
|
||||
|
||||
public sealed class WmsTransportRule : EntityBase
|
||||
{
|
||||
[MaxLength(64)] public string Code { get; set; } = "";
|
||||
[MaxLength(128)] public string Name { get; set; } = "";
|
||||
[MaxLength(64)] public string TriggerType { get; set; } = WmsTransportTriggerTypes.MaterialCall;
|
||||
public bool Enabled { get; set; } = true;
|
||||
public int Priority { get; set; }
|
||||
public string SourceSelectorJson { get; set; } = "{}";
|
||||
public string TargetSelectorJson { get; set; } = "{}";
|
||||
public string TaskOptionsJson { get; set; } = "{}";
|
||||
}
|
||||
|
||||
public sealed class WmsTransportTask : EntityBase
|
||||
{
|
||||
[MaxLength(64)] public string BusinessType { get; set; } = "";
|
||||
public Guid? RuleId { get; set; }
|
||||
public Guid SourceStorageId { get; set; }
|
||||
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; } = "";
|
||||
[MaxLength(64)] public string DeliveryId { get; set; } = "";
|
||||
[MaxLength(32)] public string DispatchStatus { get; set; } = "";
|
||||
[MaxLength(500)] public string Reason { get; set; } = "";
|
||||
public string SnapshotJson { get; set; } = "{}";
|
||||
[MaxLength(1000)] public string ErrorMessage { get; set; } = "";
|
||||
public int TaskPriority { get; set; }
|
||||
}
|
||||
|
||||
public sealed class WmsTransportReservation : EntityBase
|
||||
{
|
||||
public Guid TaskId { get; set; }
|
||||
public Guid ContainerId { get; set; }
|
||||
public Guid SourceStorageId { get; set; }
|
||||
public Guid TargetStorageId { get; set; }
|
||||
[MaxLength(32)] public string Status { get; set; } = WmsReservationStatuses.Active;
|
||||
public DateTimeOffset? ExpiresAt { get; set; }
|
||||
}
|
||||
|
||||
public sealed class WmsTransportTaskHistory
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid TaskId { get; set; }
|
||||
[MaxLength(32)] public string FromStatus { get; set; } = "";
|
||||
[MaxLength(32)] public string ToStatus { get; set; } = "";
|
||||
[MaxLength(128)] public string Operator { get; set; } = "";
|
||||
public DateTimeOffset OperatedAt { get; set; }
|
||||
[MaxLength(500)] public string Reason { get; set; } = "";
|
||||
[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 = "默认仓库";
|
||||
}
|
||||
public sealed record ContainerMaterialRequest(
|
||||
Guid? Id, long? Version, Guid ContainerId, Guid MaterialId, decimal Quantity, string BatchNo, string SerialNo,
|
||||
string Status, DateTimeOffset? LoadedAt, DateTimeOffset? UnloadedAt, string Source, string Reason,
|
||||
bool IsLock, string Remark, string Extend) : CommonRequest(Id, Version, IsLock, Remark, Extend);
|
||||
|
||||
Reference in New Issue
Block a user