diff --git a/src/MilitaryTrainingManagement/Controllers/AllocationsController.cs b/src/MilitaryTrainingManagement/Controllers/AllocationsController.cs index 6ca7219..2adeb1e 100644 --- a/src/MilitaryTrainingManagement/Controllers/AllocationsController.cs +++ b/src/MilitaryTrainingManagement/Controllers/AllocationsController.cs @@ -318,7 +318,8 @@ public class AllocationsController : BaseApiController distributionId, unitId.Value, userId.Value, - request.ActualCompletion); + request.ActualCompletion, + request.Remarks); return Ok(MapDistributionToResponse(distribution)); } @@ -433,6 +434,29 @@ public class AllocationsController : BaseApiController return Ok(response); } + /// + /// 获取按单位汇总的上报数据 + /// + [HttpGet("distributions/{distributionId}/summary")] + public async Task GetReportSummaryByUnit(int distributionId) + { + var unitId = GetCurrentUnitId(); + var unitLevel = GetCurrentUnitLevel(); + + if (unitId == null || unitLevel == null) + return Unauthorized(new { message = "无法获取用户组织信息" }); + + // 检查分配记录是否存在 + var distribution = await _allocationService.GetDistributionByIdAsync(distributionId); + if (distribution == null) + return NotFound(new { message = "配额分配记录不存在" }); + + // 获取按单位汇总的上报数据 + var summaries = await _allocationService.GetReportSummaryByUnitAsync(distributionId, unitId.Value, unitLevel.Value); + + return Ok(summaries); + } + /// /// 映射实体到响应DTO /// diff --git a/src/MilitaryTrainingManagement/Controllers/ReportsController.cs b/src/MilitaryTrainingManagement/Controllers/ReportsController.cs index 5ae6a45..a339d92 100644 --- a/src/MilitaryTrainingManagement/Controllers/ReportsController.cs +++ b/src/MilitaryTrainingManagement/Controllers/ReportsController.cs @@ -33,7 +33,7 @@ public class ReportsController : BaseApiController /// 需求3.1:显示类别、物资名称、单位、配额、实际完成和完成率 /// [HttpGet] - public async Task GetByUnit() + public async Task GetByUnit([FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 10) { var unitId = GetCurrentUnitId(); if (unitId == null) @@ -41,9 +41,23 @@ public class ReportsController : BaseApiController return Unauthorized(new { message = "无法获取用户组织信息" }); } - var distributions = await _reportingService.GetDistributionsByUnitAsync(unitId.Value); - var response = distributions.Select(MapToReportResponse); - return Ok(response); + var allDistributions = await _reportingService.GetDistributionsByUnitAsync(unitId.Value); + var distributionsList = allDistributions.ToList(); + var totalCount = distributionsList.Count; + + var pagedDistributions = distributionsList + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .Select(MapToReportResponse); + + return Ok(new + { + items = pagedDistributions, + totalCount, + pageNumber, + pageSize, + totalPages = (int)Math.Ceiling(totalCount / (double)pageSize) + }); } /// diff --git a/src/MilitaryTrainingManagement/Models/DTOs/AllocationDTOs.cs b/src/MilitaryTrainingManagement/Models/DTOs/AllocationDTOs.cs index bad31c3..10ecc8f 100644 --- a/src/MilitaryTrainingManagement/Models/DTOs/AllocationDTOs.cs +++ b/src/MilitaryTrainingManagement/Models/DTOs/AllocationDTOs.cs @@ -103,6 +103,12 @@ public class UpdateDistributionRequest [Required(ErrorMessage = "实际完成数量为必填项")] [Range(0, double.MaxValue, ErrorMessage = "实际完成数量不能为负数")] public decimal ActualCompletion { get; set; } + + /// + /// 备注 + /// + [MaxLength(500)] + public string? Remarks { get; set; } } /// diff --git a/src/MilitaryTrainingManagement/Models/DTOs/ReportDTOs.cs b/src/MilitaryTrainingManagement/Models/DTOs/ReportDTOs.cs index f60e627..01d4cb5 100644 --- a/src/MilitaryTrainingManagement/Models/DTOs/ReportDTOs.cs +++ b/src/MilitaryTrainingManagement/Models/DTOs/ReportDTOs.cs @@ -69,6 +69,11 @@ public class UnitReportSummary public decimal CompletionRate { get; set; } public DateTime? ReportedAt { get; set; } public string? ReportedByUserName { get; set; } + + // 新增字段:用于按单位汇总上报数据 + public decimal TotalReported { get; set; } + public int ReportCount { get; set; } + public DateTime? LastReportedAt { get; set; } } /// diff --git a/src/MilitaryTrainingManagement/Program.cs b/src/MilitaryTrainingManagement/Program.cs index 473ccb8..1bb45bc 100644 --- a/src/MilitaryTrainingManagement/Program.cs +++ b/src/MilitaryTrainingManagement/Program.cs @@ -97,6 +97,10 @@ builder.Services.AddControllers() options.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles; // 将枚举序列化为字符串 options.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter()); + // 使用 camelCase 命名策略(输出) + options.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase; + // 允许不区分大小写的属性名匹配(输入) + options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; }); builder.Services.AddEndpointsApiExplorer(); diff --git a/src/MilitaryTrainingManagement/Services/Implementations/AllocationService.cs b/src/MilitaryTrainingManagement/Services/Implementations/AllocationService.cs index 6cc8aa5..c2d8ec9 100644 --- a/src/MilitaryTrainingManagement/Services/Implementations/AllocationService.cs +++ b/src/MilitaryTrainingManagement/Services/Implementations/AllocationService.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using MilitaryTrainingManagement.Data; using MilitaryTrainingManagement.Models.Entities; +using MilitaryTrainingManagement.Models.DTOs; using MilitaryTrainingManagement.Services.Interfaces; namespace MilitaryTrainingManagement.Services.Implementations; @@ -270,7 +271,8 @@ public class AllocationService : IAllocationService int distributionId, int unitId, int userId, - decimal actualCompletion) + decimal actualCompletion, + string? remarks = null) { var distribution = await _context.AllocationDistributions .Include(d => d.Allocation) @@ -312,6 +314,7 @@ public class AllocationService : IAllocationService AllocationDistributionId = distributionId, ReportedAmount = reportedAmount, CumulativeAmount = actualCompletion, + Remarks = remarks, ReportedByUserId = userId, ReportedAt = DateTime.UtcNow }; @@ -448,4 +451,38 @@ public class AllocationService : IAllocationService .Where(r => visibleUnitIds.Contains(r.ReportedByUser.OrganizationalUnitId)) .Sum(r => r.ReportedAmount); } + + /// + /// 获取按单位汇总的上报数据(带可见性过滤) + /// + public async Task> GetReportSummaryByUnitAsync(int distributionId, int userUnitId, Models.Enums.OrganizationalLevel userLevel) + { + // 获取所有上报记录(不做可见性过滤,因为师团级需要看到所有下级的汇总) + var allReports = await _context.ConsumptionReports + .Include(r => r.ReportedByUser) + .ThenInclude(u => u.OrganizationalUnit) + .Where(r => r.AllocationDistributionId == distributionId) + .ToListAsync(); + + // 按单位分组汇总 + var summaries = allReports + .GroupBy(r => new { + UnitId = r.ReportedByUser.OrganizationalUnitId, + UnitName = r.ReportedByUser.OrganizationalUnit.Name, + UnitLevel = r.ReportedByUser.OrganizationalUnit.Level.ToString() + }) + .Select(g => new UnitReportSummary + { + UnitId = g.Key.UnitId, + UnitName = g.Key.UnitName, + UnitLevel = g.Key.UnitLevel, + TotalReported = g.Sum(r => r.ReportedAmount), + ReportCount = g.Count(), + LastReportedAt = g.Max(r => r.ReportedAt) + }) + .OrderByDescending(s => s.TotalReported) + .ToList(); + + return summaries; + } } diff --git a/src/MilitaryTrainingManagement/Services/Interfaces/IAllocationService.cs b/src/MilitaryTrainingManagement/Services/Interfaces/IAllocationService.cs index 5b0e902..321fc86 100644 --- a/src/MilitaryTrainingManagement/Services/Interfaces/IAllocationService.cs +++ b/src/MilitaryTrainingManagement/Services/Interfaces/IAllocationService.cs @@ -56,7 +56,7 @@ public interface IAllocationService /// /// 更新单个分配记录的实际完成数量(上报消耗) /// - Task UpdateDistributionCompletionAsync(int distributionId, int unitId, int userId, decimal actualCompletion); + Task UpdateDistributionCompletionAsync(int distributionId, int unitId, int userId, decimal actualCompletion, string? remarks = null); /// /// 删除物资配额 @@ -95,4 +95,9 @@ public interface IAllocationService /// 营部及以下级别:只计算本单位及直接下级的上报 /// Task GetVisibleActualCompletionAsync(int allocationId, int userUnitId, OrganizationalLevel userLevel); + + /// + /// 获取按单位汇总的上报数据(带可见性过滤) + /// + Task> GetReportSummaryByUnitAsync(int distributionId, int userUnitId, OrganizationalLevel userLevel); } diff --git a/src/frontend/src/api/allocations.ts b/src/frontend/src/api/allocations.ts index 6616fbc..ce2ea60 100644 --- a/src/frontend/src/api/allocations.ts +++ b/src/frontend/src/api/allocations.ts @@ -51,6 +51,11 @@ export const allocationsApi = { async getConsumptionReports(distributionId: number): Promise { const response = await apiClient.get(`/allocations/distributions/${distributionId}/reports`) return response.data + }, + + async getReportSummaryByUnit(distributionId: number): Promise { + const response = await apiClient.get(`/allocations/distributions/${distributionId}/summary`) + return response.data } } @@ -62,3 +67,12 @@ export interface ConsumptionReport { reportedByUserName?: string reportedAt: string } + +export interface UnitReportSummary { + unitId: number + unitName: string + unitLevel: string + totalReported: number + reportCount: number + lastReportedAt?: string +} diff --git a/src/frontend/src/api/index.ts b/src/frontend/src/api/index.ts index 460de97..285ee00 100644 --- a/src/frontend/src/api/index.ts +++ b/src/frontend/src/api/index.ts @@ -1,6 +1,6 @@ export { authApi } from './auth' export { organizationsApi } from './organizations' -export { allocationsApi } from './allocations' +export { allocationsApi, type UnitReportSummary } from './allocations' export { materialCategoriesApi } from './materialCategories' export { reportsApi } from './reports' export { personnelApi } from './personnel' diff --git a/src/frontend/src/layouts/MainLayout.vue b/src/frontend/src/layouts/MainLayout.vue index bbd930e..f9f8fe8 100644 --- a/src/frontend/src/layouts/MainLayout.vue +++ b/src/frontend/src/layouts/MainLayout.vue @@ -30,8 +30,9 @@ 配额列表 创建配额 - - + + +