From 355087e0f0ba31a624c73ccf30421a8909e01852 Mon Sep 17 00:00:00 2001 From: 18631081161 <2088094923@qq.com> Date: Sat, 17 Jan 2026 15:39:37 +0800 Subject: [PATCH] =?UTF-8?q?bug=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/AllocationsController.cs | 70 +++++++- .../Controllers/ApprovalsController.cs | 34 +++- .../ConsumptionChangeRequestsController.cs | 55 +++++- .../MaterialCategoriesController.cs | 50 +++++- .../Controllers/OrganizationsController.cs | 81 ++++++++- .../Controllers/PersonnelController.cs | 159 +++++++++++++++++- .../Implementations/PersonnelService.cs | 39 ++++- .../Services/Interfaces/IPersonnelService.cs | 5 + .../5361d03e-b068-452a-80c4-de732c36dc20.jpg | Bin 0 -> 8689 bytes .../8e590f7d-3b77-4744-8749-3b3d25feb14c.jpg | Bin 0 -> 7490 bytes .../3d15bd43-20e9-432a-bba3-ab1b224c0c92.jpg | Bin 0 -> 8689 bytes .../3fccae6b-1d22-4612-bdf7-1fb425a8a0fa.jpg | Bin 0 -> 7490 bytes src/frontend/src/api/personnel.ts | 6 + src/frontend/src/views/AuditLogs.vue | 60 ++++++- .../src/views/personnel/PersonnelForm.vue | 4 + .../src/views/personnel/PersonnelList.vue | 107 ++++++++++-- 16 files changed, 635 insertions(+), 35 deletions(-) create mode 100644 src/MilitaryTrainingManagement/wwwroot/uploads/documents/5361d03e-b068-452a-80c4-de732c36dc20.jpg create mode 100644 src/MilitaryTrainingManagement/wwwroot/uploads/documents/8e590f7d-3b77-4744-8749-3b3d25feb14c.jpg create mode 100644 src/MilitaryTrainingManagement/wwwroot/uploads/photos/3d15bd43-20e9-432a-bba3-ab1b224c0c92.jpg create mode 100644 src/MilitaryTrainingManagement/wwwroot/uploads/photos/3fccae6b-1d22-4612-bdf7-1fb425a8a0fa.jpg diff --git a/src/MilitaryTrainingManagement/Controllers/AllocationsController.cs b/src/MilitaryTrainingManagement/Controllers/AllocationsController.cs index 637743a..1c85689 100644 --- a/src/MilitaryTrainingManagement/Controllers/AllocationsController.cs +++ b/src/MilitaryTrainingManagement/Controllers/AllocationsController.cs @@ -15,13 +15,21 @@ public class AllocationsController : BaseApiController { private readonly IAllocationService _allocationService; private readonly IOrganizationalAuthorizationService _authorizationService; + private readonly IAuditService _auditService; public AllocationsController( IAllocationService allocationService, - IOrganizationalAuthorizationService authorizationService) + IOrganizationalAuthorizationService authorizationService, + IAuditService auditService) { _allocationService = allocationService; _authorizationService = authorizationService; + _auditService = auditService; + } + + private string? GetClientIpAddress() + { + return HttpContext.Connection.RemoteIpAddress?.ToString(); } /// @@ -202,6 +210,7 @@ public class AllocationsController : BaseApiController public async Task Create([FromBody] CreateAllocationRequest request) { var unitId = GetCurrentUnitId(); + var userId = GetCurrentUserId(); if (unitId == null) return Unauthorized(new { message = "无法获取用户组织信息" }); @@ -219,6 +228,16 @@ public class AllocationsController : BaseApiController unitId.Value, request.GetDistributionsDictionary()); + // 记录审计日志 + await _auditService.LogApprovalAsync( + "MaterialAllocation", + allocation.Id, + "Create", + $"创建物资配额:{request.Category} - {request.MaterialName},总配额:{request.TotalQuota}{request.Unit}", + userId, + unitId, + GetClientIpAddress()); + return CreatedAtAction(nameof(GetById), new { id = allocation.Id }, MapToResponse(allocation)); } catch (ArgumentException ex) @@ -235,6 +254,7 @@ public class AllocationsController : BaseApiController public async Task Update(int id, [FromBody] UpdateAllocationRequest request) { var unitId = GetCurrentUnitId(); + var userId = GetCurrentUserId(); if (unitId == null) return Unauthorized(new { message = "无法获取用户组织信息" }); @@ -260,6 +280,16 @@ public class AllocationsController : BaseApiController request.Unit, request.TotalQuota); + // 记录审计日志 + await _auditService.LogApprovalAsync( + "MaterialAllocation", + id, + "Update", + $"更新物资配额:{request.Category} - {request.MaterialName},总配额:{request.TotalQuota}{request.Unit}", + userId, + unitId, + GetClientIpAddress()); + return Ok(MapToResponse(await _allocationService.GetByIdAsync(id) ?? allocation)); } catch (ArgumentException ex) @@ -276,6 +306,7 @@ public class AllocationsController : BaseApiController public async Task UpdateDistributions(int id, [FromBody] Dictionary distributions) { var unitId = GetCurrentUnitId(); + var userId = GetCurrentUserId(); if (unitId == null) return Unauthorized(new { message = "无法获取用户组织信息" }); @@ -291,6 +322,17 @@ public class AllocationsController : BaseApiController try { var allocation = await _allocationService.UpdateDistributionsAsync(id, distributions); + + // 记录审计日志 + await _auditService.LogApprovalAsync( + "AllocationDistribution", + id, + "Update", + $"更新配额分配:{existingAllocation.MaterialName},分配给 {distributions.Count} 个单位", + userId, + unitId, + GetClientIpAddress()); + return Ok(MapToResponse(allocation)); } catch (ArgumentException ex) @@ -321,6 +363,17 @@ public class AllocationsController : BaseApiController request.ActualCompletion, request.Remarks); + // 记录审计日志 + var materialName = distribution.Allocation?.MaterialName ?? "未知物资"; + await _auditService.LogApprovalAsync( + "ConsumptionReport", + distributionId, + "Create", + $"上报消耗:{materialName},数量:{request.ActualCompletion},备注:{request.Remarks ?? "无"}", + userId, + unitId, + GetClientIpAddress()); + return Ok(MapDistributionToResponse(distribution)); } catch (UnauthorizedAccessException ex) @@ -341,6 +394,7 @@ public class AllocationsController : BaseApiController public async Task Delete(int id) { var unitId = GetCurrentUnitId(); + var userId = GetCurrentUserId(); if (unitId == null) return Unauthorized(new { message = "无法获取用户组织信息" }); @@ -355,7 +409,21 @@ public class AllocationsController : BaseApiController try { + var materialName = existingAllocation.MaterialName; + var category = existingAllocation.Category; + await _allocationService.DeleteAsync(id); + + // 记录审计日志 + await _auditService.LogApprovalAsync( + "MaterialAllocation", + id, + "Delete", + $"删除物资配额:{category} - {materialName}", + userId, + unitId, + GetClientIpAddress()); + return NoContent(); } catch (ArgumentException ex) diff --git a/src/MilitaryTrainingManagement/Controllers/ApprovalsController.cs b/src/MilitaryTrainingManagement/Controllers/ApprovalsController.cs index 165bcba..b47fca8 100644 --- a/src/MilitaryTrainingManagement/Controllers/ApprovalsController.cs +++ b/src/MilitaryTrainingManagement/Controllers/ApprovalsController.cs @@ -15,13 +15,21 @@ public class ApprovalsController : BaseApiController { private readonly IWorkflowService _workflowService; private readonly IAllocationService _allocationService; + private readonly IAuditService _auditService; public ApprovalsController( IWorkflowService workflowService, - IAllocationService allocationService) + IAllocationService allocationService, + IAuditService auditService) { _workflowService = workflowService; _allocationService = allocationService; + _auditService = auditService; + } + + private string? GetClientIpAddress() + { + return HttpContext.Connection.RemoteIpAddress?.ToString(); } /// @@ -198,12 +206,24 @@ public class ApprovalsController : BaseApiController public async Task Approve(int id, [FromBody] ProcessApprovalRequest request) { var userId = GetCurrentUserId(); + var unitId = GetCurrentUnitId(); if (userId == null) return Unauthorized(new { message = "无法获取用户信息" }); try { var approvalRequest = await _workflowService.ApproveAsync(id, userId.Value, request.Comments); + + // 记录审计日志 + await _auditService.LogApprovalAsync( + "ApprovalRequest", + id, + "Approve", + $"审批通过修改请求:{GetTypeName(approvalRequest.Type)},处理意见:{request.Comments ?? "无"}", + userId, + unitId, + GetClientIpAddress()); + return Ok(MapToResponse(approvalRequest)); } catch (ArgumentException ex) @@ -229,12 +249,24 @@ public class ApprovalsController : BaseApiController public async Task Reject(int id, [FromBody] ProcessApprovalRequest request) { var userId = GetCurrentUserId(); + var unitId = GetCurrentUnitId(); if (userId == null) return Unauthorized(new { message = "无法获取用户信息" }); try { var approvalRequest = await _workflowService.RejectAsync(id, userId.Value, request.Comments); + + // 记录审计日志 + await _auditService.LogApprovalAsync( + "ApprovalRequest", + id, + "Reject", + $"拒绝修改请求:{GetTypeName(approvalRequest.Type)},处理意见:{request.Comments ?? "无"}", + userId, + unitId, + GetClientIpAddress()); + return Ok(MapToResponse(approvalRequest)); } catch (ArgumentException ex) diff --git a/src/MilitaryTrainingManagement/Controllers/ConsumptionChangeRequestsController.cs b/src/MilitaryTrainingManagement/Controllers/ConsumptionChangeRequestsController.cs index 616e95a..80ae34e 100644 --- a/src/MilitaryTrainingManagement/Controllers/ConsumptionChangeRequestsController.cs +++ b/src/MilitaryTrainingManagement/Controllers/ConsumptionChangeRequestsController.cs @@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore; using MilitaryTrainingManagement.Data; using MilitaryTrainingManagement.Models.Entities; using MilitaryTrainingManagement.Models.Enums; +using MilitaryTrainingManagement.Services.Interfaces; namespace MilitaryTrainingManagement.Controllers; @@ -15,13 +16,21 @@ public class ConsumptionChangeRequestsController : BaseApiController { private readonly ApplicationDbContext _context; private readonly ILogger _logger; + private readonly IAuditService _auditService; public ConsumptionChangeRequestsController( ApplicationDbContext context, - ILogger logger) + ILogger logger, + IAuditService auditService) { _context = context; _logger = logger; + _auditService = auditService; + } + + private string? GetClientIpAddress() + { + return HttpContext.Connection.RemoteIpAddress?.ToString(); } /// @@ -38,6 +47,8 @@ public class ConsumptionChangeRequestsController : BaseApiController // 验证消耗记录存在且属于当前单位 var report = await _context.ConsumptionReports .Include(r => r.ReportedByUnit) + .Include(r => r.AllocationDistribution) + .ThenInclude(d => d.Allocation) .FirstOrDefaultAsync(r => r.Id == request.ConsumptionReportId); if (report == null) @@ -71,6 +82,19 @@ public class ConsumptionChangeRequestsController : BaseApiController _logger.LogInformation("单位 {UnitId} 创建了消耗记录 {ReportId} 的{Type}申请", unitId, request.ConsumptionReportId, request.RequestType); + // 记录审计日志 + var requestTypeName = request.RequestType == ChangeRequestType.Delete ? "删除" : "修改"; + var materialName = report.AllocationDistribution?.Allocation?.MaterialName ?? "未知物资"; + + await _auditService.LogApprovalAsync( + "ConsumptionReportChangeRequest", + changeRequest.Id, + "Create", + $"提交消耗记录{requestTypeName}申请,物资:{materialName},申请原因:{request.Reason}", + userId, + unitId, + GetClientIpAddress()); + return Ok(new { message = "申请已提交", id = changeRequest.Id }); } @@ -167,6 +191,7 @@ public class ConsumptionChangeRequestsController : BaseApiController var changeRequest = await _context.ConsumptionReportChangeRequests .Include(r => r.ConsumptionReport) .ThenInclude(cr => cr.AllocationDistribution) + .ThenInclude(d => d.Allocation) .Include(r => r.RequestedByUnit) .FirstOrDefaultAsync(r => r.Id == id); @@ -211,6 +236,20 @@ public class ConsumptionChangeRequestsController : BaseApiController _logger.LogInformation("单位 {UnitId} {Action}了消耗记录删改申请 {RequestId}", unitId, action, id); + // 记录审计日志 + var requestTypeName = changeRequest.RequestType == ChangeRequestType.Delete ? "删除" : "修改"; + var materialName = changeRequest.ConsumptionReport?.AllocationDistribution?.Allocation?.MaterialName ?? "未知物资"; + var reportedByUnitName = changeRequest.RequestedByUnit?.Name ?? "未知单位"; + + await _auditService.LogApprovalAsync( + "ConsumptionReportChangeRequest", + id, + request.Approved ? "Approve" : "Reject", + $"{action}「{reportedByUnitName}」的消耗记录{requestTypeName}申请,物资:{materialName},原因:{changeRequest.Reason},处理意见:{request.Comments ?? "无"}", + userId, + unitId, + GetClientIpAddress()); + return Ok(new { message = $"已{action}该申请" }); } @@ -227,6 +266,7 @@ public class ConsumptionChangeRequestsController : BaseApiController var changeRequest = await _context.ConsumptionReportChangeRequests .Include(r => r.ConsumptionReport) .ThenInclude(cr => cr.AllocationDistribution) + .ThenInclude(d => d.Allocation) .FirstOrDefaultAsync(r => r.Id == id); if (changeRequest == null) @@ -258,6 +298,19 @@ public class ConsumptionChangeRequestsController : BaseApiController _logger.LogInformation("消耗记录 {ReportId} 已被修改,数量从 {OldAmount} 改为 {NewAmount}", report.Id, oldAmount, request.NewAmount); + // 记录审计日志 + var userId = GetCurrentUserId(); + var materialName = distribution.Allocation?.MaterialName ?? "未知物资"; + + await _auditService.LogApprovalAsync( + "ConsumptionReport", + report.Id, + "Update", + $"修改消耗记录,物资:{materialName},数量从 {oldAmount} 改为 {request.NewAmount}", + userId, + unitId, + GetClientIpAddress()); + return Ok(new { message = "修改成功" }); } diff --git a/src/MilitaryTrainingManagement/Controllers/MaterialCategoriesController.cs b/src/MilitaryTrainingManagement/Controllers/MaterialCategoriesController.cs index fc47ffe..7798fc8 100644 --- a/src/MilitaryTrainingManagement/Controllers/MaterialCategoriesController.cs +++ b/src/MilitaryTrainingManagement/Controllers/MaterialCategoriesController.cs @@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore; using MilitaryTrainingManagement.Data; using MilitaryTrainingManagement.Models.DTOs; using MilitaryTrainingManagement.Models.Entities; +using MilitaryTrainingManagement.Services.Interfaces; namespace MilitaryTrainingManagement.Controllers; @@ -14,10 +15,17 @@ namespace MilitaryTrainingManagement.Controllers; public class MaterialCategoriesController : BaseApiController { private readonly ApplicationDbContext _context; + private readonly IAuditService _auditService; - public MaterialCategoriesController(ApplicationDbContext context) + public MaterialCategoriesController(ApplicationDbContext context, IAuditService auditService) { _context = context; + _auditService = auditService; + } + + private string? GetClientIpAddress() + { + return HttpContext.Connection.RemoteIpAddress?.ToString(); } /// @@ -61,6 +69,9 @@ public class MaterialCategoriesController : BaseApiController [HttpPost] public async Task Create([FromBody] CreateMaterialCategoryRequest request) { + var unitId = GetCurrentUnitId(); + var userId = GetCurrentUserId(); + // 检查名称是否已存在 var exists = await _context.MaterialCategories .AnyAsync(c => c.Name == request.Name); @@ -81,6 +92,16 @@ public class MaterialCategoriesController : BaseApiController _context.MaterialCategories.Add(category); await _context.SaveChangesAsync(); + // 记录审计日志 + await _auditService.LogApprovalAsync( + "MaterialCategory", + category.Id, + "Create", + $"创建物资类别:{request.Name}", + userId, + unitId, + GetClientIpAddress()); + return CreatedAtAction(nameof(GetById), new { id = category.Id }, category); } @@ -90,6 +111,9 @@ public class MaterialCategoriesController : BaseApiController [HttpPut("{id}")] public async Task Update(int id, [FromBody] UpdateMaterialCategoryRequest request) { + var unitId = GetCurrentUnitId(); + var userId = GetCurrentUserId(); + var category = await _context.MaterialCategories.FindAsync(id); if (category == null) { @@ -111,6 +135,16 @@ public class MaterialCategoriesController : BaseApiController await _context.SaveChangesAsync(); + // 记录审计日志 + await _auditService.LogApprovalAsync( + "MaterialCategory", + id, + "Update", + $"更新物资类别:{request.Name}", + userId, + unitId, + GetClientIpAddress()); + return Ok(category); } @@ -120,6 +154,9 @@ public class MaterialCategoriesController : BaseApiController [HttpDelete("{id}")] public async Task Delete(int id) { + var unitId = GetCurrentUnitId(); + var userId = GetCurrentUserId(); + var category = await _context.MaterialCategories.FindAsync(id); if (category == null) { @@ -134,9 +171,18 @@ public class MaterialCategoriesController : BaseApiController return BadRequest(new { message = "该类别已被使用,无法删除" }); } + var categoryName = category.Name; _context.MaterialCategories.Remove(category); await _context.SaveChangesAsync(); - return Ok(new { message = "删除成功" }); + // 记录审计日志 + await _auditService.LogApprovalAsync( + "MaterialCategory", + id, + "Delete", + $"删除物资类别:{categoryName}", + userId, + unitId, + GetClientIpAddress()); return Ok(new { message = "删除成功" }); } } diff --git a/src/MilitaryTrainingManagement/Controllers/OrganizationsController.cs b/src/MilitaryTrainingManagement/Controllers/OrganizationsController.cs index 939e3f7..ef3f09f 100644 --- a/src/MilitaryTrainingManagement/Controllers/OrganizationsController.cs +++ b/src/MilitaryTrainingManagement/Controllers/OrganizationsController.cs @@ -17,15 +17,23 @@ public class OrganizationsController : BaseApiController private readonly IOrganizationService _organizationService; private readonly IAuthenticationService _authService; private readonly ApplicationDbContext _context; + private readonly IAuditService _auditService; public OrganizationsController( IOrganizationService organizationService, IAuthenticationService authService, - ApplicationDbContext context) + ApplicationDbContext context, + IAuditService auditService) { _organizationService = organizationService; _authService = authService; _context = context; + _auditService = auditService; + } + + private string? GetClientIpAddress() + { + return HttpContext.Connection.RemoteIpAddress?.ToString(); } [HttpGet] @@ -87,23 +95,69 @@ public class OrganizationsController : BaseApiController [HttpPost] public async Task Create([FromBody] CreateOrganizationRequest request) { + var unitId = GetCurrentUnitId(); + var userId = GetCurrentUserId(); + var unit = await _organizationService.CreateAsync(request.Name, request.Level, request.ParentId); + + // 记录审计日志 + await _auditService.LogApprovalAsync( + "OrganizationalUnit", + unit.Id, + "Create", + $"创建组织单位:{request.Name},级别:{request.Level}", + userId, + unitId, + GetClientIpAddress()); + return CreatedAtAction(nameof(GetById), new { id = unit.Id }, unit); } [HttpPut("{id}")] public async Task Update(int id, [FromBody] UpdateOrganizationRequest request) { + var unitId = GetCurrentUnitId(); + var userId = GetCurrentUserId(); + var unit = await _organizationService.UpdateAsync(id, request.Name); + + // 记录审计日志 + await _auditService.LogApprovalAsync( + "OrganizationalUnit", + id, + "Update", + $"更新组织单位名称为:{request.Name}", + userId, + unitId, + GetClientIpAddress()); + return Ok(unit); } [HttpDelete("{id}")] public async Task Delete(int id) { + var unitId = GetCurrentUnitId(); + var userId = GetCurrentUserId(); + + // 获取组织信息用于日志 + var orgUnit = await _organizationService.GetByIdAsync(id); + var orgName = orgUnit?.Name ?? "未知"; + try { await _organizationService.DeleteAsync(id); + + // 记录审计日志 + await _auditService.LogApprovalAsync( + "OrganizationalUnit", + id, + "Delete", + $"删除组织单位:{orgName}", + userId, + unitId, + GetClientIpAddress()); + return NoContent(); } catch (ArgumentException ex) @@ -178,6 +232,18 @@ public class OrganizationsController : BaseApiController _context.UserAccounts.Add(account); await _context.SaveChangesAsync(); + // 记录审计日志 + var currentUnitId = GetCurrentUnitId(); + var currentUserId = GetCurrentUserId(); + await _auditService.LogApprovalAsync( + "UserAccount", + account.Id, + "Create", + $"创建账户:{request.Username},所属组织:{unit.Name}", + currentUserId, + currentUnitId, + GetClientIpAddress()); + return Ok(new { message = "账户创建成功", username = account.Username }); } @@ -219,9 +285,22 @@ public class OrganizationsController : BaseApiController return BadRequest(new { message = "该账户有审批记录,无法删除" }); } + var username = account.Username; _context.UserAccounts.Remove(account); await _context.SaveChangesAsync(); + // 记录审计日志 + var currentUnitId = GetCurrentUnitId(); + var currentUserId = GetCurrentUserId(); + await _auditService.LogApprovalAsync( + "UserAccount", + accountId, + "Delete", + $"删除账户:{username}", + currentUserId, + currentUnitId, + GetClientIpAddress()); + return Ok(new { message = "账户删除成功" }); } } diff --git a/src/MilitaryTrainingManagement/Controllers/PersonnelController.cs b/src/MilitaryTrainingManagement/Controllers/PersonnelController.cs index 98e250d..1da2269 100644 --- a/src/MilitaryTrainingManagement/Controllers/PersonnelController.cs +++ b/src/MilitaryTrainingManagement/Controllers/PersonnelController.cs @@ -17,15 +17,18 @@ public class PersonnelController : BaseApiController private readonly IPersonnelService _personnelService; private readonly IFileUploadService _fileUploadService; private readonly IOrganizationalAuthorizationService _authorizationService; + private readonly IAuditService _auditService; public PersonnelController( IPersonnelService personnelService, IFileUploadService fileUploadService, - IOrganizationalAuthorizationService authorizationService) + IOrganizationalAuthorizationService authorizationService, + IAuditService auditService) { _personnelService = personnelService; _fileUploadService = fileUploadService; _authorizationService = authorizationService; + _auditService = auditService; } /// @@ -533,8 +536,22 @@ public class PersonnelController : BaseApiController try { + // 获取人员信息用于审计日志 + var personnelBefore = await _personnelService.GetByIdAsync(id); + // 不传递level参数,让服务层根据人员所在单位自动确定等级 var personnel = await _personnelService.ApproveAsync(id, unitId.Value); + + // 记录审计日志 + await _auditService.LogApprovalAsync( + "Personnel", + id, + "Approve", + $"审批通过人才「{personnel.Name}」,等级:{GetLevelDisplayName(personnel.ApprovedLevel)}", + userId, + unitId, + GetClientIpAddress()); + return Ok(MapToResponse(personnel)); } catch (ArgumentException ex) @@ -542,6 +559,23 @@ public class PersonnelController : BaseApiController return BadRequest(new { message = ex.Message }); } } + + private string GetLevelDisplayName(PersonnelLevel? level) + { + return level switch + { + PersonnelLevel.Division => "师级", + PersonnelLevel.Regiment => "团级", + PersonnelLevel.Battalion => "营级", + PersonnelLevel.Company => "连级", + _ => "未定级" + }; + } + + private string? GetClientIpAddress() + { + return HttpContext.Connection.RemoteIpAddress?.ToString(); + } /// /// 拒绝人员 @@ -564,7 +598,21 @@ public class PersonnelController : BaseApiController return StatusCode(403, new { message = "您没有权限拒绝此人员" }); } + // 获取人员信息用于审计日志 + var personnelBefore = await _personnelService.GetByIdAsync(id); + var personnel = await _personnelService.RejectAsync(id, userId.Value, unitId.Value, request.Reason); + + // 记录审计日志 + await _auditService.LogApprovalAsync( + "Personnel", + id, + "Reject", + $"拒绝人才「{personnel.Name}」的审批申请,原因:{request.Reason ?? "无"}", + userId, + unitId, + GetClientIpAddress()); + return Ok(MapToResponse(personnel)); } @@ -777,7 +825,8 @@ public class PersonnelController : BaseApiController public async Task RequestUpgrade(int id) { var unitId = GetCurrentUnitId(); - if (unitId == null) + var userId = GetCurrentUserId(); + if (unitId == null || userId == null) { return Unauthorized(); } @@ -785,6 +834,17 @@ public class PersonnelController : BaseApiController try { var personnel = await _personnelService.RequestUpgradeAsync(id, unitId.Value); + + // 记录审计日志 + await _auditService.LogApprovalAsync( + "Personnel", + id, + "RequestUpgrade", + $"发起向上申报:人才「{personnel.Name}」({GetLevelDisplayName(personnel.ApprovedLevel)})申请升级", + userId, + unitId, + GetClientIpAddress()); + return Ok(MapToResponse(personnel)); } catch (ArgumentException ex) @@ -801,14 +861,30 @@ public class PersonnelController : BaseApiController public async Task ApproveUpgrade(int id) { var unitId = GetCurrentUnitId(); - if (unitId == null) + var userId = GetCurrentUserId(); + if (unitId == null || userId == null) { return Unauthorized(); } try { + // 获取人员信息用于审计日志 + var personnelBefore = await _personnelService.GetByIdAsync(id); + var previousLevel = personnelBefore?.ApprovedLevel; + var personnel = await _personnelService.ApproveUpgradeAsync(id, unitId.Value); + + // 记录审计日志 + await _auditService.LogApprovalAsync( + "Personnel", + id, + "ApproveUpgrade", + $"批准人才「{personnel.Name}」的向上申报,等级从{GetLevelDisplayName(previousLevel)}升级为{GetLevelDisplayName(personnel.ApprovedLevel)}", + userId, + unitId, + GetClientIpAddress()); + return Ok(MapToResponse(personnel)); } catch (ArgumentException ex) @@ -825,14 +901,29 @@ public class PersonnelController : BaseApiController public async Task RejectUpgrade(int id, [FromBody] RejectUpgradeRequest request) { var unitId = GetCurrentUnitId(); - if (unitId == null) + var userId = GetCurrentUserId(); + if (unitId == null || userId == null) { return Unauthorized(); } try { + // 获取人员信息用于审计日志 + var personnelBefore = await _personnelService.GetByIdAsync(id); + var personnel = await _personnelService.RejectUpgradeAsync(id, unitId.Value, request.Comments); + + // 记录审计日志 + await _auditService.LogApprovalAsync( + "Personnel", + id, + "RejectUpgrade", + $"拒绝人才「{personnel.Name}」的向上申报,原因:{request.Comments}", + userId, + unitId, + GetClientIpAddress()); + return Ok(MapToResponse(personnel)); } catch (ArgumentException ex) @@ -849,7 +940,8 @@ public class PersonnelController : BaseApiController public async Task DirectUpgrade(int id) { var unitId = GetCurrentUnitId(); - if (unitId == null) + var userId = GetCurrentUserId(); + if (unitId == null || userId == null) { return Unauthorized(); } @@ -857,6 +949,17 @@ public class PersonnelController : BaseApiController try { var personnel = await _personnelService.DirectUpgradeAsync(id, unitId.Value); + + // 记录审计日志 + await _auditService.LogApprovalAsync( + "Personnel", + id, + "DirectUpgrade", + $"师部直接升级人才「{personnel.Name}」为师级人才", + userId, + unitId, + GetClientIpAddress()); + return Ok(MapToResponse(personnel)); } catch (ArgumentException ex) @@ -865,4 +968,50 @@ public class PersonnelController : BaseApiController } } + /// + /// 重新提交被拒绝的人才审批 + /// + [HttpPost("{id}/resubmit")] + public async Task ResubmitPersonnel(int id) + { + var unitId = GetCurrentUnitId(); + var userId = GetCurrentUserId(); + if (unitId == null || userId == null) + { + return Unauthorized(); + } + + try + { + var personnel = await _personnelService.GetByIdAsync(id); + if (personnel == null) + { + return NotFound(new { message = "人员记录不存在" }); + } + + // 检查权限:只有提交单位可以重新提交 + if (personnel.SubmittedByUnitId != unitId.Value) + { + return Forbid(); + } + + var resubmitted = await _personnelService.ResubmitPersonnelAsync(id); + + // 记录审计日志 + await _auditService.LogApprovalAsync( + "Personnel", + id, + "Resubmit", + $"重新提交审批:人才「{resubmitted.Name}」重新提交审批申请", + userId, + unitId, + GetClientIpAddress()); + + return Ok(MapToResponse(resubmitted)); + } + catch (ArgumentException ex) + { + return BadRequest(new { message = ex.Message }); + } + } } diff --git a/src/MilitaryTrainingManagement/Services/Implementations/PersonnelService.cs b/src/MilitaryTrainingManagement/Services/Implementations/PersonnelService.cs index bde22a7..f60bfa5 100644 --- a/src/MilitaryTrainingManagement/Services/Implementations/PersonnelService.cs +++ b/src/MilitaryTrainingManagement/Services/Implementations/PersonnelService.cs @@ -353,8 +353,8 @@ public class PersonnelService : IPersonnelService if (personnel == null) return false; - // 只有待审批状态的人员可以直接修改 - if (personnel.Status == PersonnelStatus.Pending) + // 待审批状态和已拒绝状态的人员可以直接修改 + if (personnel.Status == PersonnelStatus.Pending || personnel.Status == PersonnelStatus.Rejected) return true; // 已审批的人员需要通过工作流修改 @@ -860,4 +860,39 @@ public class PersonnelService : IPersonnelService return personnel; } + + /// + /// 重新提交被拒绝的人才审批 + /// + public async Task ResubmitPersonnelAsync(int personnelId) + { + var personnel = await _context.Personnel + .Include(p => p.SubmittedByUnit) + .FirstOrDefaultAsync(p => p.Id == personnelId); + + if (personnel == null) + throw new ArgumentException("人员记录不存在"); + + if (personnel.Status != PersonnelStatus.Rejected) + throw new ArgumentException("只有已拒绝的人员才能重新提交"); + + var previousStatus = personnel.Status; + + // 重新提交后,状态变为待审批 + personnel.Status = PersonnelStatus.Pending; + personnel.SubmittedAt = DateTime.UtcNow; // 更新提交时间 + + await _context.SaveChangesAsync(); + + // 记录审批历史 + var userId = await GetUserIdByUnitAsync(personnel.SubmittedByUnitId); + await RecordApprovalHistoryAsync(personnelId, PersonnelApprovalAction.Approved, + previousStatus, PersonnelStatus.Pending, null, null, + userId, personnel.SubmittedByUnitId, "重新提交审批"); + + _logger.LogInformation("人员 {PersonnelId} 已由单位 {UnitId} 重新提交审批", + personnelId, personnel.SubmittedByUnitId); + + return personnel; + } } diff --git a/src/MilitaryTrainingManagement/Services/Interfaces/IPersonnelService.cs b/src/MilitaryTrainingManagement/Services/Interfaces/IPersonnelService.cs index bc7b339..9fb32c9 100644 --- a/src/MilitaryTrainingManagement/Services/Interfaces/IPersonnelService.cs +++ b/src/MilitaryTrainingManagement/Services/Interfaces/IPersonnelService.cs @@ -95,6 +95,11 @@ public interface IPersonnelService /// 师部直接升级团级人才为师级 /// Task DirectUpgradeAsync(int personnelId, int divisionUnitId); + + /// + /// 重新提交被拒绝的人才审批 + /// + Task ResubmitPersonnelAsync(int personnelId); } /// diff --git a/src/MilitaryTrainingManagement/wwwroot/uploads/documents/5361d03e-b068-452a-80c4-de732c36dc20.jpg b/src/MilitaryTrainingManagement/wwwroot/uploads/documents/5361d03e-b068-452a-80c4-de732c36dc20.jpg new file mode 100644 index 0000000000000000000000000000000000000000..055625a173556df8ce71d0d16157586e1dd024a8 GIT binary patch literal 8689 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 literal 0 HcmV?d00001 diff --git a/src/MilitaryTrainingManagement/wwwroot/uploads/documents/8e590f7d-3b77-4744-8749-3b3d25feb14c.jpg b/src/MilitaryTrainingManagement/wwwroot/uploads/documents/8e590f7d-3b77-4744-8749-3b3d25feb14c.jpg new file mode 100644 index 0000000000000000000000000000000000000000..444ac7e186d7bebee293916391761bab557a429e GIT binary patch literal 7490 zcmb7JS2rAh)*YQF(K8smMek*d8jMa5Z4kYWI!g2)TB3KNmmo+Oz1Qfy3#0ci5z&J1 z<=(aK5BTz|0_v!x**_<4WYpXZh=A_$4%t0DxKi3Ew3o9oDBX zrX$g0BzGv#v;8jk)-#j|s$j_cmMKy8KFmYdiGCc4*J6aLI+vtQcPeE)rdet9i76Uw z?eqxfD-JX`G2#_ePQ4TQOG&G-5?y33>?7qV!Y>mpgDQBjYA<;^9ktiEy>X9WZlv>G z1vG@uXoxfQc0QZYP<0&$*(1|CBF7yBoN1H)KIQpDC0e0bI7DQ^SkE})=ra3-Zj9ZS zfD!3_@p86kAXug0HzX)+8vYe*kpRb%k&25;e%esAtq`3`(~-}DGjuA29c zG{L{(A@u23;cPql^{Za*w_oNH+XNaj9jKt32YaUX4Q$;PI<-eo>4axlY~}rh2!O5x zBg>~$&(H-_*}vEwhZ+1aFMv*S;iQ8G(OB!^j{v25wK6KH-T{DTn)ui1_Y-_JX?NG= z`I1h8r5pK@cypgN4&P?Lu=d`Q%iOv?e4yd4XWeD%NT)v5@k`52h#jb*s`82I1BMMF`f_M=p z1@|-jf`Yso7d>qf8#sCnO|`I|6xmtk`& z3d;325LB9R5DI1rJ`Y*8z_euqlx{T(I4JjWddj~GPwz4t z)}522P9k?=P-+=vr#zriR!M5spEfFTqxp9_ z11j=vP!7FvMF8#8?u>BIamn(KeP>|LfdX~HOd_hOK1!MD|{k^bS$$N5BA(yFI6aQ)*=|rc%ylai|w00#$FY%V$ z-2>#z%LEP5SlClepZKWiSxwr-+!}NIlYWER*4+l;k9ipnk`&4d^PeZ+FVu)92{0UG z8B(*y=yJD%(sH;PtsHcx98)i8$zB>;GL+ntZ!mVhK;V8fs3BpaGUzlBC ztgGyLj@m<3tHHqw%_*+~lFVQc+Tf#}>3M6nSqe^x{A_NqvrM6;?B!2xtoOa#VUjyE zz)||^AH-9#7pA6B*u2TZs6SCuQ&|{D3+owTNCiDX8Q<`z32Qg$p68<30hQ*vycxdN zM>3M5^aFP%r81f?Aty21{t$tzb%$W8sl>Sz<@i2Y#_#)r*u8JPBu*mSkz#MpB~Hj2 zZx>a}tKT4e*(Jm|T(B@!k1K;XL^`yw{qYUGqUv@)yljJSWt%I%^ zp!%b~)Yv$Q*)Y~V+f-a>Ozm(((gYgl%D1*AFe*EpCjtk8&1`M+M}4fQ7515H=+7(X zU3EX9Q(>=9LSi2QKB{DfpO|WR=qST>r#8Jg(_-et0x%!HRY9MGwTpaU&Z~wnJ5t zT~B=}=HdhSTr(Mo2fKYENiJKI>XQ+P7(1K#agTET%3oS|xqEaJ_jVU;s}Un8wpNBA z?>E_KY}}O0*8;uE5toY2-z^s5Z0PE!JGDWTU+VD2t%EkA;r)kHADGi_NbHN-aQ_ddm!u3md^Jw~!@kliEQyff zojD7xNvaLp!zjVX<7W!ppcVtMR%NO7`nH&u<~~!cm?(I>jfz&cWu=Y0yDb7J<+|FV zu2Q^F17UN4ODAt&)v64MvS5HHb3prBQ+^Mm9tAP2Q_j|lVY$iaK9#IN5yb~y7)?+K zoj(W#{-?GwlG`pYJTI?zybRiutirj!ISAk97;|G_)X;Z}}LJ%;eNy(Zt{soOJo#6%MX zWJa6i2o?l$-jt$Bo(QTUnr$h~me*Le#zH4ZB4Aq7dYo30tdoX)53xpiMH!;vns9mJ zU~mt(WhHE3Gd4@wPyA2A@;?!*JUPBOYgpvIjp@TwZ_)UCB%ny6qVjA@#2v%< zMrexyBo~Fc$7yJ47Mz7I()Mjm2Nu|_!>D-rG-w$f0e+xL-74N1msTto*>6M3n9QFE zJtvr*h#g68nay@S7wMEP;&LLTHsR~U%@W@Dz{wyGMqbiyy*g>TnZ|)&vis1oe*vkA z&vuq{ie0pb&iU28$5?H~QjNg3s1~yBw0(u)o++Tl60jUQCp{u!gWaK8YQqh?wewS8>pjC}J~6NfER zDXyhOl#k7w1o%o5?B&sL=q_AOG8YhnSqv3xw;@19B<(JZ(@ldtgUOR5Twu*LeJ-x|V=h z*nFijWws?NJMrrRlN&eHJI;h^xLT&KQWO4Gk0p(o|Mz6mhT-LD*evLC9bNGUoNuzW z^2y_w$DR$hv}L(V3r^{Uvs#mBk=5_Y;(F=A|8-1 z#L{KX#`3*j?=tpBSS<)!%X2Duizc9dJAMs`i*Z21&Js4$HYAFc&YN1W7#jNfnL+#a zgCJMjP_)fW4ZU@!gs311+$}ieU1^XrpK=hhgn76?|0N%pd! zV)zB#(PRSV(C(&da%5JwZcW-=yB&ei!3s{mOe{FUOZPm9l|itS)g?r?t$5=TYa8`3 z(wb}yWBIIdo_}O^OhRohdpy(5#*M-J>?N7co;b^89F@`p(TJ{Hb>7Fft)gN5ZQm*y zuy$~ae?U3T_nv}INd^t5Ei&P{+9RJD%7n+CtQ<`e$2quW5Nc7vb41mHp-}sbALYa%B*@xWUf_Xu<#KOda0xWA3+g{ z$+qe(5Ad?6=Ki#nW{&@3&2MQ*1R+jM2(iB+P#yv&$R?RyKJkmY$+zS2HF02G*;hus z?C(z3EJ}=Pua8E4?u_A}2Q!W^op>V2myZtC#NNv`2?|GMFqq^E`g#&Jy0*0==?kv( zG7f{LeDNv=K8HjWdh4wvwdC6Vf#3v+7D`MB-NAFdna9!LIPl|+E{#2~-kV+|U)si= z;t`MZFngg?cM|i}Ko&ck&)eMt=_mJ3Ae5ht)T}KJup+hnzB-*8R4e?FQf;L@A=jnC za_kTr`0Xz#sUp8PIPJ^!`Jj^R=YdRkrh(&0in7GWxK4^5pI#x@&#w3@mhoEQ*Sb=Q zhgi?o{DS8q$=?Lpf+2UDM$3yGJEJO&&lrBF9;RjZVyg9lydomr*+s?lj*RN^#HP-E zq=U*rq5a|EDTk-&X34{wfkkm7jwAAXuUB7pjs9UL>6LHpo-rNI$jCCv5{={VG?qC} zfD`h?(GYyd%PlTyfc?zoBtNN6a6MNyblPgu7S@+q3aMfxT8%%?R*=PnsM*-z z6mC);ywx5rrGlsKNmU6U0?YHa#c_h)*wA=*u(q6~o8Ew)DQBoc-t8|VCZz?s>nH29 zW@BQnSX=dK?{Do_9Lb{c*Qq*t{` z%x@H9UW3+;Af?z1PI-UnP`u85rOQhHix`lEhjhga&~kdrZb!zo#Er{H`2lj@3YFUH>;kG1OYtk*=RS=Z+H6D97^ zrz!C{RK(A4Fp+pvWZ&S&>54=Nl9G2YawDWuB76B2^4xuF5-@K>lI#?IVm!AVK8KMJ zc@B9KGIo6MCJM&{uuE7a*aO$6_ggH( zqb>CQ+R#;Gp`E=}Hqw9Y4<%7@)F|mN*>)xE#1+uMoEmVObj6HtJIk5BqRQ;i;~}y` zV!oo6=1CCZQ3JcB%FvWzC(tY{t}ahxVScYPA*oC4O#WdY&QB&mOMhkR$lhpjToURu z-LMu*egA&7(Zh?A$a?;@BKO9(=6REB4z>wv*^~M%q{Bx6mI0_`JEqnNP=ZUs=4MHG zQvSYQpDx!m!2lX>(17Als$@unOI3jeNXQi|GUousSA*%Gqze=mtPxm8s&lNZrpap+ z8vQwwzH|Ez_&-F*2LkhwCYkf~>9rekD1dw~ZuSQKRX4nIdIHrvU23J1Y7HjS$WAIa zy$wz|_CIQ;S25wEMJlODyeRi(PQMi^R)I^l?2-nTu)0Djbg>tAua!hBBb`v>{YjB_ zCCIw3GRQ)qNTQ!fdOIZWk9Ni*B+!>cAtZa`L#`;?+DR@7?)t4>)`XV;sInxmSEZnG zu`9h$(5qmfmW0IbuA0x~O`o6Pfgl!FwMqr*ML_LuiM~E(8`_%QJHJQ3nqgG_7w&Hn z-EG6iQfXe!8SAw2-czi9-Ye|=sLI|T+-d2t0N~jU`nB!AXa%1cjS07!!RC2AvpEj@ zMo|lk7EveB;r+P}1##pbtg}iTQ(RI(LhYCtQ;J$S^`~9q#j@~cHlw-H*HTHU=S*?W zKuqf@SN3{{k)=m~W}!d8xy|*tXpwKj&rp#+rsI%uTA#p|(=4t^;GaUBt#Ukw6_#yk z*=j1w*xfmV{|?^9sE9#5mE;fAgWED&)-;j{Jho~HxT#y`*Hbll6})M1#1^X;7epV}%G@M*49lD)m1YLPIkSE|0>OzOr%$r3YB>=oQUCOO z8S{)p-C5o#i#Vp_PF%D0XX++UT4>Cnx?c*ad=ED24a!hD6upS-F>Ni0)WDs~+Pc=}qo;(L5=g z_;$>wMXO>9J#LAEQN597<$lgKAmvbHmk6@4KFVL7keF%jQ*Q8U3xVw$moN%SH9AZK>Cv*6jS4K@qM`lzq`V%`A*h_hYB@5g!(nY&r>F;qgm)y z4yYR#VQdzN!%l-I^2G2d{oCuE=W)C6$fzy?c2XTy!5FQFzSTGQ>hAkZTMy*b4Zq?_ z&%Xx=@W*7dH|fm{$7Oyb1{cPbPVv<0%)A&7R_N0cx2J|wt+_95XE;w13`YS_cdrSgr`L2NO_|=y8 z{a0hs_vCmh_M$I?BB-YVq|Q5@6)TJmY12aXEqAnjq;uU4YB#{|R~;{A>RYuBwL;^> zUNbe@HwcfNWiTFok&u&5KMsf=LpwFwtF_9$_H!Vx^<@hmN4(e`Vf8?mNt5OiCCYER zK6^PYil&~c>e+E^SkqLo7K8@YAiPht5g~_#H`T;^jVw<+#x`~%cbaaZ4Lg0B4!T9E z#X4%<_~JA4%K!NOuiD$K30>f^wDY~nb5U<+ha_JdB!VU;J=2Xj@%`O2tZjJDyVU4W zQ($(#sxYL!ik&Z>H&G(kX!sueF1??i&>$|gkDta1Ql_wMC~Q54lpNG+2}9D%lI3vIIjOFXr4Bi z`s$QB-%C_YqF6djs-Uw&wJBcerhPEm6sf`5+b{XFaV(Pk{%tAw{fy`=K5GtN#0ob0 z&sisKr^c;Y4M*yqsDv^vJ$6r~{7k_qE}6Ga7}FfNkLC|&Kd6QiD*15MVcUv?&N#`; ziFZ5#coK){61O`EqZ)#-3`(G;FzK1teOD`<8k^7O5`1nDP7&q)fKwBcc$%^I$f`ow z+&LbmHi4umY~yGbk?m$5nBb?(lk?<@(xnfNF zfDC4P)`A!ID(NZ8a*=BwF^lXP-yf>>di8Utec?jh>xEQSzWMkKqVHxypmu4p7Fk0c zrMWkOn?7SrRU;KfmC~NQ(ZCTB$d{OitZ#6R}I+{#P7*TE(ncUl$F$8MG1wtk^W!djKdm~PxMU#rHhaR+@X zpWZeOVJtl?*|RxrdIp6pS_Z_T&AW~VJtc@#uj+E2QRMGLH5hxc-C;ihiiN%%-W1&M zo}Nb@Uh|ygr}qEs{BkxgS~I?JTO1(51&&+Bot)55QylBK;`0p9(`^UlvXNY`V2C$jOG#g_d^VLH@Ua&dmfWz+ z5&Ix^H~>REs~cEKuj?WW6l*=9yeWBaKX((pU^r96YQu!kz(*vktB;y=TV~}RnV~bV zEpyM&S;jlGM(n~HTWyALOT1BAghp3oVKvAIVNhkG@qmFUa6 zPE7_>FOn);nyn!caI*fio2kog}m+ug7H7{MXR>Kf_aBZBJ-uMukSX>PpYj z&|95Fs}fvsA}wpArA}#VQ=uRA7PhG!KuB?1q#F1UV3zkQe?#f(dmnBXe_n;ho|cAg z9S_@48${Ek#EoRHyhTA_Fm0gDWd1_(l2|!`M0_`|camurF1|wi<(GYG^#?Bony*x= zV2&yCfySXp2rup$nTzH;3d&>T{(zWMEUg3}l-xJ=+=Bkj1yFehUR(w&7A`*53+fC^ z{q6!>?0AFM6%uCU%lr0{4u$`EyIvWKD*F>!tbT`GCO3n>;SLhv$?*L;TP$|{dDvtn}0UwOZtUvb|0r? zE*ftE9;ylmZr2PI*xnjkFcYFp=WR7ZWBQujh_5)|d4{9HtYramj!?$7EAXm@5srarK ziHl1ow)Vi6i;QJQw{CT1s32Fwegi8BuOl5%L|oBjl+POhgUZT7Q7&RB>Jj~|{bR3I zbnk}bYfZJDjlYH|KPivUTbYMB(6n3EeCUY138yd(A+*P}c66DMZrFbAVx4hLU~R!= zF(?NZE(uz9X!(7RMe?tdLF$IPf@|t)j>%Em)J@#yK{S|>VSf~HC-AzA-U~0n)6`AN2_;V7e9R5yc;w8HUyJB;~ zwHq*4Fx2!SCE;I;S*MkF1o#Tvg_oEua5*=3ab#pN{Hx1(J%`PfO@3mJb3T6;w+t!uPhXK!C@P19O4a!B-WHTI)c|qfHH?eB(n5^W&7}1GWubgjF gFN5vTMXXqi&JODP5w#&;O7n1&=HL2Pl{nj{pDw literal 0 HcmV?d00001 diff --git a/src/MilitaryTrainingManagement/wwwroot/uploads/photos/3d15bd43-20e9-432a-bba3-ab1b224c0c92.jpg b/src/MilitaryTrainingManagement/wwwroot/uploads/photos/3d15bd43-20e9-432a-bba3-ab1b224c0c92.jpg new file mode 100644 index 0000000000000000000000000000000000000000..055625a173556df8ce71d0d16157586e1dd024a8 GIT binary patch literal 8689 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 literal 0 HcmV?d00001 diff --git a/src/MilitaryTrainingManagement/wwwroot/uploads/photos/3fccae6b-1d22-4612-bdf7-1fb425a8a0fa.jpg b/src/MilitaryTrainingManagement/wwwroot/uploads/photos/3fccae6b-1d22-4612-bdf7-1fb425a8a0fa.jpg new file mode 100644 index 0000000000000000000000000000000000000000..444ac7e186d7bebee293916391761bab557a429e GIT binary patch literal 7490 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 gFN5vTMXXqi&JODP5w#&;O7n1&=HL2Pl{nj{pDw literal 0 HcmV?d00001 diff --git a/src/frontend/src/api/personnel.ts b/src/frontend/src/api/personnel.ts index ffba0c9..174b3e6 100644 --- a/src/frontend/src/api/personnel.ts +++ b/src/frontend/src/api/personnel.ts @@ -83,6 +83,12 @@ export const personnelApi = { return response.data }, + // 重新提交被拒绝的人才审批 + async resubmit(personnelId: number): Promise { + const response = await apiClient.post(`/personnel/${personnelId}/resubmit`) + return response.data + }, + // 获取审批历史 async getApprovalHistory(personnelId: number): Promise { const response = await apiClient.get(`/personnel/${personnelId}/approval-history`) diff --git a/src/frontend/src/views/AuditLogs.vue b/src/frontend/src/views/AuditLogs.vue index 7b88f8f..81f4c5a 100644 --- a/src/frontend/src/views/AuditLogs.vue +++ b/src/frontend/src/views/AuditLogs.vue @@ -14,14 +14,21 @@ - - + + + + + + + - + + + @@ -158,7 +165,29 @@ const pagination = reactive({ }) function formatDate(dateStr: string): string { - return new Date(dateStr).toLocaleString('zh-CN') + // 后端返回的是 UTC 时间,需要转换为本地时间 + const date = new Date(dateStr) + // 如果日期字符串不包含时区信息,假设它是 UTC 时间 + if (!dateStr.includes('Z') && !dateStr.includes('+') && !dateStr.includes('-', 10)) { + // 添加 Z 后缀表示 UTC 时间 + const utcDate = new Date(dateStr + 'Z') + return utcDate.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }) + } + return date.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }) } function getActionName(action: string): string { @@ -166,10 +195,15 @@ function getActionName(action: string): string { 'Create': '创建', 'Update': '更新', 'Delete': '删除', - 'Approve': '审批', - 'Reject': '拒绝', + 'Approve': '审批通过', + 'Reject': '审批拒绝', + 'RequestUpgrade': '向上申报', + 'ApproveUpgrade': '批准升级', + 'RejectUpgrade': '拒绝升级', + 'DirectUpgrade': '直接升级', 'Submit': '提交', - 'Review': '审核' + 'Review': '审核', + 'Resubmit': '重新提交' } return actionMap[action] || action } @@ -181,8 +215,13 @@ function getActionTagType(action: string): string { 'Delete': 'danger', 'Approve': 'success', 'Reject': 'danger', + 'RequestUpgrade': 'primary', + 'ApproveUpgrade': 'success', + 'RejectUpgrade': 'danger', + 'DirectUpgrade': 'success', 'Submit': 'primary', - 'Review': 'info' + 'Review': 'info', + 'Resubmit': 'primary' } return typeMap[action] || 'info' } @@ -191,6 +230,8 @@ function getEntityTypeName(entityType: string): string { const nameMap: Record = { 'MaterialAllocation': '物资配额', 'AllocationDistribution': '配额分配', + 'ConsumptionReport': '消耗记录', + 'ConsumptionReportChangeRequest': '消耗删改申请', 'UserAccount': '用户账户', 'OrganizationalUnit': '组织单位', 'Personnel': '人员信息', @@ -224,7 +265,8 @@ async function loadLogs() { } const result = await auditLogsApi.getAll(params) - logs.value = result.items + // 过滤掉没有操作人的系统记录 + logs.value = result.items.filter(log => log.userId != null) pagination.totalCount = result.totalCount } catch { ElMessage.error('加载操作记录失败') diff --git a/src/frontend/src/views/personnel/PersonnelForm.vue b/src/frontend/src/views/personnel/PersonnelForm.vue index f5c45d9..36e66b6 100644 --- a/src/frontend/src/views/personnel/PersonnelForm.vue +++ b/src/frontend/src/views/personnel/PersonnelForm.vue @@ -300,6 +300,8 @@ async function loadPersonnel() { form.unit = person.unit || '' form.position = person.position form.rank = person.rank + form.gender = person.gender || '男' + form.age = person.age || 25 form.idNumber = person.idNumber || '' form.professionalTitle = person.professionalTitle || '' form.politicalStatus = person.politicalStatus || '' @@ -351,6 +353,8 @@ async function handleSubmit() { formData.append('name', form.name) formData.append('position', form.position) formData.append('rank', form.rank) + formData.append('gender', form.gender) + formData.append('age', form.age.toString()) // 可选字段 - 只有非空时才添加 if (form.unit) formData.append('unit', form.unit) diff --git a/src/frontend/src/views/personnel/PersonnelList.vue b/src/frontend/src/views/personnel/PersonnelList.vue index 8398b3f..5561263 100644 --- a/src/frontend/src/views/personnel/PersonnelList.vue +++ b/src/frontend/src/views/personnel/PersonnelList.vue @@ -30,9 +30,6 @@ 已批准 - - 已拒绝 - @@ -68,10 +65,7 @@ @@ -117,11 +111,16 @@ - + + + + + + @@ -206,7 +205,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, Top } from '@element-plus/icons-vue' +import { Plus, Search, View, Edit, Delete, Check, Close, User, Top, Upload } from '@element-plus/icons-vue' import { useAuthStore } from '@/stores/auth' import { personnelApi } from '@/api' import type { Personnel } from '@/types' @@ -252,27 +251,68 @@ function getStatusName(status: PersonnelStatus): string { // 根据当前用户判断显示的状态文字 function getDisplayStatus(person: Personnel): string { + // 如果有待升级标记,显示升级相关状态 + if (person.pendingUpgradeByUnitId) { + if (canApproveUpgrade(person)) { + return '待审批' + } else { + return '待上级审批' + } + } + + // 已拒绝状态直接返回 + if (person.status === PersonnelStatus.Rejected) { + return '已拒绝' + } + + // 已批准状态直接返回 + if (person.status === PersonnelStatus.Approved) { + return '已批准' + } + + // Pending 状态的处理 if (person.status === PersonnelStatus.Pending) { - // 待审批状态:判断当前用户是否能审批 if (canApprovePersonnel(person)) { return '待审批' } else { return '待上级审批' } } + + // 其他情况 return getStatusName(person.status) } // 根据当前用户判断显示的状态标签类型 function getDisplayStatusType(person: Personnel): string { + // 如果有待升级标记,显示升级相关状态 + if (person.pendingUpgradeByUnitId) { + if (canApproveUpgrade(person)) { + return 'warning' // 待审批 - 黄色 + } else { + return 'primary' // 待上级审批 - 蓝色 + } + } + + // 已拒绝状态 - 红色 + if (person.status === PersonnelStatus.Rejected) { + return 'danger' + } + + // 已批准状态直接返回 + if (person.status === PersonnelStatus.Approved) { + return 'success' // 已批准 - 绿色 + } + + // Pending 状态的处理 if (person.status === PersonnelStatus.Pending) { - // 待审批状态:判断当前用户是否能审批 if (canApprovePersonnel(person)) { return 'warning' // 待审批 - 黄色 } else { return 'primary' // 待上级审批 - 蓝色 } } + return getStatusTagType(person.status) } @@ -422,7 +462,7 @@ function canApprovePersonnel(person: Personnel): boolean { if (person.submittedByUnitId === authStore.user.organizationalUnitId) return false // 用户层级必须高于提交单位层级(这里简化处理,假设能看到的数据都是下级提交的) // 实际权限检查由后端完成 - return true + return authStore.organizationalLevelNum < 4 // 只有营级及以上可以审批 } // 判断是否可以删除该人员 @@ -436,7 +476,17 @@ function canDelete(person: Personnel): boolean { return true } - // 已批准状态:用户层级必须高于或等于人员等级才能删除 + // 已拒绝状态:只有提交单位可以删除 + if (person.status === PersonnelStatus.Rejected) { + return person.submittedByUnitId === authStore.user.organizationalUnitId + } + + // 已批准状态但没有等级:这是异常状态,允许删除 + if (person.status === PersonnelStatus.Approved && !person.approvedLevel) { + return true + } + + // 已批准状态且有等级:用户层级必须高于或等于人员等级才能删除 if (person.status === PersonnelStatus.Approved && person.approvedLevel) { const personnelLevelNum = { 'Division': 1, @@ -451,6 +501,37 @@ function canDelete(person: Personnel): boolean { return false } +// 判断是否可以重新提交 +function canResubmit(person: Personnel): boolean { + if (!authStore.user) return false + // 只有提交单位可以重新提交被拒绝的人才 + return person.status === PersonnelStatus.Rejected && + person.submittedByUnitId === authStore.user.organizationalUnitId +} + +// 重新提交被拒绝的人才 +async function handleResubmit(person: Personnel) { + try { + await ElMessageBox.confirm( + `确定要重新提交 "${person.name}" 的审批申请吗?\n重新提交后,该人才将进入待审批状态。`, + '确认重新提交', + { + type: 'warning', + confirmButtonText: '确认提交', + cancelButtonText: '取消' + } + ) + + await personnelApi.resubmit(person.id) + ElMessage.success('已重新提交审批,等待上级审批') + await loadPersonnel() + } catch (error: any) { + if (error !== 'cancel') { + ElMessage.error(error.response?.data?.message || '重新提交失败') + } + } +} + function handleApprove(person: Personnel) { selectedPerson.value = person approvalForm.comments = ''