持久层重构为独立 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:
2026-07-27 14:26:04 +08:00
parent bdcd88608c
commit 51c3fc1994
57 changed files with 6663 additions and 1396 deletions
+48 -342
View File
@@ -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);