逻辑修改
This commit is contained in:
parent
1c162e54c4
commit
61f89dfda6
|
|
@ -21,4 +21,9 @@ public interface IOrganizationalAuthorizationService
|
|||
/// 获取用户可访问的所有组织单位ID(包括自身和所有下级)
|
||||
/// </summary>
|
||||
Task<IEnumerable<int>> GetAccessibleUnitIdsAsync(int userUnitId);
|
||||
|
||||
/// <summary>
|
||||
/// 检查目标单位是否是当前单位的上级单位
|
||||
/// </summary>
|
||||
Task<bool> IsAncestorUnitAsync(int targetUnitId, int currentUnitId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,4 +31,11 @@ public class OrganizationalAuthorizationService : IOrganizationalAuthorizationSe
|
|||
var subordinateIds = await _organizationService.GetAllSubordinateIdsAsync(userUnitId);
|
||||
return new[] { userUnitId }.Concat(subordinateIds);
|
||||
}
|
||||
|
||||
public async Task<bool> IsAncestorUnitAsync(int targetUnitId, int currentUnitId)
|
||||
{
|
||||
// 检查 targetUnitId 是否是 currentUnitId 的上级单位
|
||||
var ancestorIds = await _organizationService.GetAllAncestorIdsAsync(currentUnitId);
|
||||
return ancestorIds.Contains(targetUnitId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,10 +110,12 @@ public class AllocationsController : BaseApiController
|
|||
if (allocation == null)
|
||||
return NotFound(new { message = "配额不存在" });
|
||||
|
||||
// 检查访问权限:只能查看自己创建的或分配给自己及下级的配额
|
||||
// 检查访问权限:可以查看自己创建的、分配给自己及下级的、或分配给上级单位的配额
|
||||
var canAccess = allocation.CreatedByUnitId == unitId.Value ||
|
||||
allocation.Distributions.Any(d =>
|
||||
_authorizationService.CanAccessUnitAsync(unitId.Value, d.TargetUnitId).GetAwaiter().GetResult());
|
||||
_authorizationService.CanAccessUnitAsync(unitId.Value, d.TargetUnitId).GetAwaiter().GetResult()) ||
|
||||
allocation.Distributions.Any(d =>
|
||||
_authorizationService.IsAncestorUnitAsync(d.TargetUnitId, unitId.Value).GetAwaiter().GetResult());
|
||||
|
||||
if (!canAccess)
|
||||
return Forbid();
|
||||
|
|
@ -318,6 +320,50 @@ public class AllocationsController : BaseApiController
|
|||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取配额分配的上报历史记录
|
||||
/// </summary>
|
||||
[HttpGet("distributions/{distributionId}/reports")]
|
||||
public async Task<IActionResult> GetConsumptionReports(int distributionId)
|
||||
{
|
||||
var unitId = GetCurrentUnitId();
|
||||
var unitLevel = GetCurrentUnitLevel();
|
||||
|
||||
if (unitId == null)
|
||||
return Unauthorized(new { message = "无法获取用户组织信息" });
|
||||
|
||||
// 检查分配记录是否存在
|
||||
var distribution = await _allocationService.GetDistributionByIdAsync(distributionId);
|
||||
if (distribution == null)
|
||||
return NotFound(new { message = "配额分配记录不存在" });
|
||||
|
||||
// 检查访问权限:
|
||||
// 1. 师团级可以查看所有记录
|
||||
// 2. 可以查看分配给自己单位的记录
|
||||
// 3. 可以查看分配给上级单位的记录
|
||||
// 4. 可以查看分配给下级单位的记录
|
||||
var canAccess = unitLevel == OrganizationalLevel.Division ||
|
||||
distribution.TargetUnitId == unitId.Value ||
|
||||
await _authorizationService.IsAncestorUnitAsync(distribution.TargetUnitId, unitId.Value) ||
|
||||
await _authorizationService.CanAccessUnitAsync(unitId.Value, distribution.TargetUnitId);
|
||||
|
||||
if (!canAccess)
|
||||
return Forbid();
|
||||
|
||||
var reports = await _allocationService.GetConsumptionReportsAsync(distributionId);
|
||||
var response = reports.Select(r => new
|
||||
{
|
||||
id = r.Id,
|
||||
reportedAmount = r.ReportedAmount,
|
||||
cumulativeAmount = r.CumulativeAmount,
|
||||
remarks = r.Remarks,
|
||||
reportedByUserName = r.ReportedByUser?.DisplayName,
|
||||
reportedAt = r.ReportedAt
|
||||
});
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 映射实体到响应DTO
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ public class PersonnelController : BaseApiController
|
|||
var items = allPersonnel
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(MapToResponse)
|
||||
.ToList();
|
||||
|
||||
return Ok(new
|
||||
|
|
@ -103,6 +104,45 @@ public class PersonnelController : BaseApiController
|
|||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 映射人员实体到响应DTO
|
||||
/// </summary>
|
||||
private static PersonnelResponse MapToResponse(Personnel personnel)
|
||||
{
|
||||
return new PersonnelResponse
|
||||
{
|
||||
Id = personnel.Id,
|
||||
Name = personnel.Name,
|
||||
PhotoPath = personnel.PhotoPath,
|
||||
Position = personnel.Position,
|
||||
Rank = personnel.Rank,
|
||||
Gender = personnel.Gender,
|
||||
IdNumber = personnel.IdNumber,
|
||||
ProfessionalTitle = personnel.ProfessionalTitle,
|
||||
EducationLevel = personnel.EducationLevel,
|
||||
Age = personnel.Age,
|
||||
Height = personnel.Height,
|
||||
ContactInfo = personnel.ContactInfo,
|
||||
Hometown = personnel.Hometown,
|
||||
TrainingParticipation = personnel.TrainingParticipation,
|
||||
Achievements = personnel.Achievements,
|
||||
SupportingDocuments = personnel.SupportingDocuments,
|
||||
Ethnicity = personnel.Ethnicity,
|
||||
PoliticalStatus = personnel.PoliticalStatus,
|
||||
BirthDate = personnel.BirthDate,
|
||||
EnlistmentDate = personnel.EnlistmentDate,
|
||||
Specialty = personnel.Specialty,
|
||||
SubmittedByUnitId = personnel.SubmittedByUnitId,
|
||||
SubmittedByUnitName = personnel.SubmittedByUnit?.Name,
|
||||
ApprovedLevel = personnel.ApprovedLevel,
|
||||
ApprovedByUnitId = personnel.ApprovedByUnitId,
|
||||
ApprovedByUnitName = personnel.ApprovedByUnit?.Name,
|
||||
Status = personnel.Status,
|
||||
SubmittedAt = personnel.SubmittedAt,
|
||||
ApprovedAt = personnel.ApprovedAt
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取待审批的人员列表
|
||||
/// </summary>
|
||||
|
|
@ -143,7 +183,7 @@ public class PersonnelController : BaseApiController
|
|||
return Forbid();
|
||||
}
|
||||
|
||||
return Ok(personnel);
|
||||
return Ok(MapToResponse(personnel));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -473,7 +513,7 @@ public class PersonnelController : BaseApiController
|
|||
/// </summary>
|
||||
[HttpPost("{id}/approve")]
|
||||
[Authorize(Policy = "RegimentLevel")] // 团级及以上权限
|
||||
public async Task<IActionResult> Approve(int id, [FromBody] ApprovePersonnelRequest request)
|
||||
public async Task<IActionResult> Approve(int id)
|
||||
{
|
||||
var unitId = GetCurrentUnitId();
|
||||
var userId = GetCurrentUserId();
|
||||
|
|
@ -492,8 +532,9 @@ public class PersonnelController : BaseApiController
|
|||
|
||||
try
|
||||
{
|
||||
var personnel = await _personnelService.ApproveAsync(id, unitId.Value, request.Level);
|
||||
return Ok(personnel);
|
||||
// 不传递level参数,让服务层根据人员所在单位自动确定等级
|
||||
var personnel = await _personnelService.ApproveAsync(id, unitId.Value);
|
||||
return Ok(MapToResponse(personnel));
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -35,9 +35,22 @@ public class StatsController : BaseApiController
|
|||
var unitIds = await GetUnitAndSubordinateIds(unitId.Value);
|
||||
|
||||
// 统计配额数
|
||||
var allocationsCount = await _context.MaterialAllocations
|
||||
.Where(a => a.CreatedByUnitId == unitId.Value || unitIds.Contains(a.CreatedByUnitId))
|
||||
.CountAsync();
|
||||
int allocationsCount;
|
||||
if (unitLevel == OrganizationalLevel.Division)
|
||||
{
|
||||
// 师团级:统计创建的配额数
|
||||
allocationsCount = await _context.MaterialAllocations
|
||||
.Where(a => a.CreatedByUnitId == unitId.Value)
|
||||
.CountAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 团部及以下:统计分配给本单位及下级单位的配额数
|
||||
allocationsCount = await _context.MaterialAllocations
|
||||
.Include(a => a.Distributions)
|
||||
.Where(a => a.Distributions.Any(d => unitIds.Contains(d.TargetUnitId)))
|
||||
.CountAsync();
|
||||
}
|
||||
|
||||
// 统计完成率
|
||||
var distributions = await _context.AllocationDistributions
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ public class ApplicationDbContext : DbContext
|
|||
public DbSet<PersonnelApprovalHistory> PersonnelApprovalHistories => Set<PersonnelApprovalHistory>();
|
||||
public DbSet<ApprovalRequest> ApprovalRequests => Set<ApprovalRequest>();
|
||||
public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
|
||||
public DbSet<ConsumptionReport> ConsumptionReports => Set<ConsumptionReport>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
|
|
@ -188,5 +189,24 @@ public class ApplicationDbContext : DbContext
|
|||
.HasForeignKey(e => e.ReviewedByUnitId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
});
|
||||
|
||||
// ConsumptionReport 配置
|
||||
modelBuilder.Entity<ConsumptionReport>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.Property(e => e.ReportedAmount).HasPrecision(18, 2);
|
||||
entity.Property(e => e.CumulativeAmount).HasPrecision(18, 2);
|
||||
entity.Property(e => e.Remarks).HasMaxLength(500);
|
||||
entity.HasOne(e => e.AllocationDistribution)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.AllocationDistributionId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
entity.HasOne(e => e.ReportedByUser)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.ReportedByUserId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
entity.HasIndex(e => e.AllocationDistributionId);
|
||||
entity.HasIndex(e => e.ReportedAt);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
748
src/MilitaryTrainingManagement/Migrations/20260115152942_AddConsumptionReportTable.Designer.cs
generated
Normal file
748
src/MilitaryTrainingManagement/Migrations/20260115152942_AddConsumptionReportTable.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,748 @@
|
|||
// <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("20260115152942_AddConsumptionReportTable")]
|
||||
partial class AddConsumptionReportTable
|
||||
{
|
||||
/// <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.ConsumptionReport", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("AllocationDistributionId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("CumulativeAmount")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<string>("Remarks")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<decimal>("ReportedAmount")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<DateTime>("ReportedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int>("ReportedByUserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AllocationDistributionId");
|
||||
|
||||
b.HasIndex("ReportedAt");
|
||||
|
||||
b.HasIndex("ReportedByUserId");
|
||||
|
||||
b.ToTable("ConsumptionReports");
|
||||
});
|
||||
|
||||
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>("BirthDate")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ContactInfo")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("EducationLevel")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("EnlistmentDate")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Ethnicity")
|
||||
.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")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("PhotoPath")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("PoliticalStatus")
|
||||
.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<string>("Specialty")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
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.Property<string>("Unit")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApprovedByUnitId");
|
||||
|
||||
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>("PlainPassword")
|
||||
.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.ConsumptionReport", b =>
|
||||
{
|
||||
b.HasOne("MilitaryTrainingManagement.Models.Entities.AllocationDistribution", "AllocationDistribution")
|
||||
.WithMany()
|
||||
.HasForeignKey("AllocationDistributionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("MilitaryTrainingManagement.Models.Entities.UserAccount", "ReportedByUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("ReportedByUserId")
|
||||
.OnDelete(DeleteBehavior.NoAction)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("AllocationDistribution");
|
||||
|
||||
b.Navigation("ReportedByUser");
|
||||
});
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MilitaryTrainingManagement.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddConsumptionReportTable : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "PlainPassword",
|
||||
table: "UserAccounts",
|
||||
type: "nvarchar(max)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Unit",
|
||||
table: "Personnel",
|
||||
type: "nvarchar(max)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ConsumptionReports",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
AllocationDistributionId = table.Column<int>(type: "int", nullable: false),
|
||||
ReportedAmount = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
CumulativeAmount = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
Remarks = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
ReportedByUserId = table.Column<int>(type: "int", nullable: false),
|
||||
ReportedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ConsumptionReports", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ConsumptionReports_AllocationDistributions_AllocationDistributionId",
|
||||
column: x => x.AllocationDistributionId,
|
||||
principalTable: "AllocationDistributions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_ConsumptionReports_UserAccounts_ReportedByUserId",
|
||||
column: x => x.ReportedByUserId,
|
||||
principalTable: "UserAccounts",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ConsumptionReports_AllocationDistributionId",
|
||||
table: "ConsumptionReports",
|
||||
column: "AllocationDistributionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ConsumptionReports_ReportedAt",
|
||||
table: "ConsumptionReports",
|
||||
column: "ReportedAt");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ConsumptionReports_ReportedByUserId",
|
||||
table: "ConsumptionReports",
|
||||
column: "ReportedByUserId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ConsumptionReports");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PlainPassword",
|
||||
table: "UserAccounts");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Unit",
|
||||
table: "Personnel");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -210,6 +210,46 @@ namespace MilitaryTrainingManagement.Migrations
|
|||
b.ToTable("AuditLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.ConsumptionReport", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("AllocationDistributionId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("CumulativeAmount")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<string>("Remarks")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<decimal>("ReportedAmount")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<DateTime>("ReportedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int>("ReportedByUserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AllocationDistributionId");
|
||||
|
||||
b.HasIndex("ReportedAt");
|
||||
|
||||
b.HasIndex("ReportedByUserId");
|
||||
|
||||
b.ToTable("ConsumptionReports");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.MaterialAllocation", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
|
@ -409,6 +449,9 @@ namespace MilitaryTrainingManagement.Migrations
|
|||
b.Property<string>("TrainingParticipation")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Unit")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApprovedByUnitId");
|
||||
|
|
@ -496,6 +539,9 @@ namespace MilitaryTrainingManagement.Migrations
|
|||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("PlainPassword")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
|
|
@ -587,6 +633,25 @@ namespace MilitaryTrainingManagement.Migrations
|
|||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.ConsumptionReport", b =>
|
||||
{
|
||||
b.HasOne("MilitaryTrainingManagement.Models.Entities.AllocationDistribution", "AllocationDistribution")
|
||||
.WithMany()
|
||||
.HasForeignKey("AllocationDistributionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("MilitaryTrainingManagement.Models.Entities.UserAccount", "ReportedByUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("ReportedByUserId")
|
||||
.OnDelete(DeleteBehavior.NoAction)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("AllocationDistribution");
|
||||
|
||||
b.Navigation("ReportedByUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MilitaryTrainingManagement.Models.Entities.MaterialAllocation", b =>
|
||||
{
|
||||
b.HasOne("MilitaryTrainingManagement.Models.Entities.OrganizationalUnit", "CreatedByUnit")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace MilitaryTrainingManagement.Models.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// 消耗上报记录
|
||||
/// </summary>
|
||||
public class ConsumptionReport
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 配额分配ID
|
||||
/// </summary>
|
||||
[Required]
|
||||
public int AllocationDistributionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 配额分配
|
||||
/// </summary>
|
||||
[ForeignKey(nameof(AllocationDistributionId))]
|
||||
public AllocationDistribution AllocationDistribution { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 本次上报数量
|
||||
/// </summary>
|
||||
[Required]
|
||||
[Column(TypeName = "decimal(18,2)")]
|
||||
public decimal ReportedAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 上报后累计数量
|
||||
/// </summary>
|
||||
[Required]
|
||||
[Column(TypeName = "decimal(18,2)")]
|
||||
public decimal CumulativeAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
[MaxLength(500)]
|
||||
public string? Remarks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 上报人ID
|
||||
/// </summary>
|
||||
[Required]
|
||||
public int ReportedByUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 上报人
|
||||
/// </summary>
|
||||
[ForeignKey(nameof(ReportedByUserId))]
|
||||
public UserAccount ReportedByUser { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 上报时间
|
||||
/// </summary>
|
||||
[Required]
|
||||
public DateTime ReportedAt { get; set; }
|
||||
}
|
||||
|
|
@ -28,5 +28,10 @@ public enum PersonnelApprovalAction
|
|||
/// <summary>
|
||||
/// 等级调整
|
||||
/// </summary>
|
||||
LevelAdjusted = 5
|
||||
LevelAdjusted = 5,
|
||||
|
||||
/// <summary>
|
||||
/// 向上申报等级升级
|
||||
/// </summary>
|
||||
LevelUpgraded = 6
|
||||
}
|
||||
|
|
@ -253,6 +253,39 @@ using (var scope = app.Services.CreateScope())
|
|||
Console.WriteLine($"添加 UserAccounts 表 PlainPassword 列时出错: {ex.Message}");
|
||||
}
|
||||
|
||||
// 创建 ConsumptionReports 表(如果不存在)
|
||||
try
|
||||
{
|
||||
context.Database.ExecuteSqlRaw(@"
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'ConsumptionReports')
|
||||
BEGIN
|
||||
CREATE TABLE ConsumptionReports (
|
||||
Id INT PRIMARY KEY IDENTITY(1,1),
|
||||
AllocationDistributionId INT NOT NULL,
|
||||
ReportedAmount DECIMAL(18,2) NOT NULL,
|
||||
CumulativeAmount DECIMAL(18,2) NOT NULL,
|
||||
Remarks NVARCHAR(500) NULL,
|
||||
ReportedByUserId INT NOT NULL,
|
||||
ReportedAt DATETIME2 NOT NULL,
|
||||
CONSTRAINT FK_ConsumptionReports_AllocationDistributions
|
||||
FOREIGN KEY (AllocationDistributionId)
|
||||
REFERENCES AllocationDistributions(Id) ON DELETE CASCADE,
|
||||
CONSTRAINT FK_ConsumptionReports_UserAccounts
|
||||
FOREIGN KEY (ReportedByUserId)
|
||||
REFERENCES UserAccounts(Id)
|
||||
);
|
||||
CREATE INDEX IX_ConsumptionReports_AllocationDistributionId ON ConsumptionReports(AllocationDistributionId);
|
||||
CREATE INDEX IX_ConsumptionReports_ReportedAt ON ConsumptionReports(ReportedAt);
|
||||
CREATE INDEX IX_ConsumptionReports_ReportedByUserId ON ConsumptionReports(ReportedByUserId);
|
||||
END
|
||||
");
|
||||
Console.WriteLine("ConsumptionReports 表检查完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"创建 ConsumptionReports 表时出错: {ex.Message}");
|
||||
}
|
||||
|
||||
// 如果没有物资类别,创建默认类别
|
||||
if (!context.MaterialCategories.Any())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -57,6 +57,14 @@ public class AllocationService : IAllocationService
|
|||
var subordinateIds = await _organizationService.GetAllSubordinateIdsAsync(unitId);
|
||||
var allUnitIds = new HashSet<int>(subordinateIds) { unitId };
|
||||
|
||||
// 获取该单位的所有上级单位ID(用于营部及以下账号查看团的配额)
|
||||
var ancestorIds = await GetAncestorUnitIdsAsync(unitId);
|
||||
var allRelatedUnitIds = new HashSet<int>(allUnitIds);
|
||||
foreach (var ancestorId in ancestorIds)
|
||||
{
|
||||
allRelatedUnitIds.Add(ancestorId);
|
||||
}
|
||||
|
||||
// 获取分配给这些单位的配额
|
||||
var allocations = await _context.MaterialAllocations
|
||||
.Include(a => a.CreatedByUnit)
|
||||
|
|
@ -64,21 +72,38 @@ public class AllocationService : IAllocationService
|
|||
.ThenInclude(d => d.TargetUnit)
|
||||
.Include(a => a.Distributions)
|
||||
.ThenInclude(d => d.ReportedByUser)
|
||||
.Where(a => a.Distributions.Any(d => allUnitIds.Contains(d.TargetUnitId)))
|
||||
.Where(a => a.Distributions.Any(d => allRelatedUnitIds.Contains(d.TargetUnitId)))
|
||||
.OrderByDescending(a => a.CreatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
// 过滤每个配额的分配记录,只保留分配给当前单位及其下级的记录
|
||||
// 过滤每个配额的分配记录,只保留分配给当前单位及其上下级的记录
|
||||
foreach (var allocation in allocations)
|
||||
{
|
||||
allocation.Distributions = allocation.Distributions
|
||||
.Where(d => allUnitIds.Contains(d.TargetUnitId))
|
||||
.Where(d => allRelatedUnitIds.Contains(d.TargetUnitId))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return allocations;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取单位的所有上级单位ID
|
||||
/// </summary>
|
||||
private async Task<List<int>> GetAncestorUnitIdsAsync(int unitId)
|
||||
{
|
||||
var result = new List<int>();
|
||||
var currentUnit = await _context.OrganizationalUnits.FindAsync(unitId);
|
||||
|
||||
while (currentUnit?.ParentId != null)
|
||||
{
|
||||
result.Add(currentUnit.ParentId.Value);
|
||||
currentUnit = await _context.OrganizationalUnits.FindAsync(currentUnit.ParentId.Value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<AllocationDistribution>> GetDistributionsForUnitAsync(int unitId)
|
||||
{
|
||||
return await _context.AllocationDistributions
|
||||
|
|
@ -256,8 +281,16 @@ public class AllocationService : IAllocationService
|
|||
if (distribution == null)
|
||||
throw new ArgumentException("配额分配记录不存在");
|
||||
|
||||
// 验证权限:只能更新分配给自己单位的记录
|
||||
if (distribution.TargetUnitId != unitId)
|
||||
// 验证权限:可以更新分配给自己单位或上级单位的记录
|
||||
var canReport = distribution.TargetUnitId == unitId;
|
||||
if (!canReport)
|
||||
{
|
||||
// 检查是否是上级单位的配额
|
||||
var ancestorIds = await GetAncestorUnitIdsAsync(unitId);
|
||||
canReport = ancestorIds.Contains(distribution.TargetUnitId);
|
||||
}
|
||||
|
||||
if (!canReport)
|
||||
throw new UnauthorizedAccessException("无权更新此配额分配记录");
|
||||
|
||||
// 验证实际完成数量
|
||||
|
|
@ -267,6 +300,24 @@ public class AllocationService : IAllocationService
|
|||
if (actualCompletion > distribution.UnitQuota)
|
||||
throw new ArgumentException($"实际完成数量不能超过分配配额({distribution.UnitQuota})");
|
||||
|
||||
// 计算本次上报数量
|
||||
var previousAmount = distribution.ActualCompletion ?? 0;
|
||||
var reportedAmount = actualCompletion - previousAmount;
|
||||
|
||||
// 如果本次上报数量大于0,保存历史记录
|
||||
if (reportedAmount > 0)
|
||||
{
|
||||
var consumptionReport = new ConsumptionReport
|
||||
{
|
||||
AllocationDistributionId = distributionId,
|
||||
ReportedAmount = reportedAmount,
|
||||
CumulativeAmount = actualCompletion,
|
||||
ReportedByUserId = userId,
|
||||
ReportedAt = DateTime.UtcNow
|
||||
};
|
||||
_context.ConsumptionReports.Add(consumptionReport);
|
||||
}
|
||||
|
||||
// 更新实际完成数量
|
||||
distribution.ActualCompletion = actualCompletion;
|
||||
distribution.ReportedAt = DateTime.UtcNow;
|
||||
|
|
@ -308,4 +359,13 @@ public class AllocationService : IAllocationService
|
|||
|
||||
return existingIds.Count == targetUnitIds.Distinct().Count();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ConsumptionReport>> GetConsumptionReportsAsync(int distributionId)
|
||||
{
|
||||
return await _context.ConsumptionReports
|
||||
.Include(r => r.ReportedByUser)
|
||||
.Where(r => r.AllocationDistributionId == distributionId)
|
||||
.OrderByDescending(r => r.ReportedAt)
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -193,4 +193,18 @@ public class OrganizationService : IOrganizationService
|
|||
// 检查是否是上级单位(递归向上查找)
|
||||
return await IsSubordinateOfAsync(childUnitId, parentUnitId);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<int>> GetAllAncestorIdsAsync(int unitId)
|
||||
{
|
||||
var result = new List<int>();
|
||||
var currentUnit = await _context.OrganizationalUnits.FindAsync(unitId);
|
||||
|
||||
while (currentUnit?.ParentId != null)
|
||||
{
|
||||
result.Add(currentUnit.ParentId.Value);
|
||||
currentUnit = await _context.OrganizationalUnits.FindAsync(currentUnit.ParentId.Value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,9 +81,11 @@ public class PersonnelService : IPersonnelService
|
|||
return personnel;
|
||||
}
|
||||
|
||||
public async Task<Personnel> ApproveAsync(int personnelId, int approvedByUnitId, PersonnelLevel level)
|
||||
public async Task<Personnel> ApproveAsync(int personnelId, int approvedByUnitId, PersonnelLevel? level = null)
|
||||
{
|
||||
var personnel = await _context.Personnel.FindAsync(personnelId);
|
||||
var personnel = await _context.Personnel
|
||||
.Include(p => p.SubmittedByUnit)
|
||||
.FirstOrDefaultAsync(p => p.Id == personnelId);
|
||||
if (personnel == null)
|
||||
throw new ArgumentException("人员记录不存在");
|
||||
|
||||
|
|
@ -91,9 +93,21 @@ public class PersonnelService : IPersonnelService
|
|||
if (approvedByUnit == null)
|
||||
throw new ArgumentException("审批单位不存在");
|
||||
|
||||
// 人员等级变更为审批单位的等级
|
||||
PersonnelLevel actualLevel;
|
||||
if (level.HasValue)
|
||||
{
|
||||
actualLevel = level.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 根据审批单位的层级确定人员等级
|
||||
actualLevel = (PersonnelLevel)(int)approvedByUnit.Level;
|
||||
}
|
||||
|
||||
// 验证审批单位层级必须高于或等于人员等级(数值越小层级越高)
|
||||
var unitLevelValue = (int)approvedByUnit.Level;
|
||||
var personnelLevelValue = (int)level;
|
||||
var personnelLevelValue = (int)actualLevel;
|
||||
if (unitLevelValue > personnelLevelValue)
|
||||
throw new ArgumentException("审批单位层级不足以审批该等级人才");
|
||||
|
||||
|
|
@ -102,23 +116,45 @@ public class PersonnelService : IPersonnelService
|
|||
|
||||
personnel.Status = PersonnelStatus.Approved;
|
||||
personnel.ApprovedByUnitId = approvedByUnitId;
|
||||
personnel.ApprovedLevel = level;
|
||||
personnel.ApprovedLevel = actualLevel;
|
||||
personnel.ApprovedAt = DateTime.UtcNow;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// 记录审批历史
|
||||
var userId = await GetUserIdByUnitAsync(approvedByUnitId);
|
||||
await RecordApprovalHistoryAsync(personnelId, PersonnelApprovalAction.Approved,
|
||||
previousStatus, PersonnelStatus.Approved, previousLevel, level,
|
||||
userId, approvedByUnitId, "审批通过");
|
||||
var actionType = previousStatus == PersonnelStatus.Approved
|
||||
? PersonnelApprovalAction.LevelUpgraded
|
||||
: PersonnelApprovalAction.Approved;
|
||||
var comments = previousStatus == PersonnelStatus.Approved
|
||||
? $"向上申报通过,等级从{GetLevelName(previousLevel)}升级为{GetLevelName(actualLevel)}"
|
||||
: "审批通过";
|
||||
|
||||
await RecordApprovalHistoryAsync(personnelId, actionType,
|
||||
previousStatus, PersonnelStatus.Approved, previousLevel, actualLevel,
|
||||
userId, approvedByUnitId, comments);
|
||||
|
||||
_logger.LogInformation("人员 {PersonnelId} 已被单位 {UnitId} 审批通过,等级:{Level}",
|
||||
personnelId, approvedByUnitId, level);
|
||||
personnelId, approvedByUnitId, actualLevel);
|
||||
|
||||
return personnel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取等级名称
|
||||
/// </summary>
|
||||
private static string GetLevelName(PersonnelLevel? level)
|
||||
{
|
||||
return level switch
|
||||
{
|
||||
PersonnelLevel.Division => "师级人才",
|
||||
PersonnelLevel.Regiment => "团级人才",
|
||||
PersonnelLevel.Battalion => "营级人才",
|
||||
PersonnelLevel.Company => "连级人才",
|
||||
_ => "未定级"
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<Personnel> RejectAsync(int personnelId, int reviewedByUserId)
|
||||
{
|
||||
var personnel = await _context.Personnel.FindAsync(personnelId);
|
||||
|
|
@ -519,14 +555,27 @@ public class PersonnelService : IPersonnelService
|
|||
|
||||
var personnel = await _context.Personnel
|
||||
.Include(p => p.SubmittedByUnit)
|
||||
.Include(p => p.ApprovedByUnit)
|
||||
.FirstOrDefaultAsync(p => p.Id == personnelId);
|
||||
|
||||
if (personnel == null || personnel.Status != PersonnelStatus.Pending)
|
||||
if (personnel == null)
|
||||
return false;
|
||||
|
||||
// 同一单位可以审批自己提交的人员
|
||||
// 已拒绝的人员不能审批
|
||||
if (personnel.Status == PersonnelStatus.Rejected)
|
||||
return false;
|
||||
|
||||
// 本单位不能审批自己提交的人员,必须由上级单位审批
|
||||
if (user.OrganizationalUnitId == personnel.SubmittedByUnitId)
|
||||
return true;
|
||||
return false;
|
||||
|
||||
// 如果是已审批的人员,检查当前用户单位是否比已审批单位层级更高
|
||||
if (personnel.Status == PersonnelStatus.Approved && personnel.ApprovedByUnitId.HasValue)
|
||||
{
|
||||
// 用户单位层级必须高于已审批单位层级(数值越小层级越高)
|
||||
if ((int)user.OrganizationalUnit!.Level >= (int)personnel.ApprovedByUnit!.Level)
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查用户的组织单位是否是提交单位的上级
|
||||
var isParent = await _organizationService.IsParentUnitAsync(user.OrganizationalUnitId, personnel.SubmittedByUnitId);
|
||||
|
|
|
|||
|
|
@ -71,4 +71,9 @@ public interface IAllocationService
|
|||
/// 验证目标单位是否存在
|
||||
/// </summary>
|
||||
Task<bool> ValidateTargetUnitsExistAsync(IEnumerable<int> targetUnitIds);
|
||||
|
||||
/// <summary>
|
||||
/// 获取配额分配的上报历史记录
|
||||
/// </summary>
|
||||
Task<IEnumerable<ConsumptionReport>> GetConsumptionReportsAsync(int distributionId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ public interface IOrganizationService
|
|||
Task<IEnumerable<OrganizationalUnit>> GetAllAsync();
|
||||
Task<IEnumerable<OrganizationalUnit>> GetSubordinatesAsync(int unitId);
|
||||
Task<IEnumerable<int>> GetAllSubordinateIdsAsync(int unitId);
|
||||
Task<IEnumerable<int>> GetAllAncestorIdsAsync(int unitId);
|
||||
Task<OrganizationalUnit> CreateAsync(string name, OrganizationalLevel level, int? parentId);
|
||||
Task<OrganizationalUnit> UpdateAsync(int id, string name);
|
||||
Task DeleteAsync(int id);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ public interface IPersonnelService
|
|||
Task<IEnumerable<Personnel>> GetByUnitAsync(int unitId, bool includeSubordinates = false);
|
||||
Task<Personnel> CreateAsync(Personnel personnel);
|
||||
Task<Personnel> UpdateAsync(Personnel personnel);
|
||||
Task<Personnel> ApproveAsync(int personnelId, int approvedByUnitId, PersonnelLevel level);
|
||||
Task<Personnel> ApproveAsync(int personnelId, int approvedByUnitId, PersonnelLevel? level = null);
|
||||
Task<Personnel> RejectAsync(int personnelId, int reviewedByUserId);
|
||||
Task DeleteAsync(int id);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
-- 创建 ConsumptionReports 表
|
||||
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ConsumptionReports]') AND type in (N'U'))
|
||||
BEGIN
|
||||
CREATE TABLE [dbo].[ConsumptionReports](
|
||||
[Id] [int] IDENTITY(1,1) NOT NULL,
|
||||
[AllocationDistributionId] [int] NOT NULL,
|
||||
[ReportedAmount] [decimal](18, 2) NOT NULL,
|
||||
[CumulativeAmount] [decimal](18, 2) NOT NULL,
|
||||
[Remarks] [nvarchar](500) NULL,
|
||||
[ReportedByUserId] [int] NOT NULL,
|
||||
[ReportedAt] [datetime2](7) NOT NULL,
|
||||
CONSTRAINT [PK_ConsumptionReports] PRIMARY KEY CLUSTERED ([Id] ASC)
|
||||
)
|
||||
|
||||
CREATE NONCLUSTERED INDEX [IX_ConsumptionReports_AllocationDistributionId] ON [dbo].[ConsumptionReports]
|
||||
(
|
||||
[AllocationDistributionId] ASC
|
||||
)
|
||||
|
||||
CREATE NONCLUSTERED INDEX [IX_ConsumptionReports_ReportedAt] ON [dbo].[ConsumptionReports]
|
||||
(
|
||||
[ReportedAt] ASC
|
||||
)
|
||||
|
||||
CREATE NONCLUSTERED INDEX [IX_ConsumptionReports_ReportedByUserId] ON [dbo].[ConsumptionReports]
|
||||
(
|
||||
[ReportedByUserId] ASC
|
||||
)
|
||||
|
||||
ALTER TABLE [dbo].[ConsumptionReports] WITH CHECK ADD CONSTRAINT [FK_ConsumptionReports_AllocationDistributions_AllocationDistributionId] FOREIGN KEY([AllocationDistributionId])
|
||||
REFERENCES [dbo].[AllocationDistributions] ([Id])
|
||||
ON DELETE CASCADE
|
||||
|
||||
ALTER TABLE [dbo].[ConsumptionReports] CHECK CONSTRAINT [FK_ConsumptionReports_AllocationDistributions_AllocationDistributionId]
|
||||
|
||||
ALTER TABLE [dbo].[ConsumptionReports] WITH CHECK ADD CONSTRAINT [FK_ConsumptionReports_UserAccounts_ReportedByUserId] FOREIGN KEY([ReportedByUserId])
|
||||
REFERENCES [dbo].[UserAccounts] ([Id])
|
||||
|
||||
ALTER TABLE [dbo].[ConsumptionReports] CHECK CONSTRAINT [FK_ConsumptionReports_UserAccounts_ReportedByUserId]
|
||||
|
||||
PRINT 'ConsumptionReports table created successfully'
|
||||
END
|
||||
ELSE
|
||||
BEGIN
|
||||
PRINT 'ConsumptionReports table already exists'
|
||||
END
|
||||
GO
|
||||
622
src/MilitaryTrainingManagement/migration.sql
Normal file
622
src/MilitaryTrainingManagement/migration.sql
Normal file
|
|
@ -0,0 +1,622 @@
|
|||
IF OBJECT_ID(N'[__EFMigrationsHistory]') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE [__EFMigrationsHistory] (
|
||||
[MigrationId] nvarchar(150) NOT NULL,
|
||||
[ProductVersion] nvarchar(32) NOT NULL,
|
||||
CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId])
|
||||
);
|
||||
END;
|
||||
GO
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE TABLE [MaterialCategories] (
|
||||
[Id] int NOT NULL IDENTITY,
|
||||
[Name] nvarchar(50) NOT NULL,
|
||||
[Description] nvarchar(200) NULL,
|
||||
[IsActive] bit NOT NULL,
|
||||
[CreatedAt] datetime2 NOT NULL,
|
||||
[SortOrder] int NOT NULL,
|
||||
CONSTRAINT [PK_MaterialCategories] PRIMARY KEY ([Id])
|
||||
);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE TABLE [OrganizationalUnits] (
|
||||
[Id] int NOT NULL IDENTITY,
|
||||
[Name] nvarchar(100) NOT NULL,
|
||||
[Level] int NOT NULL,
|
||||
[ParentId] int NULL,
|
||||
[CreatedAt] datetime2 NOT NULL,
|
||||
CONSTRAINT [PK_OrganizationalUnits] PRIMARY KEY ([Id]),
|
||||
CONSTRAINT [FK_OrganizationalUnits_OrganizationalUnits_ParentId] FOREIGN KEY ([ParentId]) REFERENCES [OrganizationalUnits] ([Id]) ON DELETE NO ACTION
|
||||
);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE TABLE [MaterialAllocations] (
|
||||
[Id] int NOT NULL IDENTITY,
|
||||
[Category] nvarchar(100) NOT NULL,
|
||||
[MaterialName] nvarchar(200) NOT NULL,
|
||||
[Unit] nvarchar(50) NOT NULL,
|
||||
[TotalQuota] decimal(18,2) NOT NULL,
|
||||
[CreatedByUnitId] int NOT NULL,
|
||||
[CreatedAt] datetime2 NOT NULL,
|
||||
CONSTRAINT [PK_MaterialAllocations] PRIMARY KEY ([Id]),
|
||||
CONSTRAINT [FK_MaterialAllocations_OrganizationalUnits_CreatedByUnitId] FOREIGN KEY ([CreatedByUnitId]) REFERENCES [OrganizationalUnits] ([Id]) ON DELETE NO ACTION
|
||||
);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE TABLE [Personnel] (
|
||||
[Id] int NOT NULL IDENTITY,
|
||||
[Name] nvarchar(50) NOT NULL,
|
||||
[PhotoPath] nvarchar(max) NULL,
|
||||
[Position] nvarchar(100) NOT NULL,
|
||||
[Rank] nvarchar(50) NOT NULL,
|
||||
[Gender] nvarchar(10) NOT NULL,
|
||||
[IdNumber] nvarchar(18) NOT NULL,
|
||||
[ProfessionalTitle] nvarchar(max) NULL,
|
||||
[EducationLevel] nvarchar(max) NULL,
|
||||
[Age] int NOT NULL,
|
||||
[Height] decimal(5,2) NULL,
|
||||
[ContactInfo] nvarchar(max) NULL,
|
||||
[Hometown] nvarchar(max) NULL,
|
||||
[TrainingParticipation] nvarchar(max) NULL,
|
||||
[Achievements] nvarchar(max) NULL,
|
||||
[SupportingDocuments] nvarchar(max) NULL,
|
||||
[SubmittedByUnitId] int NOT NULL,
|
||||
[ApprovedLevel] int NULL,
|
||||
[ApprovedByUnitId] int NULL,
|
||||
[Status] int NOT NULL,
|
||||
[SubmittedAt] datetime2 NOT NULL,
|
||||
[ApprovedAt] datetime2 NULL,
|
||||
CONSTRAINT [PK_Personnel] PRIMARY KEY ([Id]),
|
||||
CONSTRAINT [FK_Personnel_OrganizationalUnits_ApprovedByUnitId] FOREIGN KEY ([ApprovedByUnitId]) REFERENCES [OrganizationalUnits] ([Id]),
|
||||
CONSTRAINT [FK_Personnel_OrganizationalUnits_SubmittedByUnitId] FOREIGN KEY ([SubmittedByUnitId]) REFERENCES [OrganizationalUnits] ([Id])
|
||||
);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE TABLE [UserAccounts] (
|
||||
[Id] int NOT NULL IDENTITY,
|
||||
[Username] nvarchar(50) NOT NULL,
|
||||
[PasswordHash] nvarchar(max) NOT NULL,
|
||||
[DisplayName] nvarchar(100) NOT NULL,
|
||||
[OrganizationalUnitId] int NOT NULL,
|
||||
[IsActive] bit NOT NULL,
|
||||
[CreatedAt] datetime2 NOT NULL,
|
||||
[LastLoginAt] datetime2 NULL,
|
||||
CONSTRAINT [PK_UserAccounts] PRIMARY KEY ([Id]),
|
||||
CONSTRAINT [FK_UserAccounts_OrganizationalUnits_OrganizationalUnitId] FOREIGN KEY ([OrganizationalUnitId]) REFERENCES [OrganizationalUnits] ([Id]) ON DELETE NO ACTION
|
||||
);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE TABLE [AllocationDistributions] (
|
||||
[Id] int NOT NULL IDENTITY,
|
||||
[AllocationId] int NOT NULL,
|
||||
[TargetUnitId] int NOT NULL,
|
||||
[UnitQuota] decimal(18,2) NOT NULL,
|
||||
[ActualCompletion] decimal(18,2) NULL,
|
||||
[ReportedAt] datetime2 NULL,
|
||||
[ReportedByUserId] int NULL,
|
||||
[IsApproved] bit NOT NULL,
|
||||
[ApprovedAt] datetime2 NULL,
|
||||
[ApprovedByUserId] int NULL,
|
||||
CONSTRAINT [PK_AllocationDistributions] PRIMARY KEY ([Id]),
|
||||
CONSTRAINT [FK_AllocationDistributions_MaterialAllocations_AllocationId] FOREIGN KEY ([AllocationId]) REFERENCES [MaterialAllocations] ([Id]) ON DELETE CASCADE,
|
||||
CONSTRAINT [FK_AllocationDistributions_OrganizationalUnits_TargetUnitId] FOREIGN KEY ([TargetUnitId]) REFERENCES [OrganizationalUnits] ([Id]),
|
||||
CONSTRAINT [FK_AllocationDistributions_UserAccounts_ApprovedByUserId] FOREIGN KEY ([ApprovedByUserId]) REFERENCES [UserAccounts] ([Id]),
|
||||
CONSTRAINT [FK_AllocationDistributions_UserAccounts_ReportedByUserId] FOREIGN KEY ([ReportedByUserId]) REFERENCES [UserAccounts] ([Id])
|
||||
);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE TABLE [ApprovalRequests] (
|
||||
[Id] int NOT NULL IDENTITY,
|
||||
[Type] int NOT NULL,
|
||||
[TargetEntityId] int NOT NULL,
|
||||
[RequestedByUserId] int NOT NULL,
|
||||
[RequestedByUnitId] int NOT NULL,
|
||||
[Reason] nvarchar(500) NOT NULL,
|
||||
[OriginalData] nvarchar(max) NOT NULL,
|
||||
[RequestedChanges] nvarchar(max) NOT NULL,
|
||||
[Status] int NOT NULL,
|
||||
[ReviewedByUserId] int NULL,
|
||||
[ReviewComments] nvarchar(max) NULL,
|
||||
[RequestedAt] datetime2 NOT NULL,
|
||||
[ReviewedAt] datetime2 NULL,
|
||||
CONSTRAINT [PK_ApprovalRequests] PRIMARY KEY ([Id]),
|
||||
CONSTRAINT [FK_ApprovalRequests_OrganizationalUnits_RequestedByUnitId] FOREIGN KEY ([RequestedByUnitId]) REFERENCES [OrganizationalUnits] ([Id]),
|
||||
CONSTRAINT [FK_ApprovalRequests_UserAccounts_RequestedByUserId] FOREIGN KEY ([RequestedByUserId]) REFERENCES [UserAccounts] ([Id]),
|
||||
CONSTRAINT [FK_ApprovalRequests_UserAccounts_ReviewedByUserId] FOREIGN KEY ([ReviewedByUserId]) REFERENCES [UserAccounts] ([Id])
|
||||
);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE TABLE [AuditLogs] (
|
||||
[Id] int NOT NULL IDENTITY,
|
||||
[EntityType] nvarchar(100) NOT NULL,
|
||||
[EntityId] int NOT NULL,
|
||||
[Action] nvarchar(50) NOT NULL,
|
||||
[Description] nvarchar(500) NULL,
|
||||
[OldValues] nvarchar(max) NULL,
|
||||
[NewValues] nvarchar(max) NULL,
|
||||
[ChangedFields] nvarchar(max) NULL,
|
||||
[UserId] int NULL,
|
||||
[OrganizationalUnitId] int NULL,
|
||||
[Timestamp] datetime2 NOT NULL,
|
||||
[IpAddress] nvarchar(50) NULL,
|
||||
[UserAgent] nvarchar(500) NULL,
|
||||
[RequestPath] nvarchar(500) NULL,
|
||||
[IsSuccess] bit NOT NULL,
|
||||
[ErrorMessage] nvarchar(2000) NULL,
|
||||
CONSTRAINT [PK_AuditLogs] PRIMARY KEY ([Id]),
|
||||
CONSTRAINT [FK_AuditLogs_OrganizationalUnits_OrganizationalUnitId] FOREIGN KEY ([OrganizationalUnitId]) REFERENCES [OrganizationalUnits] ([Id]),
|
||||
CONSTRAINT [FK_AuditLogs_UserAccounts_UserId] FOREIGN KEY ([UserId]) REFERENCES [UserAccounts] ([Id])
|
||||
);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE TABLE [PersonnelApprovalHistories] (
|
||||
[Id] int NOT NULL IDENTITY,
|
||||
[PersonnelId] int NOT NULL,
|
||||
[Action] int NOT NULL,
|
||||
[PreviousStatus] int NULL,
|
||||
[NewStatus] int NOT NULL,
|
||||
[PreviousLevel] int NULL,
|
||||
[NewLevel] int NULL,
|
||||
[ReviewedByUserId] int NOT NULL,
|
||||
[ReviewedByUnitId] int NULL,
|
||||
[Comments] nvarchar(max) NULL,
|
||||
[ReviewedAt] datetime2 NOT NULL,
|
||||
CONSTRAINT [PK_PersonnelApprovalHistories] PRIMARY KEY ([Id]),
|
||||
CONSTRAINT [FK_PersonnelApprovalHistories_OrganizationalUnits_ReviewedByUnitId] FOREIGN KEY ([ReviewedByUnitId]) REFERENCES [OrganizationalUnits] ([Id]),
|
||||
CONSTRAINT [FK_PersonnelApprovalHistories_Personnel_PersonnelId] FOREIGN KEY ([PersonnelId]) REFERENCES [Personnel] ([Id]) ON DELETE CASCADE,
|
||||
CONSTRAINT [FK_PersonnelApprovalHistories_UserAccounts_ReviewedByUserId] FOREIGN KEY ([ReviewedByUserId]) REFERENCES [UserAccounts] ([Id])
|
||||
);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_AllocationDistributions_AllocationId] ON [AllocationDistributions] ([AllocationId]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_AllocationDistributions_ApprovedByUserId] ON [AllocationDistributions] ([ApprovedByUserId]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_AllocationDistributions_ReportedByUserId] ON [AllocationDistributions] ([ReportedByUserId]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_AllocationDistributions_TargetUnitId] ON [AllocationDistributions] ([TargetUnitId]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_ApprovalRequests_RequestedByUnitId] ON [ApprovalRequests] ([RequestedByUnitId]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_ApprovalRequests_RequestedByUserId] ON [ApprovalRequests] ([RequestedByUserId]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_ApprovalRequests_ReviewedByUserId] ON [ApprovalRequests] ([ReviewedByUserId]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_AuditLogs_Action] ON [AuditLogs] ([Action]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_AuditLogs_EntityId] ON [AuditLogs] ([EntityId]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_AuditLogs_EntityType] ON [AuditLogs] ([EntityType]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_AuditLogs_OrganizationalUnitId] ON [AuditLogs] ([OrganizationalUnitId]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_AuditLogs_Timestamp] ON [AuditLogs] ([Timestamp]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_AuditLogs_UserId] ON [AuditLogs] ([UserId]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_MaterialAllocations_CreatedByUnitId] ON [MaterialAllocations] ([CreatedByUnitId]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE UNIQUE INDEX [IX_MaterialCategories_Name] ON [MaterialCategories] ([Name]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_OrganizationalUnits_ParentId] ON [OrganizationalUnits] ([ParentId]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_Personnel_ApprovedByUnitId] ON [Personnel] ([ApprovedByUnitId]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE UNIQUE INDEX [IX_Personnel_IdNumber] ON [Personnel] ([IdNumber]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_Personnel_SubmittedByUnitId] ON [Personnel] ([SubmittedByUnitId]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_PersonnelApprovalHistories_PersonnelId] ON [PersonnelApprovalHistories] ([PersonnelId]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_PersonnelApprovalHistories_ReviewedByUnitId] ON [PersonnelApprovalHistories] ([ReviewedByUnitId]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_PersonnelApprovalHistories_ReviewedByUserId] ON [PersonnelApprovalHistories] ([ReviewedByUserId]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_UserAccounts_OrganizationalUnitId] ON [UserAccounts] ([OrganizationalUnitId]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
CREATE UNIQUE INDEX [IX_UserAccounts_Username] ON [UserAccounts] ([Username]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260114141545_AddMaterialCategory'
|
||||
)
|
||||
BEGIN
|
||||
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
|
||||
VALUES (N'20260114141545_AddMaterialCategory', N'8.0.0');
|
||||
END;
|
||||
GO
|
||||
|
||||
COMMIT;
|
||||
GO
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260115063457_UpdatePersonnelFieldsComplete'
|
||||
)
|
||||
BEGIN
|
||||
DROP INDEX [IX_Personnel_IdNumber] ON [Personnel];
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260115063457_UpdatePersonnelFieldsComplete'
|
||||
)
|
||||
BEGIN
|
||||
DECLARE @var0 sysname;
|
||||
SELECT @var0 = [d].[name]
|
||||
FROM [sys].[default_constraints] [d]
|
||||
INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id]
|
||||
WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Personnel]') AND [c].[name] = N'IdNumber');
|
||||
IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Personnel] DROP CONSTRAINT [' + @var0 + '];');
|
||||
ALTER TABLE [Personnel] ALTER COLUMN [IdNumber] nvarchar(50) NULL;
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260115063457_UpdatePersonnelFieldsComplete'
|
||||
)
|
||||
BEGIN
|
||||
ALTER TABLE [Personnel] ADD [BirthDate] nvarchar(max) NULL;
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260115063457_UpdatePersonnelFieldsComplete'
|
||||
)
|
||||
BEGIN
|
||||
ALTER TABLE [Personnel] ADD [EnlistmentDate] nvarchar(max) NULL;
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260115063457_UpdatePersonnelFieldsComplete'
|
||||
)
|
||||
BEGIN
|
||||
ALTER TABLE [Personnel] ADD [Ethnicity] nvarchar(max) NULL;
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260115063457_UpdatePersonnelFieldsComplete'
|
||||
)
|
||||
BEGIN
|
||||
ALTER TABLE [Personnel] ADD [PoliticalStatus] nvarchar(max) NULL;
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260115063457_UpdatePersonnelFieldsComplete'
|
||||
)
|
||||
BEGIN
|
||||
ALTER TABLE [Personnel] ADD [Specialty] nvarchar(max) NULL;
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260115063457_UpdatePersonnelFieldsComplete'
|
||||
)
|
||||
BEGIN
|
||||
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
|
||||
VALUES (N'20260115063457_UpdatePersonnelFieldsComplete', N'8.0.0');
|
||||
END;
|
||||
GO
|
||||
|
||||
COMMIT;
|
||||
GO
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260115152942_AddConsumptionReportTable'
|
||||
)
|
||||
BEGIN
|
||||
ALTER TABLE [UserAccounts] ADD [PlainPassword] nvarchar(max) NULL;
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260115152942_AddConsumptionReportTable'
|
||||
)
|
||||
BEGIN
|
||||
ALTER TABLE [Personnel] ADD [Unit] nvarchar(max) NULL;
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260115152942_AddConsumptionReportTable'
|
||||
)
|
||||
BEGIN
|
||||
CREATE TABLE [ConsumptionReports] (
|
||||
[Id] int NOT NULL IDENTITY,
|
||||
[AllocationDistributionId] int NOT NULL,
|
||||
[ReportedAmount] decimal(18,2) NOT NULL,
|
||||
[CumulativeAmount] decimal(18,2) NOT NULL,
|
||||
[Remarks] nvarchar(500) NULL,
|
||||
[ReportedByUserId] int NOT NULL,
|
||||
[ReportedAt] datetime2 NOT NULL,
|
||||
CONSTRAINT [PK_ConsumptionReports] PRIMARY KEY ([Id]),
|
||||
CONSTRAINT [FK_ConsumptionReports_AllocationDistributions_AllocationDistributionId] FOREIGN KEY ([AllocationDistributionId]) REFERENCES [AllocationDistributions] ([Id]) ON DELETE CASCADE,
|
||||
CONSTRAINT [FK_ConsumptionReports_UserAccounts_ReportedByUserId] FOREIGN KEY ([ReportedByUserId]) REFERENCES [UserAccounts] ([Id])
|
||||
);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260115152942_AddConsumptionReportTable'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_ConsumptionReports_AllocationDistributionId] ON [ConsumptionReports] ([AllocationDistributionId]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260115152942_AddConsumptionReportTable'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_ConsumptionReports_ReportedAt] ON [ConsumptionReports] ([ReportedAt]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260115152942_AddConsumptionReportTable'
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX [IX_ConsumptionReports_ReportedByUserId] ON [ConsumptionReports] ([ReportedByUserId]);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM [__EFMigrationsHistory]
|
||||
WHERE [MigrationId] = N'20260115152942_AddConsumptionReportTable'
|
||||
)
|
||||
BEGIN
|
||||
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
|
||||
VALUES (N'20260115152942_AddConsumptionReportTable', N'8.0.0');
|
||||
END;
|
||||
GO
|
||||
|
||||
COMMIT;
|
||||
GO
|
||||
|
||||
|
|
@ -46,5 +46,19 @@ export const allocationsApi = {
|
|||
async getMyDistributions(): Promise<AllocationDistribution[]> {
|
||||
const response = await apiClient.get<AllocationDistribution[]>('/allocations/my-distributions')
|
||||
return response.data
|
||||
},
|
||||
|
||||
async getConsumptionReports(distributionId: number): Promise<ConsumptionReport[]> {
|
||||
const response = await apiClient.get<ConsumptionReport[]>(`/allocations/distributions/${distributionId}/reports`)
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
|
||||
export interface ConsumptionReport {
|
||||
id: number
|
||||
reportedAmount: number
|
||||
cumulativeAmount: number
|
||||
remarks?: string
|
||||
reportedByUserName?: string
|
||||
reportedAt: string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,9 +40,7 @@ export const personnelApi = {
|
|||
},
|
||||
|
||||
async approve(data: PersonnelApprovalRequest): Promise<Personnel> {
|
||||
const response = await apiClient.post<Personnel>(`/personnel/${data.personnelId}/approve`, {
|
||||
level: data.level
|
||||
})
|
||||
const response = await apiClient.post<Personnel>(`/personnel/${data.personnelId}/approve`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -63,12 +63,21 @@
|
|||
<span class="unit-text">{{ row.unit }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="totalQuota" label="总配额" width="120" align="right">
|
||||
<!-- 师团级显示总配额,团部及以下显示本单位配额 -->
|
||||
<el-table-column v-if="authStore.canCreateAllocations" prop="totalQuota" label="总配额" width="120" align="right">
|
||||
<template #default="{ row }">
|
||||
<span class="quota-value">{{ formatNumber(row.totalQuota) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分配情况" width="150" align="center">
|
||||
<el-table-column v-else prop="unitQuota" label="配额" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="quota-cell">
|
||||
<span class="quota-number">{{ formatNumber(getMyUnitQuota(row)) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 师团级显示分配情况,团部及以下显示消耗情况 -->
|
||||
<el-table-column v-if="authStore.canCreateAllocations" label="分配情况" width="150" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="distribution-info">
|
||||
<span class="dist-count">{{ row.distributions?.length || 0 }} 个单位</span>
|
||||
|
|
@ -81,7 +90,23 @@
|
|||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createdByUnitName" label="创建单位" width="120" show-overflow-tooltip />
|
||||
<el-table-column v-else label="消耗情况" width="200" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="consumption-cell">
|
||||
<div class="consumption-numbers">
|
||||
<span class="completion-num">{{ formatNumber(getMyActualCompletion(row)) }}</span>
|
||||
<span class="separator">/</span>
|
||||
<span class="quota-num">{{ formatNumber(getMyUnitQuota(row)) }}</span>
|
||||
</div>
|
||||
<el-progress
|
||||
:percentage="getMyCompletionPercentage(row)"
|
||||
:stroke-width="8"
|
||||
:status="getProgressStatus(getMyCompletionPercentage(row) / 100)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="authStore.canCreateAllocations" prop="createdByUnitName" label="创建单位" width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="createdAt" label="创建时间" width="160" align="center">
|
||||
<template #default="{ row }">
|
||||
<span class="time-cell">{{ formatDate(row.createdAt) }}</span>
|
||||
|
|
@ -128,39 +153,81 @@
|
|||
<el-dialog
|
||||
v-model="showDistributionDialog"
|
||||
:title="`配额分配详情 - ${selectedAllocation?.materialName || ''}`"
|
||||
width="900px"
|
||||
width="1000px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="distribution-summary">
|
||||
<el-row :gutter="20">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="6">
|
||||
<div class="summary-item">
|
||||
<div class="summary-label">物资类别</div>
|
||||
<div class="summary-value">
|
||||
<el-tag :type="getCategoryTagType(selectedAllocation?.category)" size="small">
|
||||
<el-tag :type="getCategoryTagType(selectedAllocation?.category)" size="large" effect="plain">
|
||||
{{ selectedAllocation?.category }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="summary-item">
|
||||
<div class="summary-label">总配额</div>
|
||||
<div class="summary-value highlight">{{ formatNumber(selectedAllocation?.totalQuota || 0) }} {{ selectedAllocation?.unit }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="summary-item">
|
||||
<div class="summary-label">已分配</div>
|
||||
<div class="summary-value">{{ formatNumber(getTotalDistributed()) }} {{ selectedAllocation?.unit }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="summary-item">
|
||||
<div class="summary-label">分配单位数</div>
|
||||
<div class="summary-value">{{ distributions.length }} 个</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<!-- 师团级显示总配额、已分配、分配单位数 -->
|
||||
<template v-if="authStore.canCreateAllocations">
|
||||
<el-col :span="6">
|
||||
<div class="summary-item">
|
||||
<div class="summary-label">总配额</div>
|
||||
<div class="summary-value highlight">
|
||||
<span class="number">{{ formatNumber(selectedAllocation?.totalQuota || 0) }}</span>
|
||||
<span class="unit">{{ selectedAllocation?.unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="summary-item">
|
||||
<div class="summary-label">已分配</div>
|
||||
<div class="summary-value allocated">
|
||||
<span class="number">{{ formatNumber(getTotalDistributed()) }}</span>
|
||||
<span class="unit">{{ selectedAllocation?.unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="summary-item">
|
||||
<div class="summary-label">分配单位数</div>
|
||||
<div class="summary-value units">
|
||||
<span class="number">{{ distributions.length }}</span>
|
||||
<span class="unit">个</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</template>
|
||||
<!-- 团部及以下显示本单位配额、总消耗、下级单位数 -->
|
||||
<template v-else>
|
||||
<el-col :span="6">
|
||||
<div class="summary-item">
|
||||
<div class="summary-label">本单位配额</div>
|
||||
<div class="summary-value highlight">
|
||||
<span class="number">{{ formatNumber(getTotalDistributed()) }}</span>
|
||||
<span class="unit">{{ selectedAllocation?.unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="summary-item">
|
||||
<div class="summary-label">总消耗</div>
|
||||
<div class="summary-value consumed">
|
||||
<span class="number">{{ formatNumber(getTotalConsumed()) }}</span>
|
||||
<span class="unit">{{ selectedAllocation?.unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="summary-item">
|
||||
<div class="summary-label">下级单位数</div>
|
||||
<div class="summary-value units">
|
||||
<span class="number">{{ distributions.length }}</span>
|
||||
<span class="unit">个</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</template>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
|
|
@ -168,47 +235,66 @@
|
|||
:data="distributions"
|
||||
style="width: 100%"
|
||||
stripe
|
||||
:header-cell-style="{ background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }"
|
||||
:header-cell-style="{ background: '#f5f7fa', color: '#606266', fontWeight: 'bold', fontSize: '14px' }"
|
||||
:row-style="{ height: '60px' }"
|
||||
>
|
||||
<el-table-column prop="targetUnitName" label="目标单位" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<span class="unit-name">{{ row.targetUnitName }}</span>
|
||||
<div class="unit-cell">
|
||||
<el-icon class="unit-icon" :size="16"><OfficeBuilding /></el-icon>
|
||||
<span class="unit-name">{{ row.targetUnitName }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="unitQuota" label="分配配额" width="120" align="right">
|
||||
<el-table-column prop="unitQuota" label="分配配额" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<span class="quota-value">{{ formatNumber(row.unitQuota) }}</span>
|
||||
<div class="quota-cell-dialog">
|
||||
<span class="quota-number-dialog">{{ formatNumber(row.unitQuota) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="actualCompletion" label="实际完成" width="120" align="right">
|
||||
<el-table-column prop="actualCompletion" label="实际完成" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<span :class="['completion-value', row.actualCompletion ? '' : 'no-data']">
|
||||
{{ row.actualCompletion ? formatNumber(row.actualCompletion) : '未上报' }}
|
||||
</span>
|
||||
<div class="completion-cell">
|
||||
<span v-if="row.actualCompletion" class="completion-number">{{ formatNumber(row.actualCompletion) }}</span>
|
||||
<el-tag v-else type="info" size="small" effect="plain">未上报</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="completionRate" label="完成率" width="150" align="center">
|
||||
<el-table-column prop="completionRate" label="完成率" width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="progress-cell">
|
||||
<div class="progress-cell-dialog">
|
||||
<el-progress
|
||||
:percentage="Math.round(row.completionRate * 100)"
|
||||
:status="getProgressStatus(row.completionRate)"
|
||||
:stroke-width="8"
|
||||
:stroke-width="10"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="reportedAt" label="上报时间" width="160" align="center">
|
||||
<el-table-column prop="reportedAt" label="上报时间" width="150" align="center">
|
||||
<template #default="{ row }">
|
||||
<span class="time-cell">{{ row.reportedAt ? formatDate(row.reportedAt) : '-' }}</span>
|
||||
<div class="time-cell-dialog">
|
||||
<el-icon v-if="row.reportedAt" class="time-icon" :size="14"><Clock /></el-icon>
|
||||
<span>{{ row.reportedAt ? formatDate(row.reportedAt) : '-' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" align="center">
|
||||
<el-table-column label="操作" width="180" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-if="row.actualCompletion"
|
||||
type="warning"
|
||||
size="small"
|
||||
plain
|
||||
@click="handleViewReports(row)"
|
||||
>
|
||||
上报记录
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canReportConsumption(row)"
|
||||
type="primary"
|
||||
size="small"
|
||||
size="small"
|
||||
@click="handleReportConsumption(row)"
|
||||
>
|
||||
上报消耗
|
||||
|
|
@ -217,6 +303,71 @@
|
|||
</el-table-column>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
|
||||
<!-- Consumption Reports Dialog -->
|
||||
<el-dialog
|
||||
v-model="showReportsDialog"
|
||||
:title="`上报记录 - ${selectedDistribution?.targetUnitName || ''}`"
|
||||
width="700px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="reports-summary">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<div class="report-stat">
|
||||
<div class="stat-label">分配配额</div>
|
||||
<div class="stat-value highlight">{{ formatNumber(selectedDistribution?.unitQuota || 0) }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="report-stat">
|
||||
<div class="stat-label">累计消耗</div>
|
||||
<div class="stat-value consumed">{{ formatNumber(selectedDistribution?.actualCompletion || 0) }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="report-stat">
|
||||
<div class="stat-label">上报次数</div>
|
||||
<div class="stat-value">{{ consumptionReports.length }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
:data="consumptionReports"
|
||||
style="width: 100%"
|
||||
v-loading="loadingReports"
|
||||
stripe
|
||||
:header-cell-style="{ background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="reportedAmount" label="本次上报" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<span class="report-amount">+{{ formatNumber(row.reportedAmount) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="cumulativeAmount" label="累计数量" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<span class="cumulative-amount">{{ formatNumber(row.cumulativeAmount) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="reportedByUserName" label="上报人" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.reportedByUserName || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="reportedAt" label="上报时间" width="160" align="center">
|
||||
<template #default="{ row }">
|
||||
<span class="time-cell">{{ formatDate(row.reportedAt) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<template v-if="consumptionReports.length === 0 && !loadingReports">
|
||||
<el-empty description="暂无上报记录" />
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -224,7 +375,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, Setting } from '@element-plus/icons-vue'
|
||||
import { Plus, Search, View, Edit, Delete, Box, Setting, OfficeBuilding, Clock } from '@element-plus/icons-vue'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { allocationsApi } from '@/api'
|
||||
import type { MaterialAllocation, AllocationDistribution } from '@/types'
|
||||
|
|
@ -234,11 +385,15 @@ const authStore = useAuthStore()
|
|||
|
||||
const allocations = ref<MaterialAllocation[]>([])
|
||||
const distributions = ref<AllocationDistribution[]>([])
|
||||
const consumptionReports = ref<any[]>([])
|
||||
const loading = ref(false)
|
||||
const loadingReports = ref(false)
|
||||
const showDistributionDialog = ref(false)
|
||||
const showReportsDialog = ref(false)
|
||||
const searchKeyword = ref('')
|
||||
const categoryFilter = ref('')
|
||||
const selectedAllocation = ref<MaterialAllocation | null>(null)
|
||||
const selectedDistribution = ref<AllocationDistribution | null>(null)
|
||||
|
||||
const pagination = reactive({
|
||||
pageNumber: 1,
|
||||
|
|
@ -259,7 +414,13 @@ const filteredAllocations = computed(() => {
|
|||
})
|
||||
|
||||
function formatDate(dateStr: string): string {
|
||||
return new Date(dateStr).toLocaleString('zh-CN')
|
||||
const date = new Date(dateStr)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
return `${year}/${month}/${day} ${hours}:${minutes}`
|
||||
}
|
||||
|
||||
function formatNumber(num: number): string {
|
||||
|
|
@ -288,14 +449,48 @@ function getDistributionPercentage(allocation: MaterialAllocation): number {
|
|||
return Math.round((distributed / allocation.totalQuota) * 100)
|
||||
}
|
||||
|
||||
function getMyUnitQuota(allocation: MaterialAllocation): number {
|
||||
if (!authStore.user || !allocation.distributions) return 0
|
||||
// 先查找分配给当前单位的配额
|
||||
let myDistribution = allocation.distributions.find(d => d.targetUnitId === authStore.user?.organizationalUnitId)
|
||||
// 如果没有,则取第一个分配记录(上级单位的配额)
|
||||
if (!myDistribution && allocation.distributions.length > 0) {
|
||||
myDistribution = allocation.distributions[0]
|
||||
}
|
||||
return myDistribution?.unitQuota || 0
|
||||
}
|
||||
|
||||
function getMyActualCompletion(allocation: MaterialAllocation): number {
|
||||
if (!authStore.user || !allocation.distributions) return 0
|
||||
// 先查找分配给当前单位的配额
|
||||
let myDistribution = allocation.distributions.find(d => d.targetUnitId === authStore.user?.organizationalUnitId)
|
||||
// 如果没有,则取第一个分配记录(上级单位的配额)
|
||||
if (!myDistribution && allocation.distributions.length > 0) {
|
||||
myDistribution = allocation.distributions[0]
|
||||
}
|
||||
return myDistribution?.actualCompletion || 0
|
||||
}
|
||||
|
||||
function getMyCompletionPercentage(allocation: MaterialAllocation): number {
|
||||
const quota = getMyUnitQuota(allocation)
|
||||
if (quota === 0) return 0
|
||||
const completion = getMyActualCompletion(allocation)
|
||||
return Math.round((completion / quota) * 100)
|
||||
}
|
||||
|
||||
function getTotalDistributed(): number {
|
||||
return distributions.value.reduce((sum, d) => sum + (d.unitQuota || 0), 0)
|
||||
}
|
||||
|
||||
function getTotalConsumed(): number {
|
||||
return distributions.value.reduce((sum, d) => sum + (d.actualCompletion || 0), 0)
|
||||
}
|
||||
|
||||
function canReportConsumption(distribution: AllocationDistribution): boolean {
|
||||
// 只有当前用户所属单位的分配才能上报消耗
|
||||
// 当前用户所属单位或其上级单位的分配都可以上报消耗
|
||||
if (!authStore.user) return false
|
||||
return distribution.targetUnitId === authStore.user.organizationalUnitId
|
||||
// 营部及以下账号可以上报所属团的配额
|
||||
return true
|
||||
}
|
||||
|
||||
function handleReportConsumption(distribution: AllocationDistribution) {
|
||||
|
|
@ -309,6 +504,21 @@ function handleReportConsumption(distribution: AllocationDistribution) {
|
|||
})
|
||||
}
|
||||
|
||||
async function handleViewReports(distribution: AllocationDistribution) {
|
||||
selectedDistribution.value = distribution
|
||||
showReportsDialog.value = true
|
||||
loadingReports.value = true
|
||||
try {
|
||||
const reports = await allocationsApi.getConsumptionReports(distribution.id)
|
||||
consumptionReports.value = reports
|
||||
} catch {
|
||||
ElMessage.error('加载上报记录失败')
|
||||
consumptionReports.value = []
|
||||
} finally {
|
||||
loadingReports.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
pagination.pageNumber = 1
|
||||
}
|
||||
|
|
@ -424,6 +634,20 @@ onMounted(() => {
|
|||
font-size: 14px;
|
||||
}
|
||||
|
||||
.quota-cell {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.quota-number {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #409EFF;
|
||||
font-family: 'Arial', sans-serif;
|
||||
}
|
||||
|
||||
.distribution-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -435,6 +659,39 @@ onMounted(() => {
|
|||
color: #606266;
|
||||
}
|
||||
|
||||
.consumption-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.consumption-numbers {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 4px;
|
||||
font-family: 'Arial', sans-serif;
|
||||
}
|
||||
|
||||
.completion-num {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #67C23A;
|
||||
}
|
||||
|
||||
.separator {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.quota-num {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.time-cell {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
|
|
@ -458,35 +715,127 @@ onMounted(() => {
|
|||
|
||||
/* Distribution Dialog Styles */
|
||||
.distribution-summary {
|
||||
background: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 20px;
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #e8eef5 100%);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
text-align: center;
|
||||
padding: 12px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.summary-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.summary-value {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.summary-value.highlight {
|
||||
.summary-value .number {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
font-family: 'Arial', sans-serif;
|
||||
}
|
||||
|
||||
.summary-value .unit {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.summary-value.highlight .number {
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
.summary-value.allocated .number {
|
||||
color: #67C23A;
|
||||
}
|
||||
|
||||
.summary-value.consumed .number {
|
||||
color: #F56C6C;
|
||||
}
|
||||
|
||||
.summary-value.units .number {
|
||||
color: #E6A23C;
|
||||
}
|
||||
|
||||
.unit-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.unit-icon {
|
||||
color: #409EFF;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.unit-name {
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.quota-cell-dialog {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.quota-number-dialog {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #409EFF;
|
||||
font-family: 'Arial', sans-serif;
|
||||
}
|
||||
|
||||
.completion-cell {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.completion-number {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #67C23A;
|
||||
font-family: 'Arial', sans-serif;
|
||||
}
|
||||
|
||||
.progress-cell-dialog {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.time-cell-dialog {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
color: #606266;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.time-icon {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.completion-value {
|
||||
|
|
@ -518,6 +867,73 @@ onMounted(() => {
|
|||
|
||||
:deep(.el-dialog__header) {
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
padding-bottom: 16px;
|
||||
padding: 20px 24px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__body) {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
:deep(.consumption-cell .el-progress) {
|
||||
width: 100%;
|
||||
max-width: 140px;
|
||||
}
|
||||
|
||||
:deep(.consumption-cell .el-progress__text) {
|
||||
font-size: 13px !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.progress-cell-dialog .el-progress__text) {
|
||||
font-size: 14px !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Reports Dialog Styles */
|
||||
.reports-summary {
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #e8eef5 100%);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.report-stat {
|
||||
text-align: center;
|
||||
padding: 12px;
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
font-family: 'Arial', sans-serif;
|
||||
}
|
||||
|
||||
.stat-value.highlight {
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
.stat-value.consumed {
|
||||
color: #F56C6C;
|
||||
}
|
||||
|
||||
.report-amount {
|
||||
font-weight: 600;
|
||||
color: #67C23A;
|
||||
}
|
||||
|
||||
.cumulative-amount {
|
||||
font-weight: 600;
|
||||
color: #409EFF;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -40,9 +40,14 @@
|
|||
<el-descriptions-item label="分配配额">
|
||||
<span class="quota-value">{{ formatNumber(distribution.unitQuota) }} {{ allocation?.unit }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="当前完成">
|
||||
<el-descriptions-item label="已上报数量">
|
||||
<span :class="['completion-value', distribution.actualCompletion ? '' : 'no-data']">
|
||||
{{ distribution.actualCompletion ? formatNumber(distribution.actualCompletion) : '未上报' }}
|
||||
{{ distribution.actualCompletion ? formatNumber(distribution.actualCompletion) : '0' }} {{ allocation?.unit }}
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="剩余配额">
|
||||
<span class="remaining-value">
|
||||
{{ formatNumber((distribution.unitQuota || 0) - (distribution.actualCompletion || 0)) }} {{ allocation?.unit }}
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
|
@ -50,7 +55,17 @@
|
|||
|
||||
<!-- 上报表单 -->
|
||||
<div class="form-section">
|
||||
<h3 class="section-title">消耗数据</h3>
|
||||
<h3 class="section-title">本次消耗上报</h3>
|
||||
<el-alert
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 20px"
|
||||
>
|
||||
<template #title>
|
||||
<span>注意:本次上报数量将累加到已上报总数中</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
|
|
@ -58,11 +73,11 @@
|
|||
label-width="120px"
|
||||
@submit.prevent="handleSubmit"
|
||||
>
|
||||
<el-form-item label="实际完成数量" prop="actualCompletion">
|
||||
<el-form-item label="本次上报数量" prop="actualCompletion">
|
||||
<el-input-number
|
||||
v-model="form.actualCompletion"
|
||||
:min="0"
|
||||
:max="distribution.unitQuota"
|
||||
:max="(distribution.unitQuota || 0) - (distribution.actualCompletion || 0)"
|
||||
:precision="2"
|
||||
:step="1"
|
||||
style="width: 300px"
|
||||
|
|
@ -70,6 +85,16 @@
|
|||
<span class="unit-hint">{{ allocation?.unit }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="上报后总数">
|
||||
<div class="total-after-report">
|
||||
<span class="total-number">{{ formatNumber((distribution.actualCompletion || 0) + (form.actualCompletion || 0)) }}</span>
|
||||
<span class="unit-text">{{ allocation?.unit }}</span>
|
||||
<span class="separator">/</span>
|
||||
<span class="quota-number">{{ formatNumber(distribution.unitQuota || 0) }}</span>
|
||||
<span class="unit-text">{{ allocation?.unit }}</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="完成率">
|
||||
<div class="completion-rate">
|
||||
<el-progress
|
||||
|
|
@ -107,19 +132,45 @@
|
|||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 历史记录 -->
|
||||
<div v-if="distribution.reportedAt" class="history-section">
|
||||
<!-- 上报历史 -->
|
||||
<div class="history-section">
|
||||
<h3 class="section-title">上报历史</h3>
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
<el-table
|
||||
v-if="consumptionReports.length > 0"
|
||||
:data="consumptionReports"
|
||||
stripe
|
||||
:header-cell-style="{ background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }"
|
||||
>
|
||||
<template #title>
|
||||
<span>上次上报时间: {{ formatDate(distribution.reportedAt) }}</span>
|
||||
</template>
|
||||
<div>上次上报数量: {{ formatNumber(distribution.actualCompletion || 0) }} {{ allocation?.unit }}</div>
|
||||
</el-alert>
|
||||
<el-table-column label="上报时间" width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="time-cell">
|
||||
<el-icon class="time-icon" :size="14"><Clock /></el-icon>
|
||||
<span>{{ formatDate(row.reportedAt) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="本次上报" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<span class="reported-amount">+{{ formatNumber(row.reportedAmount) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="累计数量" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<span class="cumulative-amount">{{ formatNumber(row.cumulativeAmount) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="上报人" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.reportedByUserName || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<span class="remarks-text">{{ row.remarks || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-else description="暂无上报记录" :image-size="80" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -132,8 +183,8 @@
|
|||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
|
||||
import { DocumentAdd, Back, Check, Close } from '@element-plus/icons-vue'
|
||||
import { allocationsApi } from '@/api'
|
||||
import { DocumentAdd, Back, Check, Close, Clock } from '@element-plus/icons-vue'
|
||||
import { allocationsApi, type ConsumptionReport } from '@/api'
|
||||
import type { MaterialAllocation, AllocationDistribution } from '@/types'
|
||||
|
||||
const router = useRouter()
|
||||
|
|
@ -143,6 +194,7 @@ const loading = ref(false)
|
|||
const submitting = ref(false)
|
||||
const allocation = ref<MaterialAllocation | null>(null)
|
||||
const distribution = ref<AllocationDistribution | null>(null)
|
||||
const consumptionReports = ref<ConsumptionReport[]>([])
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
const form = reactive({
|
||||
|
|
@ -152,17 +204,18 @@ const form = reactive({
|
|||
|
||||
const rules: FormRules = {
|
||||
actualCompletion: [
|
||||
{ required: true, message: '请输入实际完成数量', trigger: 'blur' },
|
||||
{ required: true, message: '请输入本次上报数量', trigger: 'blur' },
|
||||
{
|
||||
type: 'number',
|
||||
min: 0,
|
||||
message: '数量不能小于0',
|
||||
min: 0.01,
|
||||
message: '数量必须大于0',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
validator: (_rule, value, callback) => {
|
||||
if (value > (distribution.value?.unitQuota || 0)) {
|
||||
callback(new Error(`数量不能超过分配配额 ${distribution.value?.unitQuota}`))
|
||||
const remaining = (distribution.value?.unitQuota || 0) - (distribution.value?.actualCompletion || 0)
|
||||
if (value > remaining) {
|
||||
callback(new Error(`本次上报数量不能超过剩余配额 ${remaining}`))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
|
|
@ -191,8 +244,9 @@ function getCategoryTagType(category?: string): string {
|
|||
}
|
||||
|
||||
function getCompletionPercentage(): number {
|
||||
if (!distribution.value || !form.actualCompletion) return 0
|
||||
return Math.round((form.actualCompletion / distribution.value.unitQuota) * 100)
|
||||
if (!distribution.value) return 0
|
||||
const total = (distribution.value.actualCompletion || 0) + (form.actualCompletion || 0)
|
||||
return Math.round((total / distribution.value.unitQuota) * 100)
|
||||
}
|
||||
|
||||
function getProgressStatus(): string {
|
||||
|
|
@ -212,8 +266,10 @@ async function handleSubmit() {
|
|||
try {
|
||||
await formRef.value.validate()
|
||||
|
||||
const newTotal = (distribution.value?.actualCompletion || 0) + form.actualCompletion
|
||||
|
||||
await ElMessageBox.confirm(
|
||||
`确认上报实际完成数量为 ${form.actualCompletion} ${allocation.value?.unit} 吗?`,
|
||||
`本次上报数量:${form.actualCompletion} ${allocation.value?.unit}\n上报后总数:${newTotal} ${allocation.value?.unit}\n\n确认提交吗?`,
|
||||
'确认上报',
|
||||
{
|
||||
type: 'warning',
|
||||
|
|
@ -226,8 +282,9 @@ async function handleSubmit() {
|
|||
|
||||
if (!distribution.value) return
|
||||
|
||||
// 累加到已有数量
|
||||
await allocationsApi.updateDistribution(distribution.value.id, {
|
||||
actualCompletion: form.actualCompletion
|
||||
actualCompletion: newTotal
|
||||
})
|
||||
|
||||
ElMessage.success('上报成功')
|
||||
|
|
@ -265,10 +322,11 @@ async function loadData() {
|
|||
return
|
||||
}
|
||||
|
||||
// 如果已有上报数据,填充表单
|
||||
if (distribution.value.actualCompletion) {
|
||||
form.actualCompletion = distribution.value.actualCompletion
|
||||
}
|
||||
// 不再自动填充已上报数据,每次都是新上报
|
||||
form.actualCompletion = 0
|
||||
|
||||
// 加载上报历史记录
|
||||
await loadConsumptionReports(distributionId)
|
||||
} catch {
|
||||
ElMessage.error('加载数据失败')
|
||||
router.back()
|
||||
|
|
@ -277,6 +335,14 @@ async function loadData() {
|
|||
}
|
||||
}
|
||||
|
||||
async function loadConsumptionReports(distributionId: number) {
|
||||
try {
|
||||
consumptionReports.value = await allocationsApi.getConsumptionReports(distributionId)
|
||||
} catch {
|
||||
console.error('加载上报历史失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
|
|
@ -354,6 +420,42 @@ onMounted(() => {
|
|||
font-weight: normal;
|
||||
}
|
||||
|
||||
.remaining-value {
|
||||
font-weight: 600;
|
||||
color: #E6A23C;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.total-after-report {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 6px;
|
||||
font-family: 'Arial', sans-serif;
|
||||
}
|
||||
|
||||
.total-number {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #67C23A;
|
||||
}
|
||||
|
||||
.quota-number {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.unit-text {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.separator {
|
||||
font-size: 18px;
|
||||
color: #909399;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.unit-hint {
|
||||
margin-left: 12px;
|
||||
color: #909399;
|
||||
|
|
@ -390,4 +492,40 @@ onMounted(() => {
|
|||
:deep(.el-form-item__label) {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.time-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
color: #606266;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.time-icon {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.reported-amount {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #67C23A;
|
||||
font-family: 'Arial', sans-serif;
|
||||
}
|
||||
|
||||
.cumulative-amount {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #409EFF;
|
||||
font-family: 'Arial', sans-serif;
|
||||
}
|
||||
|
||||
.remarks-text {
|
||||
color: #606266;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.history-section :deep(.el-table) {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@
|
|||
<span class="time-cell">{{ formatDate(row.submittedAt) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" fixed="right" align="center">
|
||||
<el-table-column label="操作" width="220" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="action-buttons">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
|
|
@ -96,12 +96,17 @@
|
|||
<el-icon><Check /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip v-if="row.status === 'Approved' && authStore.canApprove && canUpgrade(row)" content="向上申报" placement="top">
|
||||
<el-button type="warning" link size="small" @click="handleUpgrade(row)">
|
||||
<el-icon><Top /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip v-if="row.status === 'Pending'" content="编辑" placement="top">
|
||||
<el-button type="warning" link size="small" @click="handleEdit(row)">
|
||||
<el-icon><Edit /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-tooltip v-if="row.status === 'Pending'" content="删除" placement="top">
|
||||
<el-button type="danger" link size="small" @click="handleDelete(row)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
|
|
@ -134,15 +139,7 @@
|
|||
<el-descriptions-item label="军衔">{{ selectedPerson?.rank }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属单位">{{ selectedPerson?.submittedByUnitName }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-form-item label="人员等级" style="margin-top: 20px">
|
||||
<el-select v-model="approvalForm.level" placeholder="请选择人员等级" style="width: 100%">
|
||||
<el-option label="师级人才" value="Division" />
|
||||
<el-option label="团级人才" value="Regiment" />
|
||||
<el-option label="营级人才" value="Battalion" />
|
||||
<el-option label="连级人才" value="Company" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="审批意见">
|
||||
<el-form-item label="审批意见" style="margin-top: 20px">
|
||||
<el-input v-model="approvalForm.comments" type="textarea" rows="3" placeholder="请输入审批意见(可选)" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
|
@ -163,7 +160,7 @@
|
|||
import { ref, reactive, watch, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Search, View, Edit, Delete, Check, Close, User } from '@element-plus/icons-vue'
|
||||
import { Plus, Search, View, Edit, Delete, Check, Close, User, Top } from '@element-plus/icons-vue'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { personnelApi } from '@/api'
|
||||
import type { Personnel } from '@/types'
|
||||
|
|
@ -272,6 +269,24 @@ function handleEdit(person: Personnel) {
|
|||
router.push(`/personnel/${person.id}/edit`)
|
||||
}
|
||||
|
||||
function canUpgrade(person: Personnel): boolean {
|
||||
// 检查当前用户单位层级是否高于人员已审批的等级
|
||||
if (!authStore.user || !person.approvedLevel) return false
|
||||
// 师级人才不能再向上申报
|
||||
if (person.approvedLevel === PersonnelLevel.Division) return false
|
||||
// 用户单位层级必须高于人员当前等级(数值越小层级越高)
|
||||
const userLevel = authStore.user.organizationalLevel
|
||||
const personnelLevel = person.approvedLevel
|
||||
// Division=1, Regiment=2, Battalion=3, Company=4
|
||||
const levelOrder: Record<string, number> = {
|
||||
'Division': 1,
|
||||
'Regiment': 2,
|
||||
'Battalion': 3,
|
||||
'Company': 4
|
||||
}
|
||||
return levelOrder[userLevel] < levelOrder[personnelLevel]
|
||||
}
|
||||
|
||||
function handleApprove(person: Personnel) {
|
||||
selectedPerson.value = person
|
||||
approvalForm.comments = ''
|
||||
|
|
@ -279,6 +294,13 @@ function handleApprove(person: Personnel) {
|
|||
showApprovalDialog.value = true
|
||||
}
|
||||
|
||||
function handleUpgrade(person: Personnel) {
|
||||
// 向上申报使用相同的审批流程
|
||||
selectedPerson.value = person
|
||||
approvalForm.comments = ''
|
||||
showApprovalDialog.value = true
|
||||
}
|
||||
|
||||
async function submitApproval(approved: boolean) {
|
||||
if (!selectedPerson.value) return
|
||||
|
||||
|
|
@ -288,8 +310,7 @@ async function submitApproval(approved: boolean) {
|
|||
await personnelApi.approve({
|
||||
personnelId: selectedPerson.value.id,
|
||||
approved: true,
|
||||
comments: approvalForm.comments,
|
||||
level: approvalForm.level
|
||||
comments: approvalForm.comments
|
||||
})
|
||||
ElMessage.success('审批通过')
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user