新增 WMS 搬运规则与任务管理
支持搬运规则维护、候选预览、任务生成、预占、下发、取消和完成,并完善仓储管理前端交互。
This commit is contained in:
@@ -18,6 +18,10 @@ public sealed class PlatformDbContext : DbContext
|
||||
public DbSet<ContainerMaterial> ContainerMaterials => Set<ContainerMaterial>();
|
||||
public DbSet<ContainerLocationHistory> ContainerLocationHistories => Set<ContainerLocationHistory>();
|
||||
public DbSet<ContainerMaterialHistory> ContainerMaterialHistories => Set<ContainerMaterialHistory>();
|
||||
public DbSet<WmsTransportRule> WmsTransportRules => Set<WmsTransportRule>();
|
||||
public DbSet<WmsTransportTask> WmsTransportTasks => Set<WmsTransportTask>();
|
||||
public DbSet<WmsTransportReservation> WmsTransportReservations => Set<WmsTransportReservation>();
|
||||
public DbSet<WmsTransportTaskHistory> WmsTransportTaskHistories => Set<WmsTransportTaskHistory>();
|
||||
public DbSet<SimpleField> SimpleFields => Set<SimpleField>();
|
||||
public DbSet<UserDashboardShortcut> UserDashboardShortcuts => Set<UserDashboardShortcut>();
|
||||
|
||||
@@ -29,6 +33,12 @@ public sealed class PlatformDbContext : DbContext
|
||||
var nullableGuid = new ValueConverter<Guid?, string?>(
|
||||
v => v.HasValue ? v.Value.ToString("D") : null,
|
||||
v => string.IsNullOrWhiteSpace(v) ? null : Guid.Parse(v));
|
||||
var dateTimeOffset = new ValueConverter<DateTimeOffset, string>(
|
||||
v => v.UtcDateTime.ToString("O"),
|
||||
v => DateTimeOffset.Parse(v));
|
||||
var nullableDateTimeOffset = new ValueConverter<DateTimeOffset?, string?>(
|
||||
v => v.HasValue ? v.Value.UtcDateTime.ToString("O") : null,
|
||||
v => string.IsNullOrWhiteSpace(v) ? null : DateTimeOffset.Parse(v));
|
||||
|
||||
foreach (var entity in modelBuilder.Model.GetEntityTypes())
|
||||
{
|
||||
@@ -36,6 +46,10 @@ public sealed class PlatformDbContext : DbContext
|
||||
modelBuilder.Entity(entity.ClrType).Property(p.Name).HasConversion(guid).HasMaxLength(36);
|
||||
foreach (var p in entity.ClrType.GetProperties().Where(p => p.PropertyType == typeof(Guid?)))
|
||||
modelBuilder.Entity(entity.ClrType).Property(p.Name).HasConversion(nullableGuid).HasMaxLength(36);
|
||||
foreach (var p in entity.ClrType.GetProperties().Where(p => p.PropertyType == typeof(DateTimeOffset)))
|
||||
modelBuilder.Entity(entity.ClrType).Property(p.Name).HasConversion(dateTimeOffset).HasMaxLength(40);
|
||||
foreach (var p in entity.ClrType.GetProperties().Where(p => p.PropertyType == typeof(DateTimeOffset?)))
|
||||
modelBuilder.Entity(entity.ClrType).Property(p.Name).HasConversion(nullableDateTimeOffset).HasMaxLength(40);
|
||||
}
|
||||
|
||||
ConfigureEntityBase<WarehouseArea>(modelBuilder, "wms_areas");
|
||||
@@ -44,9 +58,13 @@ public sealed class PlatformDbContext : DbContext
|
||||
ConfigureEntityBase<Material>(modelBuilder, "wms_materials");
|
||||
ConfigureEntityBase<ContainerLocation>(modelBuilder, "wms_container_locations");
|
||||
ConfigureEntityBase<ContainerMaterial>(modelBuilder, "wms_container_materials");
|
||||
ConfigureEntityBase<WmsTransportRule>(modelBuilder, "wms_transport_rules");
|
||||
ConfigureEntityBase<WmsTransportTask>(modelBuilder, "wms_transport_tasks");
|
||||
ConfigureEntityBase<WmsTransportReservation>(modelBuilder, "wms_transport_reservations");
|
||||
|
||||
ConfigureHistory<ContainerLocationHistory>(modelBuilder, "wms_container_location_history");
|
||||
ConfigureHistory<ContainerMaterialHistory>(modelBuilder, "wms_container_material_history");
|
||||
ConfigureTransportTaskHistory(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<WarehouseArea>().HasIndex(x => x.Code).IsUnique();
|
||||
modelBuilder.Entity<Storage>().HasIndex(x => x.Code).IsUnique();
|
||||
@@ -57,6 +75,15 @@ public sealed class PlatformDbContext : DbContext
|
||||
modelBuilder.Entity<ContainerLocation>().HasIndex(x => new { x.LocationType, x.LocationId });
|
||||
modelBuilder.Entity<ContainerMaterial>().HasIndex(x => new { x.ContainerId, x.MaterialId, x.BatchNo, x.SerialNo }).IsUnique();
|
||||
|
||||
modelBuilder.Entity<WmsTransportRule>().HasIndex(x => x.Code).IsUnique();
|
||||
modelBuilder.Entity<WmsTransportRule>().HasIndex(x => new { x.TriggerType, x.Enabled, x.Priority });
|
||||
modelBuilder.Entity<WmsTransportTask>().HasIndex(x => x.Status);
|
||||
modelBuilder.Entity<WmsTransportTask>().HasIndex(x => x.ContainerId);
|
||||
modelBuilder.Entity<WmsTransportTask>().HasIndex(x => x.TargetStorageId);
|
||||
modelBuilder.Entity<WmsTransportReservation>().HasIndex(x => new { x.ContainerId, x.Status });
|
||||
modelBuilder.Entity<WmsTransportReservation>().HasIndex(x => new { x.TargetStorageId, x.Status });
|
||||
modelBuilder.Entity<WmsTransportTask>().Property(x => x.Quantity).HasPrecision(18, 4);
|
||||
|
||||
modelBuilder.Entity<ContainerMaterial>().Property(x => x.Quantity).HasPrecision(18, 4);
|
||||
modelBuilder.Entity<ContainerMaterialHistory>().Property(x => x.QuantityDelta).HasPrecision(18, 4);
|
||||
|
||||
@@ -165,4 +192,19 @@ public sealed class PlatformDbContext : DbContext
|
||||
e.HasIndex(x => x.OperatedAt);
|
||||
e.HasIndex(x => x.EventType);
|
||||
}
|
||||
|
||||
private static void ConfigureTransportTaskHistory(ModelBuilder modelBuilder)
|
||||
{
|
||||
var e = modelBuilder.Entity<WmsTransportTaskHistory>();
|
||||
e.ToTable("wms_transport_task_history");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.FromStatus).HasMaxLength(32);
|
||||
e.Property(x => x.ToStatus).HasMaxLength(32);
|
||||
e.Property(x => x.Operator).HasMaxLength(128);
|
||||
e.Property(x => x.Reason).HasMaxLength(500);
|
||||
e.Property(x => x.ErrorMessage).HasMaxLength(1000);
|
||||
e.Property(x => x.SnapshotJson).HasColumnType("text");
|
||||
e.HasIndex(x => x.TaskId);
|
||||
e.HasIndex(x => x.OperatedAt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,10 @@ public static class PlatformPersistence
|
||||
|
||||
services.AddScoped<WmsReferenceValidator>();
|
||||
services.AddScoped<WmsService>();
|
||||
services.AddScoped<WmsTransportRuleService>();
|
||||
services.AddScoped<WmsTransportPlanner>();
|
||||
services.AddScoped<WmsTransportTaskService>();
|
||||
services.AddScoped<IWmsDispatchAdapter, NoopWmsDispatchAdapter>();
|
||||
services.AddScoped<SimpleFieldService>();
|
||||
services.AddScoped<MiGu.Server.Dashboard.DashboardShortcutService>();
|
||||
return services;
|
||||
@@ -53,6 +57,7 @@ public static class PlatformPersistence
|
||||
// EnsureCreated 只在「库文件不存在」时建表;已有 platform.db 时新增实体不会自动补表。
|
||||
await EnsureSimpleFieldsTableAsync(db);
|
||||
await EnsureUserDashboardShortcutsTableAsync(db);
|
||||
await EnsureWmsTransportSchemaAsync(db);
|
||||
}
|
||||
|
||||
/// <summary>为已存在的数据库补建 simple_fields 表(幂等)。</summary>
|
||||
@@ -101,6 +106,134 @@ public static class PlatformPersistence
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>为已存在的数据库补建 WMS 搬运规则/任务相关表及库位扩展列(幂等)。</summary>
|
||||
private static async Task EnsureWmsTransportSchemaAsync(PlatformDbContext db)
|
||||
{
|
||||
if (!db.Database.IsSqlite()) return;
|
||||
|
||||
await EnsureSqliteColumnAsync(db, "wms_storages", "Status", "TEXT NOT NULL DEFAULT 'Available'");
|
||||
await EnsureSqliteColumnAsync(db, "wms_storages", "Usage", "TEXT NOT NULL DEFAULT ''");
|
||||
await EnsureSqliteColumnAsync(db, "wms_storages", "Priority", "INTEGER NOT NULL DEFAULT 0");
|
||||
await EnsureSqliteColumnAsync(db, "wms_storages", "ZoneCode", "TEXT NOT NULL DEFAULT ''");
|
||||
await EnsureSqliteColumnAsync(db, "wms_storages", "AllowInbound", "INTEGER NOT NULL DEFAULT 1");
|
||||
await EnsureSqliteColumnAsync(db, "wms_storages", "AllowOutbound", "INTEGER NOT NULL DEFAULT 1");
|
||||
|
||||
await db.Database.ExecuteSqlRawAsync("""
|
||||
CREATE TABLE IF NOT EXISTS wms_transport_rules (
|
||||
Id TEXT NOT NULL CONSTRAINT PK_wms_transport_rules PRIMARY KEY,
|
||||
CreatedAt TEXT NOT NULL,
|
||||
UpdatedAt TEXT NOT NULL,
|
||||
IsDeleted INTEGER NOT NULL,
|
||||
DeletedAt TEXT,
|
||||
DeletedBy TEXT,
|
||||
Version INTEGER NOT NULL,
|
||||
IsLock INTEGER NOT NULL,
|
||||
CreatedBy TEXT NOT NULL,
|
||||
UpdatedBy TEXT NOT NULL,
|
||||
Remark TEXT NOT NULL,
|
||||
Extend TEXT NOT NULL,
|
||||
Code TEXT NOT NULL,
|
||||
Name TEXT NOT NULL,
|
||||
TriggerType TEXT NOT NULL,
|
||||
Enabled INTEGER NOT NULL,
|
||||
Priority INTEGER NOT NULL,
|
||||
SourceSelectorJson TEXT NOT NULL,
|
||||
TargetSelectorJson TEXT NOT NULL,
|
||||
TaskOptionsJson TEXT NOT NULL
|
||||
);
|
||||
""");
|
||||
await db.Database.ExecuteSqlRawAsync("""
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS IX_wms_transport_rules_Code ON wms_transport_rules (Code);
|
||||
""");
|
||||
|
||||
await db.Database.ExecuteSqlRawAsync("""
|
||||
CREATE TABLE IF NOT EXISTS wms_transport_tasks (
|
||||
Id TEXT NOT NULL CONSTRAINT PK_wms_transport_tasks PRIMARY KEY,
|
||||
CreatedAt TEXT NOT NULL,
|
||||
UpdatedAt TEXT NOT NULL,
|
||||
IsDeleted INTEGER NOT NULL,
|
||||
DeletedAt TEXT,
|
||||
DeletedBy TEXT,
|
||||
Version INTEGER NOT NULL,
|
||||
IsLock INTEGER NOT NULL,
|
||||
CreatedBy TEXT NOT NULL,
|
||||
UpdatedBy TEXT NOT NULL,
|
||||
Remark TEXT NOT NULL,
|
||||
Extend TEXT NOT NULL,
|
||||
BusinessType TEXT NOT NULL,
|
||||
RuleId TEXT,
|
||||
SourceStorageId TEXT NOT NULL,
|
||||
TargetStorageId TEXT NOT NULL,
|
||||
ContainerId TEXT NOT NULL,
|
||||
MaterialId TEXT,
|
||||
Quantity REAL,
|
||||
Status TEXT NOT NULL,
|
||||
DispatchMissionId TEXT NOT NULL DEFAULT '',
|
||||
DeliveryId TEXT NOT NULL DEFAULT '',
|
||||
DispatchStatus TEXT NOT NULL DEFAULT '',
|
||||
Reason TEXT NOT NULL DEFAULT '',
|
||||
SnapshotJson TEXT NOT NULL,
|
||||
ErrorMessage TEXT NOT NULL DEFAULT '',
|
||||
TaskPriority INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
""");
|
||||
|
||||
await db.Database.ExecuteSqlRawAsync("""
|
||||
CREATE TABLE IF NOT EXISTS wms_transport_reservations (
|
||||
Id TEXT NOT NULL CONSTRAINT PK_wms_transport_reservations PRIMARY KEY,
|
||||
CreatedAt TEXT NOT NULL,
|
||||
UpdatedAt TEXT NOT NULL,
|
||||
IsDeleted INTEGER NOT NULL,
|
||||
DeletedAt TEXT,
|
||||
DeletedBy TEXT,
|
||||
Version INTEGER NOT NULL,
|
||||
IsLock INTEGER NOT NULL,
|
||||
CreatedBy TEXT NOT NULL,
|
||||
UpdatedBy TEXT NOT NULL,
|
||||
Remark TEXT NOT NULL,
|
||||
Extend TEXT NOT NULL,
|
||||
TaskId TEXT NOT NULL,
|
||||
ContainerId TEXT NOT NULL,
|
||||
SourceStorageId TEXT NOT NULL,
|
||||
TargetStorageId TEXT NOT NULL,
|
||||
Status TEXT NOT NULL,
|
||||
ExpiresAt TEXT
|
||||
);
|
||||
""");
|
||||
|
||||
await db.Database.ExecuteSqlRawAsync("""
|
||||
CREATE TABLE IF NOT EXISTS wms_transport_task_history (
|
||||
Id TEXT NOT NULL CONSTRAINT PK_wms_transport_task_history PRIMARY KEY,
|
||||
TaskId TEXT NOT NULL,
|
||||
FromStatus TEXT NOT NULL,
|
||||
ToStatus TEXT NOT NULL,
|
||||
Operator TEXT NOT NULL,
|
||||
OperatedAt TEXT NOT NULL,
|
||||
Reason TEXT NOT NULL,
|
||||
ErrorMessage TEXT NOT NULL,
|
||||
SnapshotJson TEXT NOT NULL
|
||||
);
|
||||
""");
|
||||
}
|
||||
|
||||
private static async Task EnsureSqliteColumnAsync(PlatformDbContext db, string table, string column, string definition)
|
||||
{
|
||||
var conn = db.Database.GetDbConnection();
|
||||
if (conn.State != System.Data.ConnectionState.Open)
|
||||
await conn.OpenAsync();
|
||||
await using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = $"PRAGMA table_info({table});";
|
||||
await using var reader = await cmd.ExecuteReaderAsync();
|
||||
while (await reader.ReadAsync())
|
||||
{
|
||||
var name = reader.GetString(1);
|
||||
if (string.Equals(name, column, StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
}
|
||||
|
||||
await db.Database.ExecuteSqlRawAsync($"ALTER TABLE {table} ADD COLUMN {column} {definition};");
|
||||
}
|
||||
|
||||
/// <summary>为已存在的数据库补建 user_dashboard_shortcuts 表(幂等)。</summary>
|
||||
private static async Task EnsureUserDashboardShortcutsTableAsync(PlatformDbContext db)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user