From adef288429d63419e9aa472375cdf7aed0b83ec5 Mon Sep 17 00:00:00 2001 From: 18631081161 <2088094923@qq.com> Date: Wed, 14 Jan 2026 22:37:40 +0800 Subject: [PATCH] =?UTF-8?q?=E9=85=8D=E9=A2=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/AllocationsController.cs | 2 +- .../MaterialCategoriesController.cs | 142 ++++ .../Data/ApplicationDbContext.cs | 10 + ...0114141545_AddMaterialCategory.Designer.cs | 672 ++++++++++++++++++ .../20260114141545_AddMaterialCategory.cs | 456 ++++++++++++ .../ApplicationDbContextModelSnapshot.cs | 669 +++++++++++++++++ .../Models/DTOs/MaterialCategoryDTOs.cs | 35 + .../Models/Entities/MaterialCategory.cs | 34 + src/MilitaryTrainingManagement/Program.cs | 67 ++ .../Implementations/AllocationService.cs | 8 +- src/frontend/src/api/index.ts | 1 + src/frontend/src/api/materialCategories.ts | 51 ++ src/frontend/src/router/index.ts | 6 + .../src/views/allocations/AllocationForm.vue | 27 +- .../src/views/allocations/AllocationList.vue | 6 +- .../MaterialCategoryManagement.vue | 191 +++++ 16 files changed, 2369 insertions(+), 8 deletions(-) create mode 100644 src/MilitaryTrainingManagement/Controllers/MaterialCategoriesController.cs create mode 100644 src/MilitaryTrainingManagement/Migrations/20260114141545_AddMaterialCategory.Designer.cs create mode 100644 src/MilitaryTrainingManagement/Migrations/20260114141545_AddMaterialCategory.cs create mode 100644 src/MilitaryTrainingManagement/Migrations/ApplicationDbContextModelSnapshot.cs create mode 100644 src/MilitaryTrainingManagement/Models/DTOs/MaterialCategoryDTOs.cs create mode 100644 src/MilitaryTrainingManagement/Models/Entities/MaterialCategory.cs create mode 100644 src/frontend/src/api/materialCategories.ts create mode 100644 src/frontend/src/views/allocations/MaterialCategoryManagement.vue diff --git a/src/MilitaryTrainingManagement/Controllers/AllocationsController.cs b/src/MilitaryTrainingManagement/Controllers/AllocationsController.cs index 9044ea1..cbf3ea3 100644 --- a/src/MilitaryTrainingManagement/Controllers/AllocationsController.cs +++ b/src/MilitaryTrainingManagement/Controllers/AllocationsController.cs @@ -280,7 +280,7 @@ public class AllocationsController : BaseApiController quotaValid = isValid, targetUnitsExist = targetUnitsExist, isValid = isValid && targetUnitsExist, - message = !isValid ? "分配配额总和超过总配额指标" : + message = !isValid ? "有配额未分配,无法提交" : !targetUnitsExist ? "目标单位不存在" : "验证通过" }); } diff --git a/src/MilitaryTrainingManagement/Controllers/MaterialCategoriesController.cs b/src/MilitaryTrainingManagement/Controllers/MaterialCategoriesController.cs new file mode 100644 index 0000000..fc47ffe --- /dev/null +++ b/src/MilitaryTrainingManagement/Controllers/MaterialCategoriesController.cs @@ -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; + +/// +/// 物资类别管理控制器 +/// +[Authorize] +public class MaterialCategoriesController : BaseApiController +{ + private readonly ApplicationDbContext _context; + + public MaterialCategoriesController(ApplicationDbContext context) + { + _context = context; + } + + /// + /// 获取所有物资类别 + /// + [HttpGet] + public async Task 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); + } + + /// + /// 根据ID获取物资类别 + /// + [HttpGet("{id}")] + public async Task GetById(int id) + { + var category = await _context.MaterialCategories.FindAsync(id); + if (category == null) + { + return NotFound(new { message = "物资类别不存在" }); + } + return Ok(category); + } + + /// + /// 创建物资类别 + /// + [HttpPost] + public async Task 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); + } + + /// + /// 更新物资类别 + /// + [HttpPut("{id}")] + public async Task 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); + } + + /// + /// 删除物资类别 + /// + [HttpDelete("{id}")] + public async Task 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 = "删除成功" }); + } +} diff --git a/src/MilitaryTrainingManagement/Data/ApplicationDbContext.cs b/src/MilitaryTrainingManagement/Data/ApplicationDbContext.cs index e9478c2..2a6618e 100644 --- a/src/MilitaryTrainingManagement/Data/ApplicationDbContext.cs +++ b/src/MilitaryTrainingManagement/Data/ApplicationDbContext.cs @@ -17,6 +17,7 @@ public class ApplicationDbContext : DbContext public DbSet UserAccounts => Set(); public DbSet MaterialAllocations => Set(); public DbSet AllocationDistributions => Set(); + public DbSet MaterialCategories => Set(); public DbSet Personnel => Set(); public DbSet PersonnelApprovalHistories => Set(); public DbSet ApprovalRequests => Set(); @@ -65,6 +66,15 @@ public class ApplicationDbContext : DbContext .OnDelete(DeleteBehavior.Restrict); }); + // MaterialCategory 配置 + modelBuilder.Entity(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(entity => { diff --git a/src/MilitaryTrainingManagement/Migrations/20260114141545_AddMaterialCategory.Designer.cs b/src/MilitaryTrainingManagement/Migrations/20260114141545_AddMaterialCategory.Designer.cs new file mode 100644 index 0000000..b89fc4f --- /dev/null +++ b/src/MilitaryTrainingManagement/Migrations/20260114141545_AddMaterialCategory.Designer.cs @@ -0,0 +1,672 @@ +// +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 + { + /// + 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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ActualCompletion") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("AllocationId") + .HasColumnType("int"); + + b.Property("ApprovedAt") + .HasColumnType("datetime2"); + + b.Property("ApprovedByUserId") + .HasColumnType("int"); + + b.Property("IsApproved") + .HasColumnType("bit"); + + b.Property("ReportedAt") + .HasColumnType("datetime2"); + + b.Property("ReportedByUserId") + .HasColumnType("int"); + + b.Property("TargetUnitId") + .HasColumnType("int"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("OriginalData") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("RequestedAt") + .HasColumnType("datetime2"); + + b.Property("RequestedByUnitId") + .HasColumnType("int"); + + b.Property("RequestedByUserId") + .HasColumnType("int"); + + b.Property("RequestedChanges") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ReviewComments") + .HasColumnType("nvarchar(max)"); + + b.Property("ReviewedAt") + .HasColumnType("datetime2"); + + b.Property("ReviewedByUserId") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TargetEntityId") + .HasColumnType("int"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ChangedFields") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("EntityId") + .HasColumnType("int"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("IsSuccess") + .HasColumnType("bit"); + + b.Property("NewValues") + .HasColumnType("nvarchar(max)"); + + b.Property("OldValues") + .HasColumnType("nvarchar(max)"); + + b.Property("OrganizationalUnitId") + .HasColumnType("int"); + + b.Property("RequestPath") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Timestamp") + .HasColumnType("datetime2"); + + b.Property("UserAgent") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Category") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedByUnitId") + .HasColumnType("int"); + + b.Property("MaterialName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TotalQuota") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("MaterialCategories"); + }); + + modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Level") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ParentId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("OrganizationalUnits"); + }); + + modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.Personnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Achievements") + .HasColumnType("nvarchar(max)"); + + b.Property("Age") + .HasColumnType("int"); + + b.Property("ApprovedAt") + .HasColumnType("datetime2"); + + b.Property("ApprovedByUnitId") + .HasColumnType("int"); + + b.Property("ApprovedLevel") + .HasColumnType("int"); + + b.Property("ContactInfo") + .HasColumnType("nvarchar(max)"); + + b.Property("EducationLevel") + .HasColumnType("nvarchar(max)"); + + b.Property("Gender") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("Height") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("Hometown") + .HasColumnType("nvarchar(max)"); + + b.Property("IdNumber") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PhotoPath") + .HasColumnType("nvarchar(max)"); + + b.Property("Position") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ProfessionalTitle") + .HasColumnType("nvarchar(max)"); + + b.Property("Rank") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("SubmittedAt") + .HasColumnType("datetime2"); + + b.Property("SubmittedByUnitId") + .HasColumnType("int"); + + b.Property("SupportingDocuments") + .HasColumnType("nvarchar(max)"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("Comments") + .HasColumnType("nvarchar(max)"); + + b.Property("NewLevel") + .HasColumnType("int"); + + b.Property("NewStatus") + .HasColumnType("int"); + + b.Property("PersonnelId") + .HasColumnType("int"); + + b.Property("PreviousLevel") + .HasColumnType("int"); + + b.Property("PreviousStatus") + .HasColumnType("int"); + + b.Property("ReviewedAt") + .HasColumnType("datetime2"); + + b.Property("ReviewedByUnitId") + .HasColumnType("int"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("LastLoginAt") + .HasColumnType("datetime2"); + + b.Property("OrganizationalUnitId") + .HasColumnType("int"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("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 + } + } +} diff --git a/src/MilitaryTrainingManagement/Migrations/20260114141545_AddMaterialCategory.cs b/src/MilitaryTrainingManagement/Migrations/20260114141545_AddMaterialCategory.cs new file mode 100644 index 0000000..b3a0af0 --- /dev/null +++ b/src/MilitaryTrainingManagement/Migrations/20260114141545_AddMaterialCategory.cs @@ -0,0 +1,456 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MilitaryTrainingManagement.Migrations +{ + /// + public partial class AddMaterialCategory : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "MaterialCategories", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + IsActive = table.Column(type: "bit", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false), + SortOrder = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MaterialCategories", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "OrganizationalUnits", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + Level = table.Column(type: "int", nullable: false), + ParentId = table.Column(type: "int", nullable: true), + CreatedAt = table.Column(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(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Category = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + MaterialName = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Unit = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + TotalQuota = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false), + CreatedByUnitId = table.Column(type: "int", nullable: false), + CreatedAt = table.Column(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(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + PhotoPath = table.Column(type: "nvarchar(max)", nullable: true), + Position = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + Rank = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Gender = table.Column(type: "nvarchar(10)", maxLength: 10, nullable: false), + IdNumber = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: false), + ProfessionalTitle = table.Column(type: "nvarchar(max)", nullable: true), + EducationLevel = table.Column(type: "nvarchar(max)", nullable: true), + Age = table.Column(type: "int", nullable: false), + Height = table.Column(type: "decimal(5,2)", precision: 5, scale: 2, nullable: true), + ContactInfo = table.Column(type: "nvarchar(max)", nullable: true), + Hometown = table.Column(type: "nvarchar(max)", nullable: true), + TrainingParticipation = table.Column(type: "nvarchar(max)", nullable: true), + Achievements = table.Column(type: "nvarchar(max)", nullable: true), + SupportingDocuments = table.Column(type: "nvarchar(max)", nullable: true), + SubmittedByUnitId = table.Column(type: "int", nullable: false), + ApprovedLevel = table.Column(type: "int", nullable: true), + ApprovedByUnitId = table.Column(type: "int", nullable: true), + Status = table.Column(type: "int", nullable: false), + SubmittedAt = table.Column(type: "datetime2", nullable: false), + ApprovedAt = table.Column(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(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Username = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: false), + DisplayName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + OrganizationalUnitId = table.Column(type: "int", nullable: false), + IsActive = table.Column(type: "bit", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false), + LastLoginAt = table.Column(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(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + AllocationId = table.Column(type: "int", nullable: false), + TargetUnitId = table.Column(type: "int", nullable: false), + UnitQuota = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false), + ActualCompletion = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: true), + ReportedAt = table.Column(type: "datetime2", nullable: true), + ReportedByUserId = table.Column(type: "int", nullable: true), + IsApproved = table.Column(type: "bit", nullable: false), + ApprovedAt = table.Column(type: "datetime2", nullable: true), + ApprovedByUserId = table.Column(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(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Type = table.Column(type: "int", nullable: false), + TargetEntityId = table.Column(type: "int", nullable: false), + RequestedByUserId = table.Column(type: "int", nullable: false), + RequestedByUnitId = table.Column(type: "int", nullable: false), + Reason = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + OriginalData = table.Column(type: "nvarchar(max)", nullable: false), + RequestedChanges = table.Column(type: "nvarchar(max)", nullable: false), + Status = table.Column(type: "int", nullable: false), + ReviewedByUserId = table.Column(type: "int", nullable: true), + ReviewComments = table.Column(type: "nvarchar(max)", nullable: true), + RequestedAt = table.Column(type: "datetime2", nullable: false), + ReviewedAt = table.Column(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(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + EntityType = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + EntityId = table.Column(type: "int", nullable: false), + Action = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + OldValues = table.Column(type: "nvarchar(max)", nullable: true), + NewValues = table.Column(type: "nvarchar(max)", nullable: true), + ChangedFields = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "int", nullable: true), + OrganizationalUnitId = table.Column(type: "int", nullable: true), + Timestamp = table.Column(type: "datetime2", nullable: false), + IpAddress = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + UserAgent = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + RequestPath = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + IsSuccess = table.Column(type: "bit", nullable: false), + ErrorMessage = table.Column(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(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + PersonnelId = table.Column(type: "int", nullable: false), + Action = table.Column(type: "int", nullable: false), + PreviousStatus = table.Column(type: "int", nullable: true), + NewStatus = table.Column(type: "int", nullable: false), + PreviousLevel = table.Column(type: "int", nullable: true), + NewLevel = table.Column(type: "int", nullable: true), + ReviewedByUserId = table.Column(type: "int", nullable: false), + ReviewedByUnitId = table.Column(type: "int", nullable: true), + Comments = table.Column(type: "nvarchar(max)", nullable: true), + ReviewedAt = table.Column(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); + } + + /// + 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"); + } + } +} diff --git a/src/MilitaryTrainingManagement/Migrations/ApplicationDbContextModelSnapshot.cs b/src/MilitaryTrainingManagement/Migrations/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 0000000..45a79ea --- /dev/null +++ b/src/MilitaryTrainingManagement/Migrations/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,669 @@ +// +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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ActualCompletion") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("AllocationId") + .HasColumnType("int"); + + b.Property("ApprovedAt") + .HasColumnType("datetime2"); + + b.Property("ApprovedByUserId") + .HasColumnType("int"); + + b.Property("IsApproved") + .HasColumnType("bit"); + + b.Property("ReportedAt") + .HasColumnType("datetime2"); + + b.Property("ReportedByUserId") + .HasColumnType("int"); + + b.Property("TargetUnitId") + .HasColumnType("int"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("OriginalData") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("RequestedAt") + .HasColumnType("datetime2"); + + b.Property("RequestedByUnitId") + .HasColumnType("int"); + + b.Property("RequestedByUserId") + .HasColumnType("int"); + + b.Property("RequestedChanges") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ReviewComments") + .HasColumnType("nvarchar(max)"); + + b.Property("ReviewedAt") + .HasColumnType("datetime2"); + + b.Property("ReviewedByUserId") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TargetEntityId") + .HasColumnType("int"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ChangedFields") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("EntityId") + .HasColumnType("int"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("IsSuccess") + .HasColumnType("bit"); + + b.Property("NewValues") + .HasColumnType("nvarchar(max)"); + + b.Property("OldValues") + .HasColumnType("nvarchar(max)"); + + b.Property("OrganizationalUnitId") + .HasColumnType("int"); + + b.Property("RequestPath") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Timestamp") + .HasColumnType("datetime2"); + + b.Property("UserAgent") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Category") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedByUnitId") + .HasColumnType("int"); + + b.Property("MaterialName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TotalQuota") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("MaterialCategories"); + }); + + modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Level") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ParentId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("OrganizationalUnits"); + }); + + modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.Personnel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Achievements") + .HasColumnType("nvarchar(max)"); + + b.Property("Age") + .HasColumnType("int"); + + b.Property("ApprovedAt") + .HasColumnType("datetime2"); + + b.Property("ApprovedByUnitId") + .HasColumnType("int"); + + b.Property("ApprovedLevel") + .HasColumnType("int"); + + b.Property("ContactInfo") + .HasColumnType("nvarchar(max)"); + + b.Property("EducationLevel") + .HasColumnType("nvarchar(max)"); + + b.Property("Gender") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("Height") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("Hometown") + .HasColumnType("nvarchar(max)"); + + b.Property("IdNumber") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PhotoPath") + .HasColumnType("nvarchar(max)"); + + b.Property("Position") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ProfessionalTitle") + .HasColumnType("nvarchar(max)"); + + b.Property("Rank") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("SubmittedAt") + .HasColumnType("datetime2"); + + b.Property("SubmittedByUnitId") + .HasColumnType("int"); + + b.Property("SupportingDocuments") + .HasColumnType("nvarchar(max)"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("Comments") + .HasColumnType("nvarchar(max)"); + + b.Property("NewLevel") + .HasColumnType("int"); + + b.Property("NewStatus") + .HasColumnType("int"); + + b.Property("PersonnelId") + .HasColumnType("int"); + + b.Property("PreviousLevel") + .HasColumnType("int"); + + b.Property("PreviousStatus") + .HasColumnType("int"); + + b.Property("ReviewedAt") + .HasColumnType("datetime2"); + + b.Property("ReviewedByUnitId") + .HasColumnType("int"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("LastLoginAt") + .HasColumnType("datetime2"); + + b.Property("OrganizationalUnitId") + .HasColumnType("int"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("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 + } + } +} diff --git a/src/MilitaryTrainingManagement/Models/DTOs/MaterialCategoryDTOs.cs b/src/MilitaryTrainingManagement/Models/DTOs/MaterialCategoryDTOs.cs new file mode 100644 index 0000000..c355633 --- /dev/null +++ b/src/MilitaryTrainingManagement/Models/DTOs/MaterialCategoryDTOs.cs @@ -0,0 +1,35 @@ +using System.ComponentModel.DataAnnotations; + +namespace MilitaryTrainingManagement.Models.DTOs; + +/// +/// 创建物资类别请求 +/// +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; } +} + +/// +/// 更新物资类别请求 +/// +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; } +} diff --git a/src/MilitaryTrainingManagement/Models/Entities/MaterialCategory.cs b/src/MilitaryTrainingManagement/Models/Entities/MaterialCategory.cs new file mode 100644 index 0000000..2b35c76 --- /dev/null +++ b/src/MilitaryTrainingManagement/Models/Entities/MaterialCategory.cs @@ -0,0 +1,34 @@ +namespace MilitaryTrainingManagement.Models.Entities; + +/// +/// 物资类别 +/// +public class MaterialCategory +{ + public int Id { get; set; } + + /// + /// 类别名称 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 类别描述 + /// + public string? Description { get; set; } + + /// + /// 是否启用 + /// + public bool IsActive { get; set; } = true; + + /// + /// 创建时间 + /// + public DateTime CreatedAt { get; set; } + + /// + /// 排序顺序 + /// + public int SortOrder { get; set; } +} diff --git a/src/MilitaryTrainingManagement/Program.cs b/src/MilitaryTrainingManagement/Program.cs index 5bf72ef..1a1fd9d 100644 --- a/src/MilitaryTrainingManagement/Program.cs +++ b/src/MilitaryTrainingManagement/Program.cs @@ -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()) diff --git a/src/MilitaryTrainingManagement/Services/Implementations/AllocationService.cs b/src/MilitaryTrainingManagement/Services/Implementations/AllocationService.cs index ac0f94e..ec0b526 100644 --- a/src/MilitaryTrainingManagement/Services/Implementations/AllocationService.cs +++ b/src/MilitaryTrainingManagement/Services/Implementations/AllocationService.cs @@ -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 ValidateDistributionQuotasAsync(decimal totalQuota, Dictionary 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 ValidateTargetUnitsExistAsync(IEnumerable targetUnitIds) diff --git a/src/frontend/src/api/index.ts b/src/frontend/src/api/index.ts index df6cc75..390d555 100644 --- a/src/frontend/src/api/index.ts +++ b/src/frontend/src/api/index.ts @@ -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' diff --git a/src/frontend/src/api/materialCategories.ts b/src/frontend/src/api/materialCategories.ts new file mode 100644 index 0000000..14f0ac0 --- /dev/null +++ b/src/frontend/src/api/materialCategories.ts @@ -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 { + const response = await apiClient.get('/materialcategories', { + params: { activeOnly } + }) + return response.data + }, + + async getById(id: number): Promise { + const response = await apiClient.get(`/materialcategories/${id}`) + return response.data + }, + + async create(data: CreateMaterialCategoryRequest): Promise { + const response = await apiClient.post('/materialcategories', data) + return response.data + }, + + async update(id: number, data: UpdateMaterialCategoryRequest): Promise { + const response = await apiClient.put(`/materialcategories/${id}`, data) + return response.data + }, + + async delete(id: number): Promise { + await apiClient.delete(`/materialcategories/${id}`) + } +} diff --git a/src/frontend/src/router/index.ts b/src/frontend/src/router/index.ts index 7eef9f5..721a261 100644 --- a/src/frontend/src/router/index.ts +++ b/src/frontend/src/router/index.ts @@ -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', diff --git a/src/frontend/src/views/allocations/AllocationForm.vue b/src/frontend/src/views/allocations/AllocationForm.vue index 6571976..9879866 100644 --- a/src/frontend/src/views/allocations/AllocationForm.vue +++ b/src/frontend/src/views/allocations/AllocationForm.vue @@ -7,7 +7,14 @@ - + + + @@ -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() const loading = ref(false) const saving = ref(false) const subordinateUnits = ref([]) +const categories = ref([]) 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() }) diff --git a/src/frontend/src/views/allocations/AllocationList.vue b/src/frontend/src/views/allocations/AllocationList.vue index 29e3701..b1161ce 100644 --- a/src/frontend/src/views/allocations/AllocationList.vue +++ b/src/frontend/src/views/allocations/AllocationList.vue @@ -26,6 +26,10 @@ + + + 类别管理 + 创建配额 @@ -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' diff --git a/src/frontend/src/views/allocations/MaterialCategoryManagement.vue b/src/frontend/src/views/allocations/MaterialCategoryManagement.vue new file mode 100644 index 0000000..f82d372 --- /dev/null +++ b/src/frontend/src/views/allocations/MaterialCategoryManagement.vue @@ -0,0 +1,191 @@ + + + + +