diff --git a/Src/Dlhis.Basic/Device/Controllers/DevicesController.cs b/Src/Dlhis.Basic/Device/Controllers/DevicesController.cs
index 14c35704..400cff09 100644
--- a/Src/Dlhis.Basic/Device/Controllers/DevicesController.cs
+++ b/Src/Dlhis.Basic/Device/Controllers/DevicesController.cs
@@ -199,7 +199,17 @@ public class DevicesController : ControllerBase
}
///
- /// 获取近期30天新增设备数
+ /// 获取门禁在线/离线设备详情列表
+ ///
+ ///
+ [HttpGet("dooroffline")]
+ public async Task> GetDoorOfflineAsync([FromQuery] GetDoorOfflineRequest request)
+ {
+ return await _deviceService.GetDoorOfflineAsync(request);
+ }
+
+ ///
+ /// 获取近期30天考勤新增设备数
///
///
[HttpGet("recentnumbers")]
@@ -208,4 +218,15 @@ public class DevicesController : ControllerBase
return await _deviceService.GetRecentNumbersAsync();
}
+ ///
+ /// 批量导入设备信息
+ ///
+ ///
+ ///
+ [HttpPost("bulkUploads")]
+ public async Task BulkUploadsAsync([FromForm] BulkUploadsRequest request)
+ {
+ await _deviceService.BulkUploadsAsync(request);
+ }
+
}
diff --git a/Src/Dlhis.Basic/Device/Request/BulkUploadsRequest.cs b/Src/Dlhis.Basic/Device/Request/BulkUploadsRequest.cs
new file mode 100644
index 00000000..a0ff5fb0
--- /dev/null
+++ b/Src/Dlhis.Basic/Device/Request/BulkUploadsRequest.cs
@@ -0,0 +1,10 @@
+namespace Dlhis.Basic.Device.Request
+{
+ public class BulkUploadsRequest
+ {
+ ///
+ /// 设备信息文件
+ ///
+ public IFormFile FormFile { get; set; }
+ }
+}
diff --git a/Src/Dlhis.Basic/Device/Request/ImportDeviceSaveRequest.cs b/Src/Dlhis.Basic/Device/Request/ImportDeviceSaveRequest.cs
new file mode 100644
index 00000000..eddbcaed
--- /dev/null
+++ b/Src/Dlhis.Basic/Device/Request/ImportDeviceSaveRequest.cs
@@ -0,0 +1,76 @@
+using Dlhis.Entity.Device.Enums;
+using System.ComponentModel.DataAnnotations;
+
+namespace Dlhis.Basic.Device.Request
+{
+ public class ImportDeviceSaveRequest
+ {
+
+ public long? Id { get; set; }
+
+ ///
+ /// 设备唯一号
+ ///
+ public string UniqueNo { get; set; }
+
+ ///
+ /// 模板code
+ ///
+ public Guid TemplateCode { get; set; }
+
+ ///
+ /// 供应商信息
+ ///
+ public string Supplier { get; set; }
+
+ ///
+ /// 设备功能
+ ///
+ public List Funcs { get; set; }
+
+ ///
+ /// 设备类型 DeviceTypeEnumType
+ ///
+ public DeviceTypeEnumType Type { get; set; }
+
+ ///
+ /// 心跳间隔
+ ///
+ public int HeartbeatInterval { get; set; }
+
+ ///
+ /// 部署点
+ ///
+ public string DeployPlace { get; set; }
+
+ ///
+ /// 部署坐标
+ ///
+ public string DeployPosition { get; set; }
+
+ ///
+ /// 部署方向
+ ///
+ public string DeployDirection { get; set; }
+
+ ///
+ /// 连接信息
+ ///
+ public string ConnectionInfo { get; set; }
+
+ ///
+ /// 是否启用
+ ///
+ public bool Enable { get; set; }
+
+ ///
+ /// 是否作为考勤设备
+ ///
+ public bool AsAttendance { get; set; }
+
+ ///
+ /// 营业点code
+ ///
+ public Guid? PlaceeCode { get; set; }
+ }
+}
diff --git a/Src/Dlhis.Basic/Device/Response/GetDoorOfflineRequest.cs b/Src/Dlhis.Basic/Device/Response/GetDoorOfflineRequest.cs
new file mode 100644
index 00000000..89d4eff4
--- /dev/null
+++ b/Src/Dlhis.Basic/Device/Response/GetDoorOfflineRequest.cs
@@ -0,0 +1,9 @@
+using Kurisu.Core.DataAccess.Dto;
+
+namespace Dlhis.Basic.Device.Response
+{
+ public class GetDoorOfflineRequest : PageDto
+ {
+ public bool IsOnAndOff { get; set; }
+ }
+}
diff --git a/Src/Dlhis.Basic/Device/Services/IDeviceService.cs b/Src/Dlhis.Basic/Device/Services/IDeviceService.cs
index 7f47e38f..a5549121 100644
--- a/Src/Dlhis.Basic/Device/Services/IDeviceService.cs
+++ b/Src/Dlhis.Basic/Device/Services/IDeviceService.cs
@@ -23,6 +23,11 @@ public interface IDeviceService : IScopeDependency
Task> GetAttendanceOfflineAsync(DeviceOnlineAndofflineRequest request);
+ Task> GetDoorOfflineAsync(GetDoorOfflineRequest request);
+
Task> GetRecentNumbersAsync();
+ Task BulkUploadsAsync(BulkUploadsRequest request);
+
+
}
diff --git a/Src/Dlhis.Basic/Device/Services/Implements/DeviceService.cs b/Src/Dlhis.Basic/Device/Services/Implements/DeviceService.cs
index f43f3c7a..9563702a 100644
--- a/Src/Dlhis.Basic/Device/Services/Implements/DeviceService.cs
+++ b/Src/Dlhis.Basic/Device/Services/Implements/DeviceService.cs
@@ -12,8 +12,12 @@ using Kurisu.SqlSugar.Extensions;
using Kurisu.SqlSugar.Services;
using Mapster;
using Microsoft.AspNetCore.Mvc;
+using MySqlX.XDevAPI.Relational;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
+using NPOI.OpenXmlFormats;
+using NPOI.SS.UserModel;
+using NPOI.XSSF.UserModel;
using SqlSugar;
namespace Dlhis.Basic.Device.Services.Implements;
@@ -123,6 +127,53 @@ public class DeviceService : IDeviceService
await GenerateUploadRecordLogForBeginTimeAsync(entity);
}
+ ///
+ /// 批量导入
+ ///
+ ///
+ ///
+ public async Task BulkUploadsAsync(BulkUploadsRequest request)
+ {
+ var lst = ImportFromExcel(request.FormFile);
+ var places = await _dbContext.Queryable().ToListAsync();
+
+ foreach (var form in lst)
+ {
+ if (form.PlaceeCode != null)
+ {
+ var place = places.Where(x => x.Code == form.PlaceeCode).FirstOrDefault();
+ place.ThrowIfNull($"该设备{form.UniqueNo}配置的营业点营业点不存在!");
+ }
+ //插入
+ var exists = await _dbContext.Queryable().Where(x => x.UniqueNo == form.UniqueNo)
+ .AnyAsync();
+ exists.ThrowIfTrue("唯一号已经存在");
+ var template = await CheckTemplateExistsAsync(form.TemplateCode);
+ await CheckTemplateFieldAsync(form.TemplateCode, form.ConnectionInfo);
+
+ var saveEntity = form.Adapt();
+ saveEntity.Code = Guid.NewGuid();
+ await _dbContext.InsertAsync(saveEntity);
+ //保存注册信息
+ var regis = await _dbContext.Queryable()
+ .Where(x => x.DeviceUniqueNo == form.UniqueNo)
+ .FirstAsync();
+ if(regis == null)
+ {
+ var custCode = places.Where(x => x.Code == form.PlaceeCode).Select(x => x.CustomCode).FirstOrDefault();
+ regis = new DeviceRegistryEntity
+ {
+ DeviceUniqueNo = form.UniqueNo,
+ LastHeartbeatTime = DateTime.Now,
+ RegisterTime = DateTime.Now,
+ BusinessPlaceCode = form.PlaceeCode,
+ BusinessPlaceCustomCode = custCode
+ };
+ await _dbContext.InsertAsync(regis);
+ }
+ }
+ }
+
//******************************************************** help methods **********************************************************
private async Task GenerateUploadRecordLogForBeginTimeAsync(DeviceEntity entity)
@@ -343,7 +394,7 @@ public class DeviceService : IDeviceService
//离线
var offpage = await _dbContext.Queryable()
.LeftJoin((d, dt) => d.TemplateCode == dt.Code)
- .Where(d => d.Type == DeviceTypeEnumType.Attendance && d.Enable)
+ .Where(d => d.Type == DeviceTypeEnumType.Attendance)
.Where(d => offids.Contains(d.Id))
.Select((d, dt) => new DevicePageResponse
{
@@ -361,7 +412,7 @@ public class DeviceService : IDeviceService
var page = await _dbContext.Queryable()
.LeftJoin((d, dt) => d.TemplateCode == dt.Code)
- .Where(d => d.Type == DeviceTypeEnumType.Attendance && d.Enable)
+ .Where(d => d.Type == DeviceTypeEnumType.Attendance)
.Where(d => onids.Contains(d.Id))
.Select((d, dt) => new DevicePageResponse
{
@@ -375,6 +426,51 @@ public class DeviceService : IDeviceService
return page;
}
+ public async Task> GetDoorOfflineAsync(GetDoorOfflineRequest request)
+ {
+ var devices = await _dbContext.Queryable().Where(x => x.Type == DeviceTypeEnumType.Door).ToListAsync();
+
+ var offDevices = await GetOfflineDevicesCount(devices);
+ var offids = offDevices.Select(x => x.Id).ToList();
+
+ if (request.IsOnAndOff)
+ {
+ //离线
+ var offpage = await _dbContext.Queryable()
+ .LeftJoin((d, dt) => d.TemplateCode == dt.Code)
+ .Where(d => d.Type == DeviceTypeEnumType.Door)
+ .Where(d => offids.Contains(d.Id))
+ .Select((d, dt) => new DevicePageResponse
+ {
+ FuncsStr = d.Funcs.ToString(),
+ Brand = dt.Brand,
+ Type = d.Type,
+
+ }, true)
+ .ToPageListAsync(request);
+ return offpage;
+ }
+ //在线
+ var ids = devices.Select(x => x.Id).ToList();
+ List onids = ids.Except(offids).ToList();
+
+ var page = await _dbContext.Queryable()
+ .LeftJoin((d, dt) => d.TemplateCode == dt.Code)
+ .Where(d => d.Type == DeviceTypeEnumType.Door)
+ .Where(d => onids.Contains(d.Id))
+ .Select((d, dt) => new DevicePageResponse
+ {
+ FuncsStr = d.Funcs.ToString(),
+ Brand = dt.Brand,
+ Type = d.Type,
+
+ }, true)
+ .ToPageListAsync(request);
+
+ return page;
+
+ }
+
public async Task> GetRecentNumbersAsync()
{
//获取近30天并转化为"yyyy-MM-dd"集合
@@ -387,7 +483,7 @@ public class DeviceService : IDeviceService
times.Add(dateString);
}
- var devices = await _dbContext.Queryable().ToListAsync();
+ var devices = await _dbContext.Queryable().Where(x => x.Type == DeviceTypeEnumType.Attendance || (x.Type == DeviceTypeEnumType.Door && x.AsAttendance)).ToListAsync();
var list = new List();
times.ForEach(time =>
@@ -403,5 +499,68 @@ public class DeviceService : IDeviceService
return list;
}
+ private List ImportFromExcel(IFormFile formFile)
+ {
+ var requests = new List();
+
+ using (var stream = formFile.OpenReadStream())
+ {
+ XSSFWorkbook workbook = new XSSFWorkbook(stream);
+ ISheet sheet = workbook.GetSheetAt(0);
+
+ for (int rowIdx = 1; rowIdx <= sheet.LastRowNum; rowIdx++)
+ {
+ IRow row = sheet.GetRow(rowIdx);
+ if (row == null) continue;
+
+ var request = new ImportDeviceSaveRequest();
+
+ request.UniqueNo = row.GetCell(0).ToString();
+ request.TemplateCode = Guid.Parse(row.GetCell(1).ToString());
+ request.Supplier = row.GetCell(2).ToString();
+
+ var funs = new List();
+ var fs = row.GetCell(3).ToString().Split(',');
+ foreach (string str in fs)
+ {
+ switch (str)
+ {
+ case "1":
+ funs.Add(DeviceFuncEnumType.Face);
+ break;
+ case "2":
+ funs.Add(DeviceFuncEnumType.Card);
+ break;
+ case "3":
+ funs.Add(DeviceFuncEnumType.QrCode);
+ break;
+ case "4":
+ funs.Add(DeviceFuncEnumType.Control);
+ break;
+ default:
+ break;
+ }
+ }
+ request.Funcs = funs;
+ request.Type = (DeviceTypeEnumType)Enum.Parse(typeof(DeviceTypeEnumType), row.GetCell(4).ToString());
+ request.HeartbeatInterval = int.Parse(row.GetCell(5).ToString());
+ request.DeployPlace = row.GetCell(6).ToString();
+ request.DeployPosition = row.GetCell(7).ToString();
+ request.DeployDirection = row.GetCell(8).ToString();
+ request.ConnectionInfo = row.GetCell(9).ToString();
+ request.Enable = Convert.ToBoolean(Convert.ToInt32(row.GetCell(10).ToString()));
+ request.AsAttendance = Convert.ToBoolean(Convert.ToInt32(row.GetCell(11).ToString()));
+ if (row.GetCell(12) != null)
+ {
+ request.PlaceeCode = Guid.Parse(row.GetCell(12)?.ToString());
+ }
+ requests.Add(request);
+ }
+ }
+
+ return requests;
+ }
}
+
+
diff --git a/Src/Dlhis.Basic/Dlhis.Basic.csproj b/Src/Dlhis.Basic/Dlhis.Basic.csproj
index ffe86e4d..d0e8b356 100644
--- a/Src/Dlhis.Basic/Dlhis.Basic.csproj
+++ b/Src/Dlhis.Basic/Dlhis.Basic.csproj
@@ -18,6 +18,7 @@
+
diff --git a/Src/Dlhis.Basic/Summary/Controllers/SummariesController.cs b/Src/Dlhis.Basic/Summary/Controllers/SummariesController.cs
index 0f7f9fb0..2acddc4e 100644
--- a/Src/Dlhis.Basic/Summary/Controllers/SummariesController.cs
+++ b/Src/Dlhis.Basic/Summary/Controllers/SummariesController.cs
@@ -1,4 +1,5 @@
-using Dlhis.Basic.Summary.Response;
+using Dlhis.Basic.Device.Response;
+using Dlhis.Basic.Summary.Response;
using Dlhis.Basic.Summary.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -26,4 +27,14 @@ public class SummariesController : ControllerBase
{
return await _summaryService.GetAttendanceSummaryAsync();
}
+
+ ///
+ /// 门禁首页数据
+ ///
+ ///
+ [HttpGet("door-summary")]
+ public async Task GetDoorHomeDataAsync()
+ {
+ return await _summaryService.GetDoorHomeDataAsync();
+ }
}
diff --git a/Src/Dlhis.Basic/Summary/Response/DoorHomeDataResponse.cs b/Src/Dlhis.Basic/Summary/Response/DoorHomeDataResponse.cs
new file mode 100644
index 00000000..163a35fe
--- /dev/null
+++ b/Src/Dlhis.Basic/Summary/Response/DoorHomeDataResponse.cs
@@ -0,0 +1,11 @@
+using Dlhis.Basic.Summary.Dto;
+
+namespace Dlhis.Basic.Summary.Response
+{
+ public class DoorHomeDataResponse
+ {
+ public int OffNum { get; set; }
+ public int TotalNum { get; set; }
+ public List Trend { get; set; }
+ }
+}
diff --git a/Src/Dlhis.Basic/Summary/Services/ISummaryService.cs b/Src/Dlhis.Basic/Summary/Services/ISummaryService.cs
index 775379d2..9c92e218 100644
--- a/Src/Dlhis.Basic/Summary/Services/ISummaryService.cs
+++ b/Src/Dlhis.Basic/Summary/Services/ISummaryService.cs
@@ -1,8 +1,11 @@
-using Dlhis.Basic.Summary.Response;
+using Dlhis.Basic.Device.Response;
+using Dlhis.Basic.Summary.Response;
namespace Dlhis.Basic.Summary.Services;
public interface ISummaryService : IScopeDependency
{
Task GetAttendanceSummaryAsync();
+
+ Task GetDoorHomeDataAsync();
}
diff --git a/Src/Dlhis.Basic/Summary/Services/Implements/SummaryService.cs b/Src/Dlhis.Basic/Summary/Services/Implements/SummaryService.cs
index a2306942..4feeb179 100644
--- a/Src/Dlhis.Basic/Summary/Services/Implements/SummaryService.cs
+++ b/Src/Dlhis.Basic/Summary/Services/Implements/SummaryService.cs
@@ -1,4 +1,5 @@
-using Dlhis.Basic.Organization.Services;
+using Dlhis.Basic.Device.Response;
+using Dlhis.Basic.Organization.Services;
using Dlhis.Basic.Summary.Dto;
using Dlhis.Basic.Summary.Response;
using Dlhis.Entity.Basic.Entity;
@@ -81,6 +82,42 @@ public class SummaryService : ISummaryService
};
}
+ public async Task GetDoorHomeDataAsync()
+ {
+ var devices = await _dbContext.Queryable().Where(x => x.Type == DeviceTypeEnumType.Door).ToListAsync();
+
+ var offDevices = await GetOfflineDevicesCount(devices);
+
+ //获取近30天并转化为"yyyy-MM-dd"集合
+ DateTime currentDate = DateTime.Now;
+ List times = new List();
+ for (int i = 0; i < 30; i++)
+ {
+ DateTime date = currentDate.AddDays(-i);
+ string dateString = date.ToString("yyyy-MM-dd");
+ times.Add(dateString);
+ }
+
+ var list = new List();
+ times.ForEach(time =>
+ {
+ var count = devices.Where(x => x.CreateTime.ToString("yyyy-MM-dd") == time).Count();
+ var trend = new TrendDto
+ {
+ Date = DateTime.ParseExact(time, "yyyy-MM-dd", null),
+ Count = count,
+ };
+ list.Add(trend);
+ });
+ var res = new DoorHomeDataResponse
+ {
+ OffNum = offDevices.Count,
+ TotalNum = devices.Count,
+ Trend = list
+ };
+ return res;
+ }
+
private static List FixTrend(IEnumerable dtos, DateTime start, DateTime end)
{
var result = dtos.ToList();
@@ -104,4 +141,49 @@ public class SummaryService : ISummaryService
return result.OrderBy(x => x.Date).ToList();
}
+
+ ///
+ /// 获取离线设备
+ ///
+ ///
+ private async Task> GetOfflineDevicesCount(List devices)
+ {
+ var now = DateTime.Now;
+
+ 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
+ {
+ UniqueNo = d.UniqueNo,
+ d.HeartbeatInterval,
+ x.LastHeartbeatTime
+ })
+ .ToListAsync();
+
+ var OnlineDevices = deviceRegistries
+ .Where(x =>
+ {
+ if (x.HeartbeatInterval <= 0)
+ {
+ return true;
+ }
+
+ return x.LastHeartbeatTime.AddSeconds(x.HeartbeatInterval * 3) >= now;
+
+ })
+ .Select(x => new
+ {
+ UniqueNo = x.UniqueNo,
+ })
+ .ToList();
+ var onlineUniqueNos = new List();
+ OnlineDevices.ForEach(x => onlineUniqueNos.Add(x.UniqueNo));
+
+ var offUniqueNos = deviceUniqueNos.Except(onlineUniqueNos).ToList();
+ var list = devices.Where(x => offUniqueNos.Contains(x.UniqueNo)).ToList();
+ return list;
+ }
}