This commit is contained in:
code@server 2026-01-14 22:53:08 +08:00
commit 1d74e8a589
16 changed files with 2369 additions and 8 deletions

View File

@ -280,7 +280,7 @@ public class AllocationsController : BaseApiController
quotaValid = isValid,
targetUnitsExist = targetUnitsExist,
isValid = isValid && targetUnitsExist,
message = !isValid ? "分配配额总和超过总配额指标" :
message = !isValid ? "有配额未分配,无法提交" :
!targetUnitsExist ? "目标单位不存在" : "验证通过"
});
}

View File

@ -0,0 +1,142 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MilitaryTrainingManagement.Data;
using MilitaryTrainingManagement.Models.DTOs;
using MilitaryTrainingManagement.Models.Entities;
namespace MilitaryTrainingManagement.Controllers;
/// <summary>
/// 物资类别管理控制器
/// </summary>
[Authorize]
public class MaterialCategoriesController : BaseApiController
{
private readonly ApplicationDbContext _context;
public MaterialCategoriesController(ApplicationDbContext context)
{
_context = context;
}
/// <summary>
/// 获取所有物资类别
/// </summary>
[HttpGet]
public async Task<IActionResult> GetAll([FromQuery] bool? activeOnly = true)
{
var query = _context.MaterialCategories.AsQueryable();
if (activeOnly == true)
{
query = query.Where(c => c.IsActive);
}
var categories = await query
.OrderBy(c => c.SortOrder)
.ThenBy(c => c.Name)
.ToListAsync();
return Ok(categories);
}
/// <summary>
/// 根据ID获取物资类别
/// </summary>
[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
var category = await _context.MaterialCategories.FindAsync(id);
if (category == null)
{
return NotFound(new { message = "物资类别不存在" });
}
return Ok(category);
}
/// <summary>
/// 创建物资类别
/// </summary>
[HttpPost]
public async Task<IActionResult> Create([FromBody] CreateMaterialCategoryRequest request)
{
// 检查名称是否已存在
var exists = await _context.MaterialCategories
.AnyAsync(c => c.Name == request.Name);
if (exists)
{
return BadRequest(new { message = "物资类别名称已存在" });
}
var category = new MaterialCategory
{
Name = request.Name,
Description = request.Description,
SortOrder = request.SortOrder,
IsActive = true,
CreatedAt = DateTime.UtcNow
};
_context.MaterialCategories.Add(category);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetById), new { id = category.Id }, category);
}
/// <summary>
/// 更新物资类别
/// </summary>
[HttpPut("{id}")]
public async Task<IActionResult> Update(int id, [FromBody] UpdateMaterialCategoryRequest request)
{
var category = await _context.MaterialCategories.FindAsync(id);
if (category == null)
{
return NotFound(new { message = "物资类别不存在" });
}
// 检查名称是否与其他类别重复
var exists = await _context.MaterialCategories
.AnyAsync(c => c.Name == request.Name && c.Id != id);
if (exists)
{
return BadRequest(new { message = "物资类别名称已存在" });
}
category.Name = request.Name;
category.Description = request.Description;
category.SortOrder = request.SortOrder;
category.IsActive = request.IsActive;
await _context.SaveChangesAsync();
return Ok(category);
}
/// <summary>
/// 删除物资类别
/// </summary>
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
var category = await _context.MaterialCategories.FindAsync(id);
if (category == null)
{
return NotFound(new { message = "物资类别不存在" });
}
// 检查是否有配额使用此类别
var hasAllocations = await _context.MaterialAllocations
.AnyAsync(a => a.Category == category.Name);
if (hasAllocations)
{
return BadRequest(new { message = "该类别已被使用,无法删除" });
}
_context.MaterialCategories.Remove(category);
await _context.SaveChangesAsync();
return Ok(new { message = "删除成功" });
}
}

View File

@ -17,6 +17,7 @@ public class ApplicationDbContext : DbContext
public DbSet<UserAccount> UserAccounts => Set<UserAccount>();
public DbSet<MaterialAllocation> MaterialAllocations => Set<MaterialAllocation>();
public DbSet<AllocationDistribution> AllocationDistributions => Set<AllocationDistribution>();
public DbSet<MaterialCategory> MaterialCategories => Set<MaterialCategory>();
public DbSet<Personnel> Personnel => Set<Personnel>();
public DbSet<PersonnelApprovalHistory> PersonnelApprovalHistories => Set<PersonnelApprovalHistory>();
public DbSet<ApprovalRequest> ApprovalRequests => Set<ApprovalRequest>();
@ -65,6 +66,15 @@ public class ApplicationDbContext : DbContext
.OnDelete(DeleteBehavior.Restrict);
});
// MaterialCategory 配置
modelBuilder.Entity<MaterialCategory>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired().HasMaxLength(50);
entity.HasIndex(e => e.Name).IsUnique();
entity.Property(e => e.Description).HasMaxLength(200);
});
// AllocationDistribution 配置
modelBuilder.Entity<AllocationDistribution>(entity =>
{

View File

@ -0,0 +1,672 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using MilitaryTrainingManagement.Data;
#nullable disable
namespace MilitaryTrainingManagement.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20260114141545_AddMaterialCategory")]
partial class AddMaterialCategory
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.AllocationDistribution", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<decimal?>("ActualCompletion")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<int>("AllocationId")
.HasColumnType("int");
b.Property<DateTime?>("ApprovedAt")
.HasColumnType("datetime2");
b.Property<int?>("ApprovedByUserId")
.HasColumnType("int");
b.Property<bool>("IsApproved")
.HasColumnType("bit");
b.Property<DateTime?>("ReportedAt")
.HasColumnType("datetime2");
b.Property<int?>("ReportedByUserId")
.HasColumnType("int");
b.Property<int>("TargetUnitId")
.HasColumnType("int");
b.Property<decimal>("UnitQuota")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.HasKey("Id");
b.HasIndex("AllocationId");
b.HasIndex("ApprovedByUserId");
b.HasIndex("ReportedByUserId");
b.HasIndex("TargetUnitId");
b.ToTable("AllocationDistributions");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.ApprovalRequest", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("OriginalData")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Reason")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<DateTime>("RequestedAt")
.HasColumnType("datetime2");
b.Property<int>("RequestedByUnitId")
.HasColumnType("int");
b.Property<int>("RequestedByUserId")
.HasColumnType("int");
b.Property<string>("RequestedChanges")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("ReviewComments")
.HasColumnType("nvarchar(max)");
b.Property<DateTime?>("ReviewedAt")
.HasColumnType("datetime2");
b.Property<int?>("ReviewedByUserId")
.HasColumnType("int");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<int>("TargetEntityId")
.HasColumnType("int");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("RequestedByUnitId");
b.HasIndex("RequestedByUserId");
b.HasIndex("ReviewedByUserId");
b.ToTable("ApprovalRequests");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.AuditLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Action")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("ChangedFields")
.HasColumnType("nvarchar(max)");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<int>("EntityId")
.HasColumnType("int");
b.Property<string>("EntityType")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("ErrorMessage")
.HasMaxLength(2000)
.HasColumnType("nvarchar(2000)");
b.Property<string>("IpAddress")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<bool>("IsSuccess")
.HasColumnType("bit");
b.Property<string>("NewValues")
.HasColumnType("nvarchar(max)");
b.Property<string>("OldValues")
.HasColumnType("nvarchar(max)");
b.Property<int?>("OrganizationalUnitId")
.HasColumnType("int");
b.Property<string>("RequestPath")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<DateTime>("Timestamp")
.HasColumnType("datetime2");
b.Property<string>("UserAgent")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<int?>("UserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("Action");
b.HasIndex("EntityId");
b.HasIndex("EntityType");
b.HasIndex("OrganizationalUnitId");
b.HasIndex("Timestamp");
b.HasIndex("UserId");
b.ToTable("AuditLogs");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.MaterialAllocation", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Category")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<int>("CreatedByUnitId")
.HasColumnType("int");
b.Property<string>("MaterialName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<decimal>("TotalQuota")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<string>("Unit")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.HasKey("Id");
b.HasIndex("CreatedByUnitId");
b.ToTable("MaterialAllocations");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.MaterialCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Description")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<int>("SortOrder")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("Name")
.IsUnique();
b.ToTable("MaterialCategories");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<int>("Level")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<int?>("ParentId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("OrganizationalUnits");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.Personnel", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Achievements")
.HasColumnType("nvarchar(max)");
b.Property<int>("Age")
.HasColumnType("int");
b.Property<DateTime?>("ApprovedAt")
.HasColumnType("datetime2");
b.Property<int?>("ApprovedByUnitId")
.HasColumnType("int");
b.Property<int?>("ApprovedLevel")
.HasColumnType("int");
b.Property<string>("ContactInfo")
.HasColumnType("nvarchar(max)");
b.Property<string>("EducationLevel")
.HasColumnType("nvarchar(max)");
b.Property<string>("Gender")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("nvarchar(10)");
b.Property<decimal?>("Height")
.HasPrecision(5, 2)
.HasColumnType("decimal(5,2)");
b.Property<string>("Hometown")
.HasColumnType("nvarchar(max)");
b.Property<string>("IdNumber")
.IsRequired()
.HasMaxLength(18)
.HasColumnType("nvarchar(18)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("PhotoPath")
.HasColumnType("nvarchar(max)");
b.Property<string>("Position")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("ProfessionalTitle")
.HasColumnType("nvarchar(max)");
b.Property<string>("Rank")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<DateTime>("SubmittedAt")
.HasColumnType("datetime2");
b.Property<int>("SubmittedByUnitId")
.HasColumnType("int");
b.Property<string>("SupportingDocuments")
.HasColumnType("nvarchar(max)");
b.Property<string>("TrainingParticipation")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("ApprovedByUnitId");
b.HasIndex("IdNumber")
.IsUnique();
b.HasIndex("SubmittedByUnitId");
b.ToTable("Personnel");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.PersonnelApprovalHistory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("Action")
.HasColumnType("int");
b.Property<string>("Comments")
.HasColumnType("nvarchar(max)");
b.Property<int?>("NewLevel")
.HasColumnType("int");
b.Property<int>("NewStatus")
.HasColumnType("int");
b.Property<int>("PersonnelId")
.HasColumnType("int");
b.Property<int?>("PreviousLevel")
.HasColumnType("int");
b.Property<int?>("PreviousStatus")
.HasColumnType("int");
b.Property<DateTime>("ReviewedAt")
.HasColumnType("datetime2");
b.Property<int?>("ReviewedByUnitId")
.HasColumnType("int");
b.Property<int>("ReviewedByUserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("PersonnelId");
b.HasIndex("ReviewedByUnitId");
b.HasIndex("ReviewedByUserId");
b.ToTable("PersonnelApprovalHistories");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.UserAccount", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("DisplayName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<DateTime?>("LastLoginAt")
.HasColumnType("datetime2");
b.Property<int>("OrganizationalUnitId")
.HasColumnType("int");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Username")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.HasKey("Id");
b.HasIndex("OrganizationalUnitId");
b.HasIndex("Username")
.IsUnique();
b.ToTable("UserAccounts");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.AllocationDistribution", b =>
{
b.HasOne("MilitaryTrainingManagement.Models.Entities.MaterialAllocation", "Allocation")
.WithMany("Distributions")
.HasForeignKey("AllocationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("MilitaryTrainingManagement.Models.Entities.UserAccount", "ApprovedByUser")
.WithMany()
.HasForeignKey("ApprovedByUserId")
.OnDelete(DeleteBehavior.NoAction);
b.HasOne("MilitaryTrainingManagement.Models.Entities.UserAccount", "ReportedByUser")
.WithMany()
.HasForeignKey("ReportedByUserId")
.OnDelete(DeleteBehavior.NoAction);
b.HasOne("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", "TargetUnit")
.WithMany()
.HasForeignKey("TargetUnitId")
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
b.Navigation("Allocation");
b.Navigation("ApprovedByUser");
b.Navigation("ReportedByUser");
b.Navigation("TargetUnit");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.ApprovalRequest", b =>
{
b.HasOne("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", "RequestedByUnit")
.WithMany()
.HasForeignKey("RequestedByUnitId")
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
b.HasOne("MilitaryTrainingManagement.Models.Entities.UserAccount", "RequestedByUser")
.WithMany()
.HasForeignKey("RequestedByUserId")
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
b.HasOne("MilitaryTrainingManagement.Models.Entities.UserAccount", "ReviewedByUser")
.WithMany()
.HasForeignKey("ReviewedByUserId")
.OnDelete(DeleteBehavior.NoAction);
b.Navigation("RequestedByUnit");
b.Navigation("RequestedByUser");
b.Navigation("ReviewedByUser");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.AuditLog", b =>
{
b.HasOne("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", "OrganizationalUnit")
.WithMany()
.HasForeignKey("OrganizationalUnitId")
.OnDelete(DeleteBehavior.NoAction);
b.HasOne("MilitaryTrainingManagement.Models.Entities.UserAccount", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.NoAction);
b.Navigation("OrganizationalUnit");
b.Navigation("User");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.MaterialAllocation", b =>
{
b.HasOne("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", "CreatedByUnit")
.WithMany()
.HasForeignKey("CreatedByUnitId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("CreatedByUnit");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", b =>
{
b.HasOne("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", "Parent")
.WithMany("Children")
.HasForeignKey("ParentId")
.OnDelete(DeleteBehavior.Restrict);
b.Navigation("Parent");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.Personnel", b =>
{
b.HasOne("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", "ApprovedByUnit")
.WithMany()
.HasForeignKey("ApprovedByUnitId")
.OnDelete(DeleteBehavior.NoAction);
b.HasOne("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", "SubmittedByUnit")
.WithMany()
.HasForeignKey("SubmittedByUnitId")
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
b.Navigation("ApprovedByUnit");
b.Navigation("SubmittedByUnit");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.PersonnelApprovalHistory", b =>
{
b.HasOne("MilitaryTrainingManagement.Models.Entities.Personnel", "Personnel")
.WithMany()
.HasForeignKey("PersonnelId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", "ReviewedByUnit")
.WithMany()
.HasForeignKey("ReviewedByUnitId")
.OnDelete(DeleteBehavior.NoAction);
b.HasOne("MilitaryTrainingManagement.Models.Entities.UserAccount", "ReviewedByUser")
.WithMany()
.HasForeignKey("ReviewedByUserId")
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
b.Navigation("Personnel");
b.Navigation("ReviewedByUnit");
b.Navigation("ReviewedByUser");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.UserAccount", b =>
{
b.HasOne("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", "OrganizationalUnit")
.WithMany("Accounts")
.HasForeignKey("OrganizationalUnitId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("OrganizationalUnit");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.MaterialAllocation", b =>
{
b.Navigation("Distributions");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", b =>
{
b.Navigation("Accounts");
b.Navigation("Children");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,456 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace MilitaryTrainingManagement.Migrations
{
/// <inheritdoc />
public partial class AddMaterialCategory : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "MaterialCategories",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
Description = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
IsActive = table.Column<bool>(type: "bit", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
SortOrder = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_MaterialCategories", x => x.Id);
});
migrationBuilder.CreateTable(
name: "OrganizationalUnits",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
Level = table.Column<int>(type: "int", nullable: false),
ParentId = table.Column<int>(type: "int", nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_OrganizationalUnits", x => x.Id);
table.ForeignKey(
name: "FK_OrganizationalUnits_OrganizationalUnits_ParentId",
column: x => x.ParentId,
principalTable: "OrganizationalUnits",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "MaterialAllocations",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Category = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
MaterialName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Unit = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
TotalQuota = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
CreatedByUnitId = table.Column<int>(type: "int", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_MaterialAllocations", x => x.Id);
table.ForeignKey(
name: "FK_MaterialAllocations_OrganizationalUnits_CreatedByUnitId",
column: x => x.CreatedByUnitId,
principalTable: "OrganizationalUnits",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "Personnel",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
PhotoPath = table.Column<string>(type: "nvarchar(max)", nullable: true),
Position = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
Rank = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
Gender = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false),
IdNumber = table.Column<string>(type: "nvarchar(18)", maxLength: 18, nullable: false),
ProfessionalTitle = table.Column<string>(type: "nvarchar(max)", nullable: true),
EducationLevel = table.Column<string>(type: "nvarchar(max)", nullable: true),
Age = table.Column<int>(type: "int", nullable: false),
Height = table.Column<decimal>(type: "decimal(5,2)", precision: 5, scale: 2, nullable: true),
ContactInfo = table.Column<string>(type: "nvarchar(max)", nullable: true),
Hometown = table.Column<string>(type: "nvarchar(max)", nullable: true),
TrainingParticipation = table.Column<string>(type: "nvarchar(max)", nullable: true),
Achievements = table.Column<string>(type: "nvarchar(max)", nullable: true),
SupportingDocuments = table.Column<string>(type: "nvarchar(max)", nullable: true),
SubmittedByUnitId = table.Column<int>(type: "int", nullable: false),
ApprovedLevel = table.Column<int>(type: "int", nullable: true),
ApprovedByUnitId = table.Column<int>(type: "int", nullable: true),
Status = table.Column<int>(type: "int", nullable: false),
SubmittedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
ApprovedAt = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Personnel", x => x.Id);
table.ForeignKey(
name: "FK_Personnel_OrganizationalUnits_ApprovedByUnitId",
column: x => x.ApprovedByUnitId,
principalTable: "OrganizationalUnits",
principalColumn: "Id");
table.ForeignKey(
name: "FK_Personnel_OrganizationalUnits_SubmittedByUnitId",
column: x => x.SubmittedByUnitId,
principalTable: "OrganizationalUnits",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "UserAccounts",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Username = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
PasswordHash = table.Column<string>(type: "nvarchar(max)", nullable: false),
DisplayName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
OrganizationalUnitId = table.Column<int>(type: "int", nullable: false),
IsActive = table.Column<bool>(type: "bit", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
LastLoginAt = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_UserAccounts", x => x.Id);
table.ForeignKey(
name: "FK_UserAccounts_OrganizationalUnits_OrganizationalUnitId",
column: x => x.OrganizationalUnitId,
principalTable: "OrganizationalUnits",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "AllocationDistributions",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
AllocationId = table.Column<int>(type: "int", nullable: false),
TargetUnitId = table.Column<int>(type: "int", nullable: false),
UnitQuota = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
ActualCompletion = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: true),
ReportedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
ReportedByUserId = table.Column<int>(type: "int", nullable: true),
IsApproved = table.Column<bool>(type: "bit", nullable: false),
ApprovedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
ApprovedByUserId = table.Column<int>(type: "int", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AllocationDistributions", x => x.Id);
table.ForeignKey(
name: "FK_AllocationDistributions_MaterialAllocations_AllocationId",
column: x => x.AllocationId,
principalTable: "MaterialAllocations",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_AllocationDistributions_OrganizationalUnits_TargetUnitId",
column: x => x.TargetUnitId,
principalTable: "OrganizationalUnits",
principalColumn: "Id");
table.ForeignKey(
name: "FK_AllocationDistributions_UserAccounts_ApprovedByUserId",
column: x => x.ApprovedByUserId,
principalTable: "UserAccounts",
principalColumn: "Id");
table.ForeignKey(
name: "FK_AllocationDistributions_UserAccounts_ReportedByUserId",
column: x => x.ReportedByUserId,
principalTable: "UserAccounts",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "ApprovalRequests",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Type = table.Column<int>(type: "int", nullable: false),
TargetEntityId = table.Column<int>(type: "int", nullable: false),
RequestedByUserId = table.Column<int>(type: "int", nullable: false),
RequestedByUnitId = table.Column<int>(type: "int", nullable: false),
Reason = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
OriginalData = table.Column<string>(type: "nvarchar(max)", nullable: false),
RequestedChanges = table.Column<string>(type: "nvarchar(max)", nullable: false),
Status = table.Column<int>(type: "int", nullable: false),
ReviewedByUserId = table.Column<int>(type: "int", nullable: true),
ReviewComments = table.Column<string>(type: "nvarchar(max)", nullable: true),
RequestedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
ReviewedAt = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ApprovalRequests", x => x.Id);
table.ForeignKey(
name: "FK_ApprovalRequests_OrganizationalUnits_RequestedByUnitId",
column: x => x.RequestedByUnitId,
principalTable: "OrganizationalUnits",
principalColumn: "Id");
table.ForeignKey(
name: "FK_ApprovalRequests_UserAccounts_RequestedByUserId",
column: x => x.RequestedByUserId,
principalTable: "UserAccounts",
principalColumn: "Id");
table.ForeignKey(
name: "FK_ApprovalRequests_UserAccounts_ReviewedByUserId",
column: x => x.ReviewedByUserId,
principalTable: "UserAccounts",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "AuditLogs",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
EntityType = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
EntityId = table.Column<int>(type: "int", nullable: false),
Action = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
Description = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
OldValues = table.Column<string>(type: "nvarchar(max)", nullable: true),
NewValues = table.Column<string>(type: "nvarchar(max)", nullable: true),
ChangedFields = table.Column<string>(type: "nvarchar(max)", nullable: true),
UserId = table.Column<int>(type: "int", nullable: true),
OrganizationalUnitId = table.Column<int>(type: "int", nullable: true),
Timestamp = table.Column<DateTime>(type: "datetime2", nullable: false),
IpAddress = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
UserAgent = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
RequestPath = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
IsSuccess = table.Column<bool>(type: "bit", nullable: false),
ErrorMessage = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AuditLogs", x => x.Id);
table.ForeignKey(
name: "FK_AuditLogs_OrganizationalUnits_OrganizationalUnitId",
column: x => x.OrganizationalUnitId,
principalTable: "OrganizationalUnits",
principalColumn: "Id");
table.ForeignKey(
name: "FK_AuditLogs_UserAccounts_UserId",
column: x => x.UserId,
principalTable: "UserAccounts",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "PersonnelApprovalHistories",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
PersonnelId = table.Column<int>(type: "int", nullable: false),
Action = table.Column<int>(type: "int", nullable: false),
PreviousStatus = table.Column<int>(type: "int", nullable: true),
NewStatus = table.Column<int>(type: "int", nullable: false),
PreviousLevel = table.Column<int>(type: "int", nullable: true),
NewLevel = table.Column<int>(type: "int", nullable: true),
ReviewedByUserId = table.Column<int>(type: "int", nullable: false),
ReviewedByUnitId = table.Column<int>(type: "int", nullable: true),
Comments = table.Column<string>(type: "nvarchar(max)", nullable: true),
ReviewedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PersonnelApprovalHistories", x => x.Id);
table.ForeignKey(
name: "FK_PersonnelApprovalHistories_OrganizationalUnits_ReviewedByUnitId",
column: x => x.ReviewedByUnitId,
principalTable: "OrganizationalUnits",
principalColumn: "Id");
table.ForeignKey(
name: "FK_PersonnelApprovalHistories_Personnel_PersonnelId",
column: x => x.PersonnelId,
principalTable: "Personnel",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_PersonnelApprovalHistories_UserAccounts_ReviewedByUserId",
column: x => x.ReviewedByUserId,
principalTable: "UserAccounts",
principalColumn: "Id");
});
migrationBuilder.CreateIndex(
name: "IX_AllocationDistributions_AllocationId",
table: "AllocationDistributions",
column: "AllocationId");
migrationBuilder.CreateIndex(
name: "IX_AllocationDistributions_ApprovedByUserId",
table: "AllocationDistributions",
column: "ApprovedByUserId");
migrationBuilder.CreateIndex(
name: "IX_AllocationDistributions_ReportedByUserId",
table: "AllocationDistributions",
column: "ReportedByUserId");
migrationBuilder.CreateIndex(
name: "IX_AllocationDistributions_TargetUnitId",
table: "AllocationDistributions",
column: "TargetUnitId");
migrationBuilder.CreateIndex(
name: "IX_ApprovalRequests_RequestedByUnitId",
table: "ApprovalRequests",
column: "RequestedByUnitId");
migrationBuilder.CreateIndex(
name: "IX_ApprovalRequests_RequestedByUserId",
table: "ApprovalRequests",
column: "RequestedByUserId");
migrationBuilder.CreateIndex(
name: "IX_ApprovalRequests_ReviewedByUserId",
table: "ApprovalRequests",
column: "ReviewedByUserId");
migrationBuilder.CreateIndex(
name: "IX_AuditLogs_Action",
table: "AuditLogs",
column: "Action");
migrationBuilder.CreateIndex(
name: "IX_AuditLogs_EntityId",
table: "AuditLogs",
column: "EntityId");
migrationBuilder.CreateIndex(
name: "IX_AuditLogs_EntityType",
table: "AuditLogs",
column: "EntityType");
migrationBuilder.CreateIndex(
name: "IX_AuditLogs_OrganizationalUnitId",
table: "AuditLogs",
column: "OrganizationalUnitId");
migrationBuilder.CreateIndex(
name: "IX_AuditLogs_Timestamp",
table: "AuditLogs",
column: "Timestamp");
migrationBuilder.CreateIndex(
name: "IX_AuditLogs_UserId",
table: "AuditLogs",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_MaterialAllocations_CreatedByUnitId",
table: "MaterialAllocations",
column: "CreatedByUnitId");
migrationBuilder.CreateIndex(
name: "IX_MaterialCategories_Name",
table: "MaterialCategories",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_OrganizationalUnits_ParentId",
table: "OrganizationalUnits",
column: "ParentId");
migrationBuilder.CreateIndex(
name: "IX_Personnel_ApprovedByUnitId",
table: "Personnel",
column: "ApprovedByUnitId");
migrationBuilder.CreateIndex(
name: "IX_Personnel_IdNumber",
table: "Personnel",
column: "IdNumber",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Personnel_SubmittedByUnitId",
table: "Personnel",
column: "SubmittedByUnitId");
migrationBuilder.CreateIndex(
name: "IX_PersonnelApprovalHistories_PersonnelId",
table: "PersonnelApprovalHistories",
column: "PersonnelId");
migrationBuilder.CreateIndex(
name: "IX_PersonnelApprovalHistories_ReviewedByUnitId",
table: "PersonnelApprovalHistories",
column: "ReviewedByUnitId");
migrationBuilder.CreateIndex(
name: "IX_PersonnelApprovalHistories_ReviewedByUserId",
table: "PersonnelApprovalHistories",
column: "ReviewedByUserId");
migrationBuilder.CreateIndex(
name: "IX_UserAccounts_OrganizationalUnitId",
table: "UserAccounts",
column: "OrganizationalUnitId");
migrationBuilder.CreateIndex(
name: "IX_UserAccounts_Username",
table: "UserAccounts",
column: "Username",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AllocationDistributions");
migrationBuilder.DropTable(
name: "ApprovalRequests");
migrationBuilder.DropTable(
name: "AuditLogs");
migrationBuilder.DropTable(
name: "MaterialCategories");
migrationBuilder.DropTable(
name: "PersonnelApprovalHistories");
migrationBuilder.DropTable(
name: "MaterialAllocations");
migrationBuilder.DropTable(
name: "Personnel");
migrationBuilder.DropTable(
name: "UserAccounts");
migrationBuilder.DropTable(
name: "OrganizationalUnits");
}
}
}

View File

@ -0,0 +1,669 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using MilitaryTrainingManagement.Data;
#nullable disable
namespace MilitaryTrainingManagement.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.AllocationDistribution", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<decimal?>("ActualCompletion")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<int>("AllocationId")
.HasColumnType("int");
b.Property<DateTime?>("ApprovedAt")
.HasColumnType("datetime2");
b.Property<int?>("ApprovedByUserId")
.HasColumnType("int");
b.Property<bool>("IsApproved")
.HasColumnType("bit");
b.Property<DateTime?>("ReportedAt")
.HasColumnType("datetime2");
b.Property<int?>("ReportedByUserId")
.HasColumnType("int");
b.Property<int>("TargetUnitId")
.HasColumnType("int");
b.Property<decimal>("UnitQuota")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.HasKey("Id");
b.HasIndex("AllocationId");
b.HasIndex("ApprovedByUserId");
b.HasIndex("ReportedByUserId");
b.HasIndex("TargetUnitId");
b.ToTable("AllocationDistributions");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.ApprovalRequest", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("OriginalData")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Reason")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<DateTime>("RequestedAt")
.HasColumnType("datetime2");
b.Property<int>("RequestedByUnitId")
.HasColumnType("int");
b.Property<int>("RequestedByUserId")
.HasColumnType("int");
b.Property<string>("RequestedChanges")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("ReviewComments")
.HasColumnType("nvarchar(max)");
b.Property<DateTime?>("ReviewedAt")
.HasColumnType("datetime2");
b.Property<int?>("ReviewedByUserId")
.HasColumnType("int");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<int>("TargetEntityId")
.HasColumnType("int");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("RequestedByUnitId");
b.HasIndex("RequestedByUserId");
b.HasIndex("ReviewedByUserId");
b.ToTable("ApprovalRequests");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.AuditLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Action")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("ChangedFields")
.HasColumnType("nvarchar(max)");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<int>("EntityId")
.HasColumnType("int");
b.Property<string>("EntityType")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("ErrorMessage")
.HasMaxLength(2000)
.HasColumnType("nvarchar(2000)");
b.Property<string>("IpAddress")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<bool>("IsSuccess")
.HasColumnType("bit");
b.Property<string>("NewValues")
.HasColumnType("nvarchar(max)");
b.Property<string>("OldValues")
.HasColumnType("nvarchar(max)");
b.Property<int?>("OrganizationalUnitId")
.HasColumnType("int");
b.Property<string>("RequestPath")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<DateTime>("Timestamp")
.HasColumnType("datetime2");
b.Property<string>("UserAgent")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<int?>("UserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("Action");
b.HasIndex("EntityId");
b.HasIndex("EntityType");
b.HasIndex("OrganizationalUnitId");
b.HasIndex("Timestamp");
b.HasIndex("UserId");
b.ToTable("AuditLogs");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.MaterialAllocation", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Category")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<int>("CreatedByUnitId")
.HasColumnType("int");
b.Property<string>("MaterialName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<decimal>("TotalQuota")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<string>("Unit")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.HasKey("Id");
b.HasIndex("CreatedByUnitId");
b.ToTable("MaterialAllocations");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.MaterialCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Description")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<int>("SortOrder")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("Name")
.IsUnique();
b.ToTable("MaterialCategories");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<int>("Level")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<int?>("ParentId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("OrganizationalUnits");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.Personnel", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Achievements")
.HasColumnType("nvarchar(max)");
b.Property<int>("Age")
.HasColumnType("int");
b.Property<DateTime?>("ApprovedAt")
.HasColumnType("datetime2");
b.Property<int?>("ApprovedByUnitId")
.HasColumnType("int");
b.Property<int?>("ApprovedLevel")
.HasColumnType("int");
b.Property<string>("ContactInfo")
.HasColumnType("nvarchar(max)");
b.Property<string>("EducationLevel")
.HasColumnType("nvarchar(max)");
b.Property<string>("Gender")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("nvarchar(10)");
b.Property<decimal?>("Height")
.HasPrecision(5, 2)
.HasColumnType("decimal(5,2)");
b.Property<string>("Hometown")
.HasColumnType("nvarchar(max)");
b.Property<string>("IdNumber")
.IsRequired()
.HasMaxLength(18)
.HasColumnType("nvarchar(18)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("PhotoPath")
.HasColumnType("nvarchar(max)");
b.Property<string>("Position")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("ProfessionalTitle")
.HasColumnType("nvarchar(max)");
b.Property<string>("Rank")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<DateTime>("SubmittedAt")
.HasColumnType("datetime2");
b.Property<int>("SubmittedByUnitId")
.HasColumnType("int");
b.Property<string>("SupportingDocuments")
.HasColumnType("nvarchar(max)");
b.Property<string>("TrainingParticipation")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("ApprovedByUnitId");
b.HasIndex("IdNumber")
.IsUnique();
b.HasIndex("SubmittedByUnitId");
b.ToTable("Personnel");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.PersonnelApprovalHistory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("Action")
.HasColumnType("int");
b.Property<string>("Comments")
.HasColumnType("nvarchar(max)");
b.Property<int?>("NewLevel")
.HasColumnType("int");
b.Property<int>("NewStatus")
.HasColumnType("int");
b.Property<int>("PersonnelId")
.HasColumnType("int");
b.Property<int?>("PreviousLevel")
.HasColumnType("int");
b.Property<int?>("PreviousStatus")
.HasColumnType("int");
b.Property<DateTime>("ReviewedAt")
.HasColumnType("datetime2");
b.Property<int?>("ReviewedByUnitId")
.HasColumnType("int");
b.Property<int>("ReviewedByUserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("PersonnelId");
b.HasIndex("ReviewedByUnitId");
b.HasIndex("ReviewedByUserId");
b.ToTable("PersonnelApprovalHistories");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.UserAccount", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("DisplayName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<DateTime?>("LastLoginAt")
.HasColumnType("datetime2");
b.Property<int>("OrganizationalUnitId")
.HasColumnType("int");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Username")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.HasKey("Id");
b.HasIndex("OrganizationalUnitId");
b.HasIndex("Username")
.IsUnique();
b.ToTable("UserAccounts");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.AllocationDistribution", b =>
{
b.HasOne("MilitaryTrainingManagement.Models.Entities.MaterialAllocation", "Allocation")
.WithMany("Distributions")
.HasForeignKey("AllocationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("MilitaryTrainingManagement.Models.Entities.UserAccount", "ApprovedByUser")
.WithMany()
.HasForeignKey("ApprovedByUserId")
.OnDelete(DeleteBehavior.NoAction);
b.HasOne("MilitaryTrainingManagement.Models.Entities.UserAccount", "ReportedByUser")
.WithMany()
.HasForeignKey("ReportedByUserId")
.OnDelete(DeleteBehavior.NoAction);
b.HasOne("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", "TargetUnit")
.WithMany()
.HasForeignKey("TargetUnitId")
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
b.Navigation("Allocation");
b.Navigation("ApprovedByUser");
b.Navigation("ReportedByUser");
b.Navigation("TargetUnit");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.ApprovalRequest", b =>
{
b.HasOne("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", "RequestedByUnit")
.WithMany()
.HasForeignKey("RequestedByUnitId")
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
b.HasOne("MilitaryTrainingManagement.Models.Entities.UserAccount", "RequestedByUser")
.WithMany()
.HasForeignKey("RequestedByUserId")
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
b.HasOne("MilitaryTrainingManagement.Models.Entities.UserAccount", "ReviewedByUser")
.WithMany()
.HasForeignKey("ReviewedByUserId")
.OnDelete(DeleteBehavior.NoAction);
b.Navigation("RequestedByUnit");
b.Navigation("RequestedByUser");
b.Navigation("ReviewedByUser");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.AuditLog", b =>
{
b.HasOne("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", "OrganizationalUnit")
.WithMany()
.HasForeignKey("OrganizationalUnitId")
.OnDelete(DeleteBehavior.NoAction);
b.HasOne("MilitaryTrainingManagement.Models.Entities.UserAccount", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.NoAction);
b.Navigation("OrganizationalUnit");
b.Navigation("User");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.MaterialAllocation", b =>
{
b.HasOne("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", "CreatedByUnit")
.WithMany()
.HasForeignKey("CreatedByUnitId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("CreatedByUnit");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", b =>
{
b.HasOne("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", "Parent")
.WithMany("Children")
.HasForeignKey("ParentId")
.OnDelete(DeleteBehavior.Restrict);
b.Navigation("Parent");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.Personnel", b =>
{
b.HasOne("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", "ApprovedByUnit")
.WithMany()
.HasForeignKey("ApprovedByUnitId")
.OnDelete(DeleteBehavior.NoAction);
b.HasOne("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", "SubmittedByUnit")
.WithMany()
.HasForeignKey("SubmittedByUnitId")
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
b.Navigation("ApprovedByUnit");
b.Navigation("SubmittedByUnit");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.PersonnelApprovalHistory", b =>
{
b.HasOne("MilitaryTrainingManagement.Models.Entities.Personnel", "Personnel")
.WithMany()
.HasForeignKey("PersonnelId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", "ReviewedByUnit")
.WithMany()
.HasForeignKey("ReviewedByUnitId")
.OnDelete(DeleteBehavior.NoAction);
b.HasOne("MilitaryTrainingManagement.Models.Entities.UserAccount", "ReviewedByUser")
.WithMany()
.HasForeignKey("ReviewedByUserId")
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
b.Navigation("Personnel");
b.Navigation("ReviewedByUnit");
b.Navigation("ReviewedByUser");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.UserAccount", b =>
{
b.HasOne("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", "OrganizationalUnit")
.WithMany("Accounts")
.HasForeignKey("OrganizationalUnitId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("OrganizationalUnit");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.MaterialAllocation", b =>
{
b.Navigation("Distributions");
});
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", b =>
{
b.Navigation("Accounts");
b.Navigation("Children");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,35 @@
using System.ComponentModel.DataAnnotations;
namespace MilitaryTrainingManagement.Models.DTOs;
/// <summary>
/// 创建物资类别请求
/// </summary>
public class CreateMaterialCategoryRequest
{
[Required(ErrorMessage = "类别名称不能为空")]
[StringLength(50, ErrorMessage = "类别名称长度不能超过50个字符")]
public string Name { get; set; } = string.Empty;
[StringLength(200, ErrorMessage = "描述长度不能超过200个字符")]
public string? Description { get; set; }
public int SortOrder { get; set; }
}
/// <summary>
/// 更新物资类别请求
/// </summary>
public class UpdateMaterialCategoryRequest
{
[Required(ErrorMessage = "类别名称不能为空")]
[StringLength(50, ErrorMessage = "类别名称长度不能超过50个字符")]
public string Name { get; set; } = string.Empty;
[StringLength(200, ErrorMessage = "描述长度不能超过200个字符")]
public string? Description { get; set; }
public int SortOrder { get; set; }
public bool IsActive { get; set; }
}

View File

@ -0,0 +1,34 @@
namespace MilitaryTrainingManagement.Models.Entities;
/// <summary>
/// 物资类别
/// </summary>
public class MaterialCategory
{
public int Id { get; set; }
/// <summary>
/// 类别名称
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 类别描述
/// </summary>
public string? Description { get; set; }
/// <summary>
/// 是否启用
/// </summary>
public bool IsActive { get; set; } = true;
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// 排序顺序
/// </summary>
public int SortOrder { get; set; }
}

View File

@ -158,6 +158,73 @@ using (var scope = app.Services.CreateScope())
// 确保数据库已创建
context.Database.EnsureCreated();
// 手动创建 MaterialCategories 表(如果不存在)
try
{
var tableExists = context.Database.ExecuteSqlRaw(@"
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'MaterialCategories')
BEGIN
CREATE TABLE MaterialCategories (
Id INT PRIMARY KEY IDENTITY(1,1),
Name NVARCHAR(50) NOT NULL,
Description NVARCHAR(200),
IsActive BIT NOT NULL DEFAULT 1,
CreatedAt DATETIME2 NOT NULL,
SortOrder INT NOT NULL DEFAULT 0
);
CREATE UNIQUE INDEX IX_MaterialCategories_Name ON MaterialCategories(Name);
END
");
Console.WriteLine("MaterialCategories 表检查完成");
}
catch (Exception ex)
{
Console.WriteLine($"创建 MaterialCategories 表时出错: {ex.Message}");
}
// 如果没有物资类别,创建默认类别
if (!context.MaterialCategories.Any())
{
var categories = new[]
{
new MilitaryTrainingManagement.Models.Entities.MaterialCategory
{
Name = "弹药",
Description = "各类训练弹药",
IsActive = true,
SortOrder = 1,
CreatedAt = DateTime.UtcNow
},
new MilitaryTrainingManagement.Models.Entities.MaterialCategory
{
Name = "装备",
Description = "训练装备器材",
IsActive = true,
SortOrder = 2,
CreatedAt = DateTime.UtcNow
},
new MilitaryTrainingManagement.Models.Entities.MaterialCategory
{
Name = "物资",
Description = "训练保障物资",
IsActive = true,
SortOrder = 3,
CreatedAt = DateTime.UtcNow
},
new MilitaryTrainingManagement.Models.Entities.MaterialCategory
{
Name = "器材",
Description = "训练辅助器材",
IsActive = true,
SortOrder = 4,
CreatedAt = DateTime.UtcNow
}
};
context.MaterialCategories.AddRange(categories);
await context.SaveChangesAsync();
Console.WriteLine("默认物资类别已创建!");
}
// 如果没有组织单位,创建种子数据
if (!context.OrganizationalUnits.Any())

View File

@ -112,7 +112,7 @@ public class AllocationService : IAllocationService
throw new ArgumentException("单位配额不能为负数");
if (!await ValidateDistributionQuotasAsync(totalQuota, distributions))
throw new ArgumentException("分配配额总和超过总配额指标");
throw new ArgumentException("有配额未分配,无法提交");
// 验证目标单位存在
if (!await ValidateTargetUnitsExistAsync(distributions.Keys))
@ -203,7 +203,7 @@ public class AllocationService : IAllocationService
throw new ArgumentException("单位配额不能为负数");
if (!await ValidateDistributionQuotasAsync(allocation.TotalQuota, distributions))
throw new ArgumentException("分配配额总和超过总配额指标");
throw new ArgumentException("有配额未分配,无法提交");
// 验证目标单位存在
if (!await ValidateTargetUnitsExistAsync(distributions.Keys))
@ -242,10 +242,10 @@ public class AllocationService : IAllocationService
public Task<bool> ValidateDistributionQuotasAsync(decimal totalQuota, Dictionary<int, decimal> distributions)
{
if (distributions == null || distributions.Count == 0)
return Task.FromResult(true);
return Task.FromResult(false); // 必须有分配
var sum = distributions.Values.Sum();
return Task.FromResult(sum <= totalQuota);
return Task.FromResult(sum == totalQuota); // 必须完全分配,不能多也不能少
}
public async Task<bool> ValidateTargetUnitsExistAsync(IEnumerable<int> targetUnitIds)

View File

@ -1,6 +1,7 @@
export { authApi } from './auth'
export { organizationsApi } from './organizations'
export { allocationsApi } from './allocations'
export { materialCategoriesApi } from './materialCategories'
export { reportsApi } from './reports'
export { personnelApi } from './personnel'
export { approvalsApi } from './approvals'

View File

@ -0,0 +1,51 @@
import apiClient from './client'
export interface MaterialCategory {
id: number
name: string
description?: string
isActive: boolean
createdAt: string
sortOrder: number
}
export interface CreateMaterialCategoryRequest {
name: string
description?: string
sortOrder: number
}
export interface UpdateMaterialCategoryRequest {
name: string
description?: string
sortOrder: number
isActive: boolean
}
export const materialCategoriesApi = {
async getAll(activeOnly: boolean = true): Promise<MaterialCategory[]> {
const response = await apiClient.get<MaterialCategory[]>('/materialcategories', {
params: { activeOnly }
})
return response.data
},
async getById(id: number): Promise<MaterialCategory> {
const response = await apiClient.get<MaterialCategory>(`/materialcategories/${id}`)
return response.data
},
async create(data: CreateMaterialCategoryRequest): Promise<MaterialCategory> {
const response = await apiClient.post<MaterialCategory>('/materialcategories', data)
return response.data
},
async update(id: number, data: UpdateMaterialCategoryRequest): Promise<MaterialCategory> {
const response = await apiClient.put<MaterialCategory>(`/materialcategories/${id}`, data)
return response.data
},
async delete(id: number): Promise<void> {
await apiClient.delete(`/materialcategories/${id}`)
}
}

View File

@ -35,6 +35,12 @@ const routes: RouteRecordRaw[] = [
component: () => import('@/views/allocations/AllocationList.vue'),
meta: { title: '物资配额' }
},
{
path: 'allocations/categories',
name: 'MaterialCategories',
component: () => import('@/views/allocations/MaterialCategoryManagement.vue'),
meta: { title: '物资类别管理', minLevel: 1 }
},
{
path: 'allocations/create',
name: 'AllocationCreate',

View File

@ -7,7 +7,14 @@
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" v-loading="loading">
<el-form-item label="物资类别" prop="category">
<el-input v-model="form.category" placeholder="请输入物资类别" />
<el-select v-model="form.category" placeholder="请选择物资类别" style="width: 100%">
<el-option
v-for="cat in categories"
:key="cat.id"
:label="cat.name"
:value="cat.name"
/>
</el-select>
</el-form-item>
<el-form-item label="物资名称" prop="materialName">
<el-input v-model="form.materialName" placeholder="请输入物资名称" />
@ -64,9 +71,10 @@ import { ref, reactive, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, FormInstance, FormRules } from 'element-plus'
import { Plus, Delete } from '@element-plus/icons-vue'
import { allocationsApi, organizationsApi } from '@/api'
import { allocationsApi, organizationsApi, materialCategoriesApi } from '@/api'
import { useAuthStore } from '@/stores/auth'
import type { OrganizationalUnit, DistributionRequest } from '@/types'
import type { MaterialCategory } from '@/api/materialCategories'
const route = useRoute()
const router = useRouter()
@ -77,6 +85,7 @@ const formRef = ref<FormInstance>()
const loading = ref(false)
const saving = ref(false)
const subordinateUnits = ref<OrganizationalUnit[]>([])
const categories = ref<MaterialCategory[]>([])
const form = reactive({
category: '',
@ -114,6 +123,14 @@ async function loadSubordinateUnits() {
}
}
async function loadCategories() {
try {
categories.value = await materialCategoriesApi.getAll(true) //
} catch {
ElMessage.error('加载物资类别失败')
}
}
async function loadAllocation() {
if (!isEdit.value) return
@ -146,6 +163,11 @@ async function handleSubmit() {
return
}
if (distributedTotal.value < form.totalQuota) {
ElMessage.error('有配额未分配,无法提交')
return
}
saving.value = true
try {
if (isEdit.value) {
@ -178,6 +200,7 @@ async function handleSubmit() {
onMounted(() => {
loadSubordinateUnits()
loadCategories()
loadAllocation()
})
</script>

View File

@ -26,6 +26,10 @@
<el-option label="物资" value="物资" />
<el-option label="器材" value="器材" />
</el-select>
<el-button v-if="authStore.canCreateAllocations" @click="$router.push('/allocations/categories')">
<el-icon><Setting /></el-icon>
类别管理
</el-button>
<el-button v-if="authStore.canCreateAllocations" type="primary" @click="$router.push('/allocations/create')">
<el-icon><Plus /></el-icon>
创建配额
@ -208,7 +212,7 @@
import { ref, reactive, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search, View, Edit, Delete, Box } from '@element-plus/icons-vue'
import { Plus, Search, View, Edit, Delete, Box, Setting } from '@element-plus/icons-vue'
import { useAuthStore } from '@/stores/auth'
import { allocationsApi } from '@/api'
import type { MaterialAllocation, AllocationDistribution } from '@/types'

View File

@ -0,0 +1,191 @@
<template>
<div class="material-category-management">
<el-card>
<template #header>
<div class="card-header">
<span>物资类别管理</span>
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
添加类别
</el-button>
</div>
</template>
<el-table :data="categories" v-loading="loading">
<el-table-column prop="name" label="类别名称" min-width="150" />
<el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip />
<el-table-column prop="sortOrder" label="排序" width="100" align="center" />
<el-table-column prop="isActive" label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.isActive ? 'success' : 'info'" size="small">
{{ row.isActive ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createdAt" label="创建时间" width="160">
<template #default="{ row }">
{{ formatDate(row.createdAt) }}
</template>
</el-table-column>
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button type="primary" text size="small" @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" text size="small" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- Create/Edit Dialog -->
<el-dialog v-model="showDialog" :title="dialogTitle" width="500px">
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="类别名称" prop="name">
<el-input v-model="form.name" placeholder="请输入类别名称" maxlength="50" show-word-limit />
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input
v-model="form.description"
type="textarea"
:rows="3"
placeholder="请输入类别描述(可选)"
maxlength="200"
show-word-limit
/>
</el-form-item>
<el-form-item label="排序" prop="sortOrder">
<el-input-number v-model="form.sortOrder" :min="0" :max="9999" />
</el-form-item>
<el-form-item label="状态" prop="isActive" v-if="editingCategory">
<el-switch v-model="form.isActive" active-text="启用" inactive-text="禁用" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showDialog = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="handleSave">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox, FormInstance, FormRules } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { materialCategoriesApi } from '@/api'
import type { MaterialCategory } from '@/api/materialCategories'
const loading = ref(false)
const saving = ref(false)
const showDialog = ref(false)
const categories = ref<MaterialCategory[]>([])
const editingCategory = ref<MaterialCategory | null>(null)
const formRef = ref<FormInstance>()
const form = reactive({
name: '',
description: '',
sortOrder: 0,
isActive: true
})
const dialogTitle = computed(() => editingCategory.value ? '编辑类别' : '添加类别')
const rules: FormRules = {
name: [{ required: true, message: '请输入类别名称', trigger: 'blur' }],
sortOrder: [{ required: true, message: '请输入排序', trigger: 'blur' }]
}
function formatDate(dateStr: string): string {
return new Date(dateStr).toLocaleString('zh-CN')
}
async function loadCategories() {
loading.value = true
try {
categories.value = await materialCategoriesApi.getAll(false) //
} catch {
ElMessage.error('加载类别列表失败')
} finally {
loading.value = false
}
}
function handleAdd() {
editingCategory.value = null
form.name = ''
form.description = ''
form.sortOrder = 0
form.isActive = true
showDialog.value = true
}
function handleEdit(category: MaterialCategory) {
editingCategory.value = category
form.name = category.name
form.description = category.description || ''
form.sortOrder = category.sortOrder
form.isActive = category.isActive
showDialog.value = true
}
async function handleDelete(category: MaterialCategory) {
try {
await ElMessageBox.confirm(`确定要删除类别"${category.name}"吗?`, '确认删除', {
type: 'warning'
})
await materialCategoriesApi.delete(category.id)
ElMessage.success('删除成功')
await loadCategories()
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error(error.response?.data?.message || '删除失败')
}
}
}
async function handleSave() {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (!valid) return
saving.value = true
try {
if (editingCategory.value) {
await materialCategoriesApi.update(editingCategory.value.id, {
name: form.name,
description: form.description || undefined,
sortOrder: form.sortOrder,
isActive: form.isActive
})
ElMessage.success('更新成功')
} else {
await materialCategoriesApi.create({
name: form.name,
description: form.description || undefined,
sortOrder: form.sortOrder
})
ElMessage.success('创建成功')
}
showDialog.value = false
await loadCategories()
} catch (error: any) {
ElMessage.error(error.response?.data?.message || '操作失败')
} finally {
saving.value = false
}
})
}
onMounted(() => {
loadCategories()
})
</script>
<style scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>