diff --git a/Src/Dlhis.Basic/Dlhis.Basic.csproj b/Src/Dlhis.Basic/Dlhis.Basic.csproj
index 6dc23042..1b0c0da7 100644
--- a/Src/Dlhis.Basic/Dlhis.Basic.csproj
+++ b/Src/Dlhis.Basic/Dlhis.Basic.csproj
@@ -28,4 +28,8 @@
+
+
+
+
diff --git a/Src/Dlhis.Basic/Summary/Controllers/SummariesController.cs b/Src/Dlhis.Basic/Summary/Controllers/SummariesController.cs
new file mode 100644
index 00000000..0f7f9fb0
--- /dev/null
+++ b/Src/Dlhis.Basic/Summary/Controllers/SummariesController.cs
@@ -0,0 +1,29 @@
+using Dlhis.Basic.Summary.Response;
+using Dlhis.Basic.Summary.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Dlhis.Basic.Summary.Controllers;
+
+[Authorize]
+[Route("api/[controller]")]
+[ApiController]
+public class SummariesController : ControllerBase
+{
+ private readonly ISummaryService _summaryService;
+
+ public SummariesController(ISummaryService summaryService)
+ {
+ _summaryService = summaryService;
+ }
+
+ ///
+ /// 考勤首页
+ ///
+ ///
+ [HttpGet("attendance-summary")]
+ public async Task GetKaoQingSummary()
+ {
+ return await _summaryService.GetAttendanceSummaryAsync();
+ }
+}
diff --git a/Src/Dlhis.Basic/Summary/Dto/TrendDto.cs b/Src/Dlhis.Basic/Summary/Dto/TrendDto.cs
new file mode 100644
index 00000000..2d2d046d
--- /dev/null
+++ b/Src/Dlhis.Basic/Summary/Dto/TrendDto.cs
@@ -0,0 +1,8 @@
+namespace Dlhis.Basic.Summary.Dto;
+
+public class TrendDto
+{
+ public DateTime Date { get; set; }
+
+ public int Count { get; set; }
+}
diff --git a/Src/Dlhis.Basic/Summary/Response/AttendanceDetailSummary.cs b/Src/Dlhis.Basic/Summary/Response/AttendanceDetailSummary.cs
new file mode 100644
index 00000000..f4f9c24a
--- /dev/null
+++ b/Src/Dlhis.Basic/Summary/Response/AttendanceDetailSummary.cs
@@ -0,0 +1,21 @@
+using Dlhis.Basic.Summary.Dto;
+
+namespace Dlhis.Basic.Summary.Response;
+
+public class AttendanceDetailSummary
+{
+ ///
+ /// 在职人数
+ ///
+ public int Employed { get; set; }
+
+ ///
+ /// 非在职人数
+ ///
+ public int Unemployed { get; set; }
+
+ ///
+ /// 人员趋势 7天
+ ///
+ public GainfoTrendResponse GainfoTrend { get; set; }
+}
diff --git a/Src/Dlhis.Basic/Summary/Response/AttendanceDeviceSummary.cs b/Src/Dlhis.Basic/Summary/Response/AttendanceDeviceSummary.cs
new file mode 100644
index 00000000..c0ebc007
--- /dev/null
+++ b/Src/Dlhis.Basic/Summary/Response/AttendanceDeviceSummary.cs
@@ -0,0 +1,26 @@
+using Dlhis.Basic.Summary.Dto;
+
+namespace Dlhis.Basic.Summary.Response;
+
+public class AttendanceDeviceSummary
+{
+ ///
+ /// 总数
+ ///
+ public int Total { get; set; }
+
+ ///
+ /// 在线数
+ ///
+ public int OnlineCount { get; set; }
+
+ ///
+ /// 启用数
+ ///
+ public int EnableCount { get; set; }
+
+ ///
+ /// 新增设备趋势 30天内
+ ///
+ public List Trend { get; set; }
+}
diff --git a/Src/Dlhis.Basic/Summary/Response/AttendanceSummaryResponse.cs b/Src/Dlhis.Basic/Summary/Response/AttendanceSummaryResponse.cs
new file mode 100644
index 00000000..ab930d60
--- /dev/null
+++ b/Src/Dlhis.Basic/Summary/Response/AttendanceSummaryResponse.cs
@@ -0,0 +1,9 @@
+namespace Dlhis.Basic.Summary.Response
+{
+ public class AttendanceSummaryResponse
+ {
+ public AttendanceDeviceSummary DeviceSummary { get; set; }
+
+ public AttendanceDetailSummary AttendanceDetailSummary { get; set; }
+ }
+}
diff --git a/Src/Dlhis.Basic/Summary/Response/GainfoTrendResponse.cs b/Src/Dlhis.Basic/Summary/Response/GainfoTrendResponse.cs
new file mode 100644
index 00000000..4050c983
--- /dev/null
+++ b/Src/Dlhis.Basic/Summary/Response/GainfoTrendResponse.cs
@@ -0,0 +1,10 @@
+using Dlhis.Basic.Summary.Dto;
+
+namespace Dlhis.Basic.Summary.Response
+{
+ public class GainfoTrendResponse
+ {
+ public List EmployedTrend { get; set; }
+ public List UnemployedTrend { get; set; }
+ }
+}
diff --git a/Src/Dlhis.Basic/Summary/Services/ISummaryService.cs b/Src/Dlhis.Basic/Summary/Services/ISummaryService.cs
new file mode 100644
index 00000000..775379d2
--- /dev/null
+++ b/Src/Dlhis.Basic/Summary/Services/ISummaryService.cs
@@ -0,0 +1,8 @@
+using Dlhis.Basic.Summary.Response;
+
+namespace Dlhis.Basic.Summary.Services;
+
+public interface ISummaryService : IScopeDependency
+{
+ Task GetAttendanceSummaryAsync();
+}
diff --git a/Src/Dlhis.Basic/Summary/Services/Implements/SummaryService.cs b/Src/Dlhis.Basic/Summary/Services/Implements/SummaryService.cs
new file mode 100644
index 00000000..ad4f3e1e
--- /dev/null
+++ b/Src/Dlhis.Basic/Summary/Services/Implements/SummaryService.cs
@@ -0,0 +1,106 @@
+using Dlhis.Basic.Organization.Services;
+using Dlhis.Basic.Summary.Dto;
+using Dlhis.Basic.Summary.Response;
+using Dlhis.Entity.Basic.Entity;
+using Dlhis.Entity.Basic.Enums;
+using Dlhis.Entity.Device.Entities;
+using Kurisu.SqlSugar.Services;
+
+namespace Dlhis.Basic.Summary.Services.Implements;
+
+public class SummaryService : ISummaryService
+{
+ private readonly IDbContext _dbContext;
+ private readonly IGainfoStore _gainfoStore;
+
+ public SummaryService(IDbContext dbContext, IGainfoStore gainfoStore)
+ {
+ _dbContext = dbContext;
+ _gainfoStore = gainfoStore;
+ }
+
+ public async Task GetAttendanceSummaryAsync()
+ {
+ var now = DateTime.Now;
+
+ var currentDay = DateTime.Today.AddDays(1).AddSeconds(-1);
+ var before30Day = DateTime.Today.AddDays(-29);
+ var before7Day = DateTime.Today.AddDays(-6);
+
+ var devices = await _dbContext.Queryable().ToListAsync();
+ var deviceUniqueNos = devices.Select(x => x.UniqueNo).ToList();
+
+ var deviceRegistries = await _dbContext.Queryable()
+ .LeftJoin((x, d) => x.DeviceUniqueNo == d.UniqueNo)
+ .Where(x => deviceUniqueNos.Contains(x.DeviceUniqueNo))
+ .Select((x, d) => new
+ {
+ d.HeartbeatInterval,
+ x.LastHeartbeatTime
+ })
+ .ToListAsync();
+
+ var workingGainfos = await _gainfoStore.Queryable(GainfoStatusEnumType.Working).ToListAsync();
+ var leaveGainfos = await _gainfoStore.Queryable(GainfoStatusEnumType.Leave).ToListAsync();
+
+ return new AttendanceSummaryResponse
+ {
+ DeviceSummary = new AttendanceDeviceSummary
+ {
+ Total = devices.Count,
+ EnableCount = devices.Where(x => x.Enable).Count(),
+ OnlineCount = deviceRegistries.Where(x =>
+ {
+ if (x.HeartbeatInterval <= 0)
+ {
+ return false;
+ }
+
+ return x.LastHeartbeatTime.AddSeconds(x.HeartbeatInterval * 3) >= now;
+
+ }).Count(),
+ Trend = FixTrend(devices.Where(x => x.CreateTime >= before30Day && x.CreateTime <= currentDay)
+ .GroupBy(x => x.CreateTime.Date)
+ .Select(x => new TrendDto { Date = x.Key, Count = x.Count() }), before30Day, currentDay)
+ },
+ AttendanceDetailSummary = new AttendanceDetailSummary
+ {
+ Employed = workingGainfos.Count,
+ Unemployed = leaveGainfos.Count,
+ GainfoTrend = new GainfoTrendResponse
+ {
+ EmployedTrend = FixTrend(workingGainfos.Where(x => x.CreateTime >= before7Day && x.CreateTime <= currentDay)
+ .GroupBy(x => x.CreateTime.Date)
+ .Select(x => new TrendDto { Date = x.Key, Count = x.Count() }), before7Day, currentDay),
+ UnemployedTrend = FixTrend(leaveGainfos.Where(x => x.CreateTime >= before7Day && x.CreateTime <= currentDay)
+ .GroupBy(x => x.CreateTime.Date)
+ .Select(x => new TrendDto { Date = x.Key, Count = x.Count() }), before7Day, currentDay),
+ }
+ }
+ };
+ }
+
+ private static List FixTrend(IEnumerable dtos, DateTime start, DateTime end)
+ {
+ var result = dtos.ToList();
+
+ start = start.Date;
+ end = end.Date;
+
+ var currentDate = start;
+ while (currentDate <= end)
+ {
+ if (result.Any(x => x.Date == currentDate))
+ {
+ currentDate = currentDate.AddDays(1);
+ continue;
+ }
+
+ result.Add(new TrendDto { Date = currentDate, Count = 0 });
+
+ currentDate = currentDate.AddDays(1);
+ }
+
+ return result;
+ }
+}
diff --git a/Src/Dlhis.Entity/Canteen/Enums/CanteenBusinessPlaceCatalogEnumType.cs b/Src/Dlhis.Entity/Canteen/Enums/CanteenBusinessPlaceCatalogEnumType.cs
index 9f0c4f97..80724371 100644
--- a/Src/Dlhis.Entity/Canteen/Enums/CanteenBusinessPlaceCatalogEnumType.cs
+++ b/Src/Dlhis.Entity/Canteen/Enums/CanteenBusinessPlaceCatalogEnumType.cs
@@ -20,10 +20,15 @@ public enum CanteenBusinessPlaceCatalogEnumType
[Description("食堂")]
Cafeteria = 2,
-
///
/// 超市
///
[Description("超市")]
Supermarket = 3,
+
+ ///
+ /// 水吧
+ ///
+ [Description("水吧")]
+ Bar = 4,
}
\ No newline at end of file
diff --git a/Src/Dlhis.Entity/Sys/Entities/RoleEntity.cs b/Src/Dlhis.Entity/Sys/Entities/RoleEntity.cs
index 610770db..f9a7afa6 100644
--- a/Src/Dlhis.Entity/Sys/Entities/RoleEntity.cs
+++ b/Src/Dlhis.Entity/Sys/Entities/RoleEntity.cs
@@ -17,5 +17,5 @@ public class RoleEntity
public string NormalizedName { get; set; }
- public string ConcurrencyStamp { get; set; }
+ public Guid ConcurrencyStamp { get; set; }
}
diff --git a/Src/Dlhis.Entity/Sys/Entities/UserRoleEntity.cs b/Src/Dlhis.Entity/Sys/Entities/UserRoleEntity.cs
new file mode 100644
index 00000000..8fe6a3cf
--- /dev/null
+++ b/Src/Dlhis.Entity/Sys/Entities/UserRoleEntity.cs
@@ -0,0 +1,13 @@
+using Microsoft.Extensions.DependencyInjection;
+using SqlSugar;
+
+namespace Dlhis.Entity.Sys.Entities;
+
+[SkipScan]
+[SugarTable(TableName = "UserRoles")]
+public class UserRoleEntity
+{
+ public int UserId { get; set; }
+
+ public int RoleId { get; set; }
+}
diff --git a/Src/Dlhis.Sys/Identity/Controllers/RolesController.cs b/Src/Dlhis.Sys/Identity/Controllers/RolesController.cs
index ae0ba399..8fa618fc 100644
--- a/Src/Dlhis.Sys/Identity/Controllers/RolesController.cs
+++ b/Src/Dlhis.Sys/Identity/Controllers/RolesController.cs
@@ -10,7 +10,7 @@ namespace Dlhis.Sys.Identity.Controllers;
///
/// 角色
///
-[Authorize(Roles = "admin")]
+[Authorize]
[Route("api/[controller]")]
[ApiController]
public class RolesController : ControllerBase
@@ -27,9 +27,55 @@ public class RolesController : ControllerBase
///
///
///
- [HttpGet]
- public async Task> GetPageAsync(RolePageRequest request)
+ [Authorize(Roles = "admin")]
+ [HttpGet("page")]
+ public async Task> GetPageAsync([FromQuery] RolePageRequest request)
{
return await _roleService.GetPageAsync(request);
}
+
+ ///
+ /// 分配角色
+ ///
+ ///
+ ///
+ [Authorize(Roles = "admin")]
+ [HttpPost("assign")]
+ public async Task AssignRole(AssignRoleRequest request)
+ {
+ await _roleService.AssignRoleAsync(request);
+ }
+
+ ///
+ /// 保存角色
+ ///
+ ///
+ [Authorize(Roles = "admin")]
+ [HttpPost("save")]
+ public async Task SaveRole(RoleSaveRequest request)
+ {
+ await _roleService.SaveRoleAsync(request);
+ }
+
+ ///
+ /// 移除角色
+ ///
+ ///
+ ///
+ [Authorize(Roles = "admin")]
+ [HttpPost("delete")]
+ public async Task RemoveRole(RoleRemoveRequest request)
+ {
+ await _roleService.RemoveRoleAsync(request);
+ }
+
+ ///
+ /// 我的角色
+ ///
+ ///
+ [HttpGet("my")]
+ public async Task GetMyRoleAsync()
+ {
+ return await _roleService.GetMyRoleAsync();
+ }
}
diff --git a/Src/Dlhis.Sys/Identity/Request/AssignRoleRequest.cs b/Src/Dlhis.Sys/Identity/Request/AssignRoleRequest.cs
new file mode 100644
index 00000000..c66d25c5
--- /dev/null
+++ b/Src/Dlhis.Sys/Identity/Request/AssignRoleRequest.cs
@@ -0,0 +1,8 @@
+namespace Dlhis.Sys.Identity.Request;
+
+public class AssignRoleRequest
+{
+ public int UserId { get; set; }
+
+ public int? RoleId { get; set; }
+}
diff --git a/Src/Dlhis.Sys/Identity/Request/RoleRemoveRequest.cs b/Src/Dlhis.Sys/Identity/Request/RoleRemoveRequest.cs
new file mode 100644
index 00000000..ade423a8
--- /dev/null
+++ b/Src/Dlhis.Sys/Identity/Request/RoleRemoveRequest.cs
@@ -0,0 +1,6 @@
+namespace Dlhis.Sys.Identity.Request;
+
+public class RoleRemoveRequest
+{
+ public int Id { get; set; }
+}
diff --git a/Src/Dlhis.Sys/Identity/Request/RoleSaveRequest.cs b/Src/Dlhis.Sys/Identity/Request/RoleSaveRequest.cs
new file mode 100644
index 00000000..843bc8c5
--- /dev/null
+++ b/Src/Dlhis.Sys/Identity/Request/RoleSaveRequest.cs
@@ -0,0 +1,11 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Dlhis.Sys.Identity.Request;
+
+public class RoleSaveRequest
+{
+ public int? Id { get; set; }
+
+ [Required(ErrorMessage = "角色名不能为空")]
+ public string Name { get; set; }
+}
diff --git a/Src/Dlhis.Sys/Identity/Services/IRoleService.cs b/Src/Dlhis.Sys/Identity/Services/IRoleService.cs
index 41af7fd6..eb101cc1 100644
--- a/Src/Dlhis.Sys/Identity/Services/IRoleService.cs
+++ b/Src/Dlhis.Sys/Identity/Services/IRoleService.cs
@@ -7,4 +7,12 @@ namespace Dlhis.User.Identity.Services;
public interface IRoleService : IScopeDependency
{
Task> GetPageAsync(RolePageRequest request);
+
+ Task SaveRoleAsync(RoleSaveRequest request);
+
+ Task GetMyRoleAsync();
+
+ Task RemoveRoleAsync(RoleRemoveRequest request);
+
+ Task AssignRoleAsync(AssignRoleRequest request);
}
diff --git a/Src/Dlhis.Sys/Identity/Services/Implements/RoleService.cs b/Src/Dlhis.Sys/Identity/Services/Implements/RoleService.cs
index 8768d181..399b1d7f 100644
--- a/Src/Dlhis.Sys/Identity/Services/Implements/RoleService.cs
+++ b/Src/Dlhis.Sys/Identity/Services/Implements/RoleService.cs
@@ -2,8 +2,11 @@
using Dlhis.Sys.Identity.Request;
using Dlhis.Sys.Identity.Response;
using Dlhis.User.Identity.Services;
+using Kurisu.AspNetCore.Scope;
+using Kurisu.AspNetCore.UnifyResultAndValidation;
using Kurisu.AspNetCore.Utils.Extensions;
using Kurisu.Core.DataAccess.Dto;
+using Kurisu.Core.User.Abstractions;
using Kurisu.SqlSugar.Extensions;
using Kurisu.SqlSugar.Services;
@@ -11,20 +14,162 @@ namespace Dlhis.Sys.Identity.Services.Implements;
public class RoleService : IRoleService
{
- private readonly IDbContext _dbContext;
+ private readonly IConfiguration _configuration;
+ private readonly ICurrentUser _currentUser;
- public RoleService(IDbContext dbContext, IConfiguration configuration)
+ public RoleService(IConfiguration configuration, ICurrentUser currentUser)
{
- var identityConnection = configuration.GetSection("SqlSugarOptions:IdentityConnectionString").Value;
- dbContext.Client.CurrentConnectionConfig.ConnectionString = identityConnection;
- _dbContext = dbContext;
+ _configuration = configuration;
+ _currentUser = currentUser;
}
public async Task> GetPageAsync(RolePageRequest request)
{
- return await _dbContext.Queryable()
+ return await Scoped.Request.Value.CreateAsync(async sp =>
+ {
+ var dbContext = sp.GetService();
+ var identityConnection = _configuration.GetSection("SqlSugarOptions:IdentityConnectionString").Value;
+ dbContext.Client.CurrentConnectionConfig.ConnectionString = identityConnection;
+
+
+ return await dbContext.Queryable()
.WhereIF(request.Search.IsPresent(), x => x.Name.Contains(request.Search) || x.NormalizedName.Contains(request.Search))
.Select()
.ToPageListAsync(request);
+ });
+ }
+
+ public async Task GetMyRoleAsync()
+ {
+ var userId = _currentUser.GetIntSubjectId();
+
+ return await Scoped.Request.Value.CreateAsync(async sp =>
+ {
+ var dbContext = sp.GetService();
+ var identityConnection = _configuration.GetSection("SqlSugarOptions:IdentityConnectionString").Value;
+ dbContext.Client.CurrentConnectionConfig.ConnectionString = identityConnection;
+
+
+ return await dbContext.Queryable()
+ .LeftJoin((x, r) => x.RoleId == r.Id)
+ .Where(x => x.UserId == userId)
+ .Select((x, r) => new RoleRespone
+ {
+ Id = r.Id,
+ Name = r.Name,
+ NormalizedName = r.NormalizedName,
+ })
+ .FirstAsync();
+ });
+ }
+
+ public async Task SaveRoleAsync(RoleSaveRequest request)
+ {
+ await Scoped.Request.Value.CreateAsync(async sp =>
+ {
+ var dbContext = sp.GetService();
+ var identityConnection = _configuration.GetSection("SqlSugarOptions:IdentityConnectionString").Value;
+ dbContext.Client.CurrentConnectionConfig.ConnectionString = identityConnection;
+
+
+ await CheckDuplicateNameAsync(dbContext, request);
+
+ if (!request.Id.HasValue)
+ {
+ await dbContext.InsertAsync(new RoleEntity
+ {
+ Name = request.Name,
+ NormalizedName = request.Name.ToUpperInvariant(),
+ ConcurrencyStamp = Guid.NewGuid(),
+ });
+ }
+ else
+ {
+ var entity = await dbContext.Queryable().Where(x => x.Id == request.Id).FirstAsync();
+ entity.ThrowIfNull("角色不存在");
+
+ entity.Name = request.Name;
+ entity.NormalizedName = request.Name.ToUpperInvariant();
+ entity.ConcurrencyStamp = Guid.NewGuid();
+
+ await dbContext.UpdateAsync(entity);
+ }
+ });
+ }
+
+
+ private async Task CheckDuplicateNameAsync(IDbContext db, RoleSaveRequest request)
+ {
+ var exists = await db.Queryable()
+ .WhereIF(request.Id.HasValue, x => x.Id != request.Id)
+ .Where(x => x.Name == request.Name)
+ .AnyAsync();
+
+ exists.ThrowIfTrue($"重复角色名称{request.Name}");
+ }
+
+ public async Task AssignRoleAsync(AssignRoleRequest request)
+ {
+ var myUserId = _currentUser.GetIntSubjectId();
+
+ await Scoped.Request.Value.CreateAsync(async sp =>
+ {
+ //不能被其他人修改
+ if (request.UserId == 1 && request.UserId != myUserId)
+ {
+ throw new UserFriendlyException("无法修改admin角色");
+ }
+
+ var dbContext = sp.GetService();
+ var identityConnection = _configuration.GetSection("SqlSugarOptions:IdentityConnectionString").Value;
+ dbContext.Client.CurrentConnectionConfig.ConnectionString = identityConnection;
+
+ var existsUser = await dbContext.Queryable().Where(x => x.Id == request.UserId).AnyAsync();
+ existsUser.ThrowIfFalse("分配的用户不存在");
+
+ var userRole = await dbContext.Queryable().Where(x => x.UserId == request.UserId).FirstAsync();
+
+ if (request.RoleId.HasValue)
+ {
+ var existsRole = await dbContext.Queryable().Where(x => x.Id == request.RoleId).AnyAsync();
+ existsUser.ThrowIfFalse("分配的角色不存在");
+
+ if (userRole == null)
+ {
+ await dbContext.InsertAsync(new UserRoleEntity
+ {
+ UserId = request.UserId,
+ RoleId = request.RoleId.Value,
+ });
+ }
+ else
+ {
+ await dbContext.Updateable()
+ .SetColumns(x => x.RoleId == request.RoleId)
+ .Where(x => x.UserId == request.UserId)
+ .ExecuteCommandAsync();
+ }
+ }
+ else
+ {
+ await dbContext.DeleteReallyAsync(userRole);
+ }
+ });
+ }
+
+ public async Task RemoveRoleAsync(RoleRemoveRequest request)
+ {
+ await Scoped.Request.Value.CreateAsync(async sp =>
+ {
+ var dbContext = sp.GetService();
+ var identityConnection = _configuration.GetSection("SqlSugarOptions:IdentityConnectionString").Value;
+ dbContext.Client.CurrentConnectionConfig.ConnectionString = identityConnection;
+
+ var role = await dbContext.Queryable().FirstAsync(x => x.Id == request.Id);
+ role.ThrowIfNull("角色不存在");
+
+ //删除角色,自动删除UserRoles对应关系
+ await dbContext.DeleteReallyAsync(role);
+ });
}
}