diff --git a/IOT_API/Controllers/Asset/EquipmentController.cs b/IOT_API/Controllers/Asset/EquipmentController.cs new file mode 100644 index 0000000..4e8336f --- /dev/null +++ b/IOT_API/Controllers/Asset/EquipmentController.cs @@ -0,0 +1,141 @@ +using Microsoft.AspNetCore.Mvc; +using Model; +using Model.Entity.Asset; +using ORM; +using Service.Implement; +using Service.Interface; +using SqlSugar; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace IOT_API.Controllers.Asset +{ + /// + /// 资产管理 - 设备台账控制器 + /// + [Route("api/Equipment")] //资产管理模块下的设备管理接口 + [ApiController] + public class EquipmentController : ControllerBase + { + private readonly IEquipmentService _equipmentService; + + public EquipmentController() + { + // 项目未注册 DI 容器,SqlSugarContext.DbContext 为静态单例,直接实例化服务 + _equipmentService = new EquipmentService( + new SqlSugarRepository(), + new SqlSugarRepository()); + } + + /// + /// 设备列表(分页查询,支持关键字/分类/状态筛选) + /// + /// 页码(从1开始,默认1) + /// 每页数量(默认10) + /// 关键字(模糊匹配设备编号/名称) + /// 设备分类Id(0表示不过滤) + /// 设备状态(不传表示不过滤) + [HttpGet("list")] + public async Task>> GetList(int pageIndex = 1, int pageSize = 10, + string? keyword = null, long categoryId = 0, EquipmentStatusEnum? status = null) + { + RefAsync total = 0; + var result = await _equipmentService.GetPagedAsync(pageIndex, pageSize, total, keyword, categoryId, status); + Response.Headers["X-Total-Count"] = total.Value.ToString(); + return result; + } + + /// + /// 查询全部设备(不分页,供下拉选择等场景使用) + /// + [HttpGet("all")] + public async Task>> GetAll() + { + return await _equipmentService.GetAllAsync(); + } + + /// + /// 设备详情 + /// + /// 设备主键 Id + [HttpGet("{id}")] + public async Task> GetById(long id) + { + return await _equipmentService.GetByIdAsync(id); + } + + /// + /// 新增设备 + /// + /// 设备实体 + [HttpPost] + public async Task> Add([FromBody] EquipmentEntity entity) + { + return await _equipmentService.InsertAsync(entity); + } + + /// + /// 修改设备 + /// + /// 设备主键 Id + /// 设备实体 + [HttpPut("{id}")] + public async Task> Update(long id, [FromBody] EquipmentEntity entity) + { + entity.Id = id; + return await _equipmentService.UpdateAsync(entity); + } + + /// + /// 删除设备(软删除) + /// + /// 设备主键 Id + [HttpDelete("{id}")] + public async Task> Delete(long id) + { + return await _equipmentService.DeleteEquipmentAsync(id); + } + + /// + /// 变更设备状态(同步记录状态变更历史) + /// + /// 设备主键 Id + /// 状态变更请求 + [HttpPost("{id}/status")] + public async Task> ChangeStatus(long id, [FromBody] ChangeStatusRequest request) + { + return await _equipmentService.ChangeStatusAsync(id, request.Status, request.Operator, request.Remark); + } + + /// + /// 查询设备状态变更历史记录 + /// + /// 设备主键 Id + [HttpGet("{id}/status-records")] + public async Task>> GetStatusRecords(long id) + { + return await _equipmentService.GetStatusRecordsAsync(id); + } + } + + /// + /// 状态变更请求体 + /// + public class ChangeStatusRequest + { + /// + /// 目标状态 + /// + public EquipmentStatusEnum Status { get; set; } + + /// + /// 操作人 + /// + public string? Operator { get; set; } + + /// + /// 变更原因/备注 + /// + public string? Remark { get; set; } + } +} diff --git a/IOT_API/Program.cs b/IOT_API/Program.cs index 4bf7877..9a2a91d 100644 --- a/IOT_API/Program.cs +++ b/IOT_API/Program.cs @@ -1,4 +1,5 @@ using Common; +using ORM; using System.Text.Json; namespace WebAPI @@ -7,33 +8,54 @@ namespace WebAPI { public static void Main(string[] args) { - // ======= תδ쳣ʱԶ MiniDump ======= + // ======= ����ת����δ�����쳣ʱ�Զ����� MiniDump ======= AppDomain.CurrentDomain.UnhandledException += (sender, e) => { - //¼dumpļ + //��¼dump�ļ� Exception ex = e.ExceptionObject as Exception; MiniDump.TryDump($"dumps\\Error_{DateTime.Now:yyyy-MM-dd HH-mm-ss-ms}.dmp", MiniDump.Option.WithFullMemory, ex); }; - // ======= ת ======= + // ======= ����ת������ ======= var builder = WebApplication.CreateBuilder(args); // Add services to the container. - // ======= ÿʼ ======= + // ======= �������ÿ�ʼ ======= builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; options.JsonSerializerOptions.NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString; - // 3. ԱԭǿתΪշ壩 + // 3. �����������Ա���ԭ������ǿ��תΪ�շ壩 options.JsonSerializerOptions.PropertyNamingPolicy = null; }); - // ======= ý ======= + // ======= �������ý��� ======= // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); + // ======= Database init start ======= + // Npgsql 时间兼容开关:允许向 PostgreSQL 写入本地时间 DateTime(DateTime.Now) + AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); + + // 从 appsettings.json 读取数据库配置(PostgreSQL) + var dbConfig = builder.Configuration.GetSection("Database"); + DatabaseConfig.InitPostgreSql( + dbConfig["Server"] ?? "localhost", + int.TryParse(dbConfig["Port"], out var port) ? port : 5432, + dbConfig["Database"] ?? "iot", + dbConfig["Uid"] ?? "postgres", + dbConfig["Pwd"] ?? ""); + + // 数据库不存在则创建,并检查连接(租户雪花机器码可按需设置) + DatabaseConfig.SetTenant(10001); + DatabaseConfig.CreateDatabaseAndCheckConnection(createDatabase: true, checkConnection: true); + + // CodeFirst 自动建表:扫描 Model.dll 中所有以 Entity 结尾的类建表/补列 + SqlSugarContext.InitDatabase(); + // ======= Database init end ======= + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -43,7 +65,7 @@ namespace WebAPI app.UseSwaggerUI(); } - // ע⣺λֻ֧ http ֳʱҪʵע͵ Https ض + // ע�⣺�����λ��ֻ֧�� http �����ֳ�����ʱ�������Ҫ����ʵ�����ע�͵��������� Https �ض��� app.UseHttpsRedirection(); app.UseAuthorization(); diff --git a/IOT_API/appsettings.json b/IOT_API/appsettings.json index 10f68b8..853529b 100644 --- a/IOT_API/appsettings.json +++ b/IOT_API/appsettings.json @@ -5,5 +5,12 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "Database": { + "Server": "localhost", + "Port": 5432, + "Database": "iot", + "Uid": "postgres", + "Pwd": "postgres" + } } diff --git a/Model/Entity/Asset/EquipmentAttachmentEntity.cs b/Model/Entity/Asset/EquipmentAttachmentEntity.cs new file mode 100644 index 0000000..e8a528f --- /dev/null +++ b/Model/Entity/Asset/EquipmentAttachmentEntity.cs @@ -0,0 +1,55 @@ +using SqlSugar; + +namespace Model.Entity.Asset +{ + /// + /// 设备附件(资产附件管理:文件上传,支持按文件类型筛选与预览) + /// + public class EquipmentAttachmentEntity : BaseEntity + { + /// + /// 设备Id(关联 EquipmentEntity.Id) + /// + public long EquipmentId { get; set; } + + /// + /// 文件名称 + /// + [SugarColumn(Length = 200)] + public string? FileName { get; set; } + + /// + /// 文件类型(使用说明书/合格证明/校准证书/保养卡/合同扫描件/验收照片等) + /// + public AttachmentTypeEnum FileType { get; set; } + + /// + /// 文件扩展名(如 .pdf、.jpg) + /// + [SugarColumn(Length = 20)] + public string? FileExt { get; set; } + + /// + /// 文件存储地址(上传后存储的相对路径) + /// + [SugarColumn(Length = 200)] + public string? FileUrl { get; set; } + + /// + /// 文件大小(字节) + /// + public long FileSize { get; set; } + + /// + /// 上传人 + /// + [SugarColumn(Length = 50)] + public string? Uploader { get; set; } + + /// + /// 备注 + /// + [SugarColumn(Length = 500)] + public string? Remark { get; set; } + } +} diff --git a/Model/Entity/Asset/EquipmentCategoryEntity.cs b/Model/Entity/Asset/EquipmentCategoryEntity.cs new file mode 100644 index 0000000..9c7aaab --- /dev/null +++ b/Model/Entity/Asset/EquipmentCategoryEntity.cs @@ -0,0 +1,43 @@ +using SqlSugar; + +namespace Model.Entity.Asset +{ + /// + /// 设备分类(资产分类:支持按实验室/楼层/部门/位置/产品等多级树形分类,可按类型筛选展示) + /// + public class EquipmentCategoryEntity : BaseEntity + { + /// + /// 父级分类Id(0表示顶级) + /// + public long ParentId { get; set; } + + /// + /// 分类名称 + /// + [SugarColumn(Length = 100)] + public string? Name { get; set; } + + /// + /// 分类编码 + /// + [SugarColumn(Length = 50)] + public string? Code { get; set; } + + /// + /// 层级(1级、2级……,便于树形展示与筛选) + /// + public int Level { get; set; } = 1; + + /// + /// 排序号 + /// + public int Sort { get; set; } + + /// + /// 备注 + /// + [SugarColumn(Length = 500)] + public string? Remark { get; set; } + } +} diff --git a/Model/Entity/Asset/EquipmentEntity.cs b/Model/Entity/Asset/EquipmentEntity.cs new file mode 100644 index 0000000..0c96c3f --- /dev/null +++ b/Model/Entity/Asset/EquipmentEntity.cs @@ -0,0 +1,229 @@ +using SqlSugar; + +namespace Model.Entity.Asset +{ + /// + /// 设备台账(资产台账) + /// + public class EquipmentEntity : BaseEntity + { + #region 基础信息(设备信息录入) + /// + /// 设备编号(唯一) + /// + [SugarColumn(Length = 50)] + public string? Code { get; set; } + + /// + /// 设备名称 + /// + [SugarColumn(Length = 100)] + public string? Name { get; set; } + + /// + /// 设备分类Id(关联 EquipmentCategoryEntity.Id) + /// + public long CategoryId { get; set; } + + /// + /// 设备类型(实验室/楼层/部门/位置/产品/自定义等分类维度下的类型名称,冗余便于展示) + /// + [SugarColumn(Length = 100)] + public string? Type { get; set; } + + /// + /// 设备品牌 + /// + [SugarColumn(Length = 100)] + public string? Brand { get; set; } + + /// + /// 设备型号 + /// + [SugarColumn(Length = 100)] + public string? Model { get; set; } + + /// + /// 规格参数 + /// + [SugarColumn(Length = 200)] + public string? Specifications { get; set; } + + /// + /// 制造商(设备制造商) + /// + [SugarColumn(Length = 100)] + public string? Manufacturer { get; set; } + + /// + /// 供应商(资产供应商) + /// + [SugarColumn(Length = 100)] + public string? Supplier { get; set; } + + /// + /// 设备图片地址(图片上传后存储的相对路径) + /// + [SugarColumn(Length = 200)] + public string? ImageUrl { get; set; } + + /// + /// 二维码编号(一物一码) + /// + [SugarColumn(Length = 50)] + public string? QrCode { get; set; } + + /// + /// 设备描述 + /// + [SugarColumn(ColumnDataType = "text")] + public string? Description { get; set; } + #endregion + + #region 权属与使用信息(设备分类管理:实验室/楼层/部门/位置) + /// + /// 使用部门名称(资产使用部门) + /// + [SugarColumn(Length = 100)] + public string? Department { get; set; } + + /// + /// 存放位置(实验室/楼层/房间等位置信息) + /// + [SugarColumn(Length = 200)] + public string? Location { get; set; } + + /// + /// 责任人(资产责任人) + /// + [SugarColumn(Length = 50)] + public string? ResponsiblePerson { get; set; } + + /// + /// 责任人联系电话(资产责任人电话) + /// + [SugarColumn(Length = 30)] + public string? ContactPhone { get; set; } + + /// + /// 保管人 + /// + [SugarColumn(Length = 50)] + public string? Custodian { get; set; } + #endregion + + #region 购置与财务信息(资产全生命周期:采购阶段) + /// + /// 购置日期 + /// + public DateTime? PurchaseDate { get; set; } + + /// + /// 购置价格(资产购置价格) + /// + [SugarColumn(DecimalDigits = 2, Length = 18)] + public decimal? PurchasePrice { get; set; } + + /// + /// 保修到期日期(资产保修到期) + /// + public DateTime? WarrantyExpireDate { get; set; } + #endregion + + #region 生命周期信息(资产全生命周期:验收/使用/报废阶段) + /// + /// 验收日期(资产验收日期) + /// + public DateTime? AcceptanceDate { get; set; } + + /// + /// 启用日期(资产启用日期) + /// + public DateTime? EnableDate { get; set; } + + /// + /// 使用年限(资产使用年限,单位:年) + /// + public int? ServiceLifeYears { get; set; } + + /// + /// 报废日期(资产报废日期) + /// + public DateTime? ScrapDate { get; set; } + #endregion + + #region 状态信息(设备状态管理:待验收→使用中→已借出→闲置→维修中→已停用→待处置→已处置→入库) + /// + /// 设备当前状态(状态流转记录见 EquipmentStatusRecordEntity) + /// + public EquipmentStatusEnum Status { get; set; } = EquipmentStatusEnum.PendingAcceptance; + #endregion + + #region 合规信息(设备合规管理:校准/检定/保养) + /// + /// 校准状态(正常/超期) + /// + public ComplianceStatusEnum CalibrationStatus { get; set; } = ComplianceStatusEnum.Normal; + + /// + /// 上次校准日期(校准日期) + /// + public DateTime? LastCalibrationDate { get; set; } + + /// + /// 下次校准到期日期(用于判断是否超期) + /// + public DateTime? NextCalibrationDate { get; set; } + + /// + /// 检定日期 + /// + public DateTime? InspectionDate { get; set; } + + /// + /// 下次检定到期日期(用于判断是否超期) + /// + public DateTime? NextInspectionDate { get; set; } + + /// + /// 保养状态(正常/超期) + /// + public ComplianceStatusEnum MaintenanceStatus { get; set; } = ComplianceStatusEnum.Normal; + + /// + /// 上次保养日期(保养日期) + /// + public DateTime? LastMaintenanceDate { get; set; } + + /// + /// 下次保养到期日期(保养周期到期,用于判断是否超期) + /// + public DateTime? NextMaintenanceDate { get; set; } + #endregion + + #region 审计信息 + /// + /// 备注 + /// + [SugarColumn(Length = 500)] + public string? Remark { get; set; } + + /// + /// 创建人 + /// + [SugarColumn(Length = 50)] + public string? CreateBy { get; set; } + + /// + /// 更新人(修改人) + /// + [SugarColumn(Length = 50)] + public string? UpdateBy { get; set; } + + /// + /// 更新时间(修改时间) + /// + public DateTime? UpdateTime { get; set; } + #endregion + } +} diff --git a/Model/Entity/Asset/EquipmentEnums.cs b/Model/Entity/Asset/EquipmentEnums.cs new file mode 100644 index 0000000..213c33c --- /dev/null +++ b/Model/Entity/Asset/EquipmentEnums.cs @@ -0,0 +1,110 @@ +namespace Model.Entity.Asset +{ + /// + /// 设备状态(设备状态管理:待验收 → 使用中 → 已借出 → 闲置 → 维修中 → 已停用 → 待处置 → 已处置 → 入库) + /// + public enum EquipmentStatusEnum + { + /// + /// 待验收 + /// + PendingAcceptance = 1, + + /// + /// 使用中 + /// + InUse = 2, + + /// + /// 已借出 + /// + Borrowed = 3, + + /// + /// 闲置 + /// + Idle = 4, + + /// + /// 维修中 + /// + Repairing = 5, + + /// + /// 已停用 + /// + Suspended = 6, + + /// + /// 待处置 + /// + PendingDisposal = 7, + + /// + /// 已处置 + /// + Disposed = 8, + + /// + /// 入库 + /// + InStorage = 9 + } + + /// + /// 合规状态(设备合规管理:校准状态、保养状态) + /// + public enum ComplianceStatusEnum + { + /// + /// 正常 + /// + Normal = 1, + + /// + /// 超期 + /// + Overdue = 2 + } + + /// + /// 附件文件类型(设备附件管理:文件类型筛选) + /// + public enum AttachmentTypeEnum + { + /// + /// 使用说明书 + /// + UserManual = 1, + + /// + /// 合格证明 + /// + Certificate = 2, + + /// + /// 校准证书 + /// + CalibrationCertificate = 3, + + /// + /// 保养卡 + /// + MaintenanceCard = 4, + + /// + /// 合同扫描件 + /// + Contract = 5, + + /// + /// 验收照片 + /// + AcceptancePhoto = 6, + + /// + /// 其他 + /// + Other = 99 + } +} diff --git a/Model/Entity/Asset/EquipmentStatusRecordEntity.cs b/Model/Entity/Asset/EquipmentStatusRecordEntity.cs new file mode 100644 index 0000000..f752229 --- /dev/null +++ b/Model/Entity/Asset/EquipmentStatusRecordEntity.cs @@ -0,0 +1,42 @@ +using SqlSugar; + +namespace Model.Entity.Asset +{ + /// + /// 设备台账状态记录(设备状态管理:状态变更历史记录,并可按时间筛选查询) + /// + public class EquipmentStatusRecordEntity : BaseEntity + { + /// + /// 设备Id(关联 EquipmentEntity.Id) + /// + public long EquipmentId { get; set; } + + /// + /// 变更前状态 + /// + public EquipmentStatusEnum FromStatus { get; set; } + + /// + /// 变更后状态 + /// + public EquipmentStatusEnum ToStatus { get; set; } + + /// + /// 状态变更时间 + /// + public DateTime ChangeTime { get; set; } + + /// + /// 操作人 + /// + [SugarColumn(Length = 50)] + public string? Operator { get; set; } + + /// + /// 变更原因/备注 + /// + [SugarColumn(Length = 500)] + public string? Remark { get; set; } + } +} diff --git a/ORM/DatabaseConfig.cs b/ORM/DatabaseConfig.cs index a790081..9eca36b 100644 --- a/ORM/DatabaseConfig.cs +++ b/ORM/DatabaseConfig.cs @@ -74,6 +74,12 @@ namespace ORM string DBPath = Path.Combine(folder, "SQL.db"); DbConnectionString =$@"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog={DBPath};Integrated Security=True;"; } + public static void InitPostgreSql(string Server, int Port, string Database, string Uid, string Pwd) + { + DbConnectionType = DbType.PostgreSQL; + DbConnectionString = + $"Host={Server};Port={Port};Database={Database};Username={Uid};Password={Pwd};"; + } #endregion /// /// 设置数据库连接字符串(可手动覆盖) diff --git a/Service/Implement/EquipmentService.cs b/Service/Implement/EquipmentService.cs new file mode 100644 index 0000000..025a644 --- /dev/null +++ b/Service/Implement/EquipmentService.cs @@ -0,0 +1,266 @@ +using Model; +using Model.Entity.Asset; +using ORM; +using Service.Interface; +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Service.Implement +{ + /// + /// 设备台账服务实现(资产管理) + /// + public class EquipmentService : BaseService, IEquipmentService + { + private readonly SqlSugarRepository _statusRecordRepository; + + public EquipmentService(SqlSugarRepository repository, + SqlSugarRepository statusRecordRepository) + : base(repository) + { + _statusRecordRepository = statusRecordRepository; + } + + /// + /// 查询全部设备(过滤已删除数据) + /// + public override async Task>> GetAllAsync() + { + try + { + var list = await _repository.Entities + .Where(x => x.IsDel == 0) + .OrderBy(x => x.CreateTime, OrderByType.Desc) + .ToListAsync(); + return Result>.Success(list); + } + catch (Exception ex) + { + return Result>.Error("查询设备台账失败", ex); + } + } + + /// + /// 分页查询设备台账(支持关键字、分类、状态筛选) + /// + public async Task>> GetPagedAsync(int pageIndex, int pageSize, RefAsync total, + string? keyword, long categoryId = 0, EquipmentStatusEnum? status = null) + { + try + { + var list = await _repository.Entities + .Where(x => x.IsDel == 0) + .WhereIF(!string.IsNullOrWhiteSpace(keyword), + x => x.Code!.Contains(keyword!) || x.Name!.Contains(keyword!)) + .WhereIF(categoryId > 0, x => x.CategoryId == categoryId) + .WhereIF(status.HasValue, x => x.Status == status!.Value) + .OrderBy(x => x.CreateTime, OrderByType.Desc) + .ToPageListAsync(pageIndex, pageSize, total); + total.Value = (int)Math.Ceiling((double)total.Value / pageSize); + return Result>.Success(list); + } + catch (Exception ex) + { + return Result>.Error("分页查询设备台账失败", ex); + } + } + + /// + /// 根据 Id 获取设备台账详情 + /// + public async Task> GetByIdAsync(long id) + { + try + { + var entity = await _repository.Entities + .Where(x => x.Id == id && x.IsDel == 0) + .FirstAsync(); + if (entity == null) + { + return Result.Error("设备不存在或已被删除"); + } + return Result.Success(entity); + } + catch (Exception ex) + { + return Result.Error("查询设备详情失败", ex); + } + } + + /// + /// 新增设备台账(校验编号唯一性,补充创建时间) + /// + public override async Task> InsertAsync(EquipmentEntity entity) + { + if (string.IsNullOrWhiteSpace(entity.Code)) + { + return Result.Error("设备编号不能为空"); + } + try + { + // 设备编号唯一性校验(未删除范围内) + bool exists = await _repository.Entities + .AnyAsync(x => x.Code == entity.Code && x.IsDel == 0); + if (exists) + { + return Result.Error($"设备编号【{entity.Code}】已存在"); + } + + entity.CreateTime = DateTime.Now; + var result = await _repository.Context.Insertable(entity).ExecuteCommandAsync(); + return Result.Success(result > 0); + } + catch (Exception ex) + { + return Result.Error("新增设备失败", ex); + } + } + + /// + /// 更新设备台账 + /// + public async Task> UpdateAsync(EquipmentEntity entity) + { + if (entity.Id <= 0) + { + return Result.Error("设备 Id 无效"); + } + try + { + var old = await _repository.Entities + .Where(x => x.Id == entity.Id && x.IsDel == 0) + .FirstAsync(); + if (old == null) + { + return Result.Error("设备不存在或已被删除"); + } + + // 设备编号变更时校验唯一性 + if (!string.IsNullOrWhiteSpace(entity.Code) && entity.Code != old.Code) + { + bool exists = await _repository.Entities + .AnyAsync(x => x.Code == entity.Code && x.IsDel == 0 && x.Id != entity.Id); + if (exists) + { + return Result.Error($"设备编号【{entity.Code}】已存在"); + } + } + + entity.IsDel = old.IsDel; + entity.CreateTime = old.CreateTime; + entity.UpdateTime = DateTime.Now; + var result = await _repository.Context.Updateable(entity).ExecuteCommandAsync(); + return Result.Success(result > 0); + } + catch (Exception ex) + { + return Result.Error("更新设备失败", ex); + } + } + + /// + /// 软删除设备台账(置 IsDel = 1) + /// + public async Task> DeleteEquipmentAsync(long id) + { + if (id <= 0) + { + return Result.Error("设备 Id 无效"); + } + try + { + var result = await _repository.Context.Updateable() + .SetColumns(x => x.IsDel == 1) + .Where(x => x.Id == id) + .ExecuteCommandAsync(); + return Result.Success(result > 0); + } + catch (Exception ex) + { + return Result.Error("删除设备失败", ex); + } + } + + /// + /// 变更设备状态,并记录状态变更历史(事务保证一致性) + /// + public async Task> ChangeStatusAsync(long equipmentId, EquipmentStatusEnum toStatus, string? operatorName, string? remark) + { + if (equipmentId <= 0) + { + return Result.Error("设备 Id 无效"); + } + try + { + var equipment = await _repository.Entities + .Where(x => x.Id == equipmentId && x.IsDel == 0) + .FirstAsync(); + if (equipment == null) + { + return Result.Error("设备不存在或已被删除"); + } + if (equipment.Status == toStatus) + { + return Result.Error("目标状态与当前状态一致,无需变更"); + } + + var now = DateTime.Now; + var record = new EquipmentStatusRecordEntity + { + EquipmentId = equipmentId, + FromStatus = equipment.Status, + ToStatus = toStatus, + ChangeTime = now, + Operator = operatorName, + Remark = remark, + CreateTime = now + }; + + // 事务:更新设备状态 + 插入状态变更记录 + _repository.BeginTran(); + try + { + equipment.Status = toStatus; + equipment.UpdateTime = now; + await _repository.Context.Updateable(equipment) + .UpdateColumns(x => new { x.Status, x.UpdateTime }) + .ExecuteCommandAsync(); + await _statusRecordRepository.Context.Insertable(record).ExecuteCommandAsync(); + _repository.CommitTran(); + } + catch + { + _repository.RollbackTran(); + throw; + } + + return Result.Success(true); + } + catch (Exception ex) + { + return Result.Error("变更设备状态失败", ex); + } + } + + /// + /// 查询指定设备的状态变更历史记录(按变更时间倒序) + /// + public async Task>> GetStatusRecordsAsync(long equipmentId) + { + try + { + var list = await _statusRecordRepository.Entities + .Where(x => x.EquipmentId == equipmentId && x.IsDel == 0) + .OrderBy(x => x.ChangeTime, OrderByType.Desc) + .ToListAsync(); + return Result>.Success(list); + } + catch (Exception ex) + { + return Result>.Error("查询状态变更记录失败", ex); + } + } + } +} diff --git a/Service/Interface/IEquipmentService.cs b/Service/Interface/IEquipmentService.cs new file mode 100644 index 0000000..72b74b1 --- /dev/null +++ b/Service/Interface/IEquipmentService.cs @@ -0,0 +1,65 @@ +using Model; +using Model.Entity.Asset; +using SqlSugar; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Service.Interface +{ + /// + /// 设备台账服务接口(资产管理) + /// + public interface IEquipmentService : IBaseService + { + /// + /// 分页查询设备台账(支持按关键字、分类、状态筛选) + /// + /// 页码(从1开始) + /// 每页数量 + /// 总页数(输出参数) + /// 关键字(模糊匹配设备编号/名称) + /// 设备分类Id(0表示不过滤) + /// 设备状态(null表示不过滤) + /// 返回包含分页数据的 Result + Task>> GetPagedAsync(int pageIndex, int pageSize, RefAsync total, + string? keyword, long categoryId = 0, EquipmentStatusEnum? status = null); + + /// + /// 根据 Id 获取设备台账详情 + /// + /// 主键 Id + /// 返回包含设备详情的 Result + Task> GetByIdAsync(long id); + + /// + /// 更新设备台账 + /// + /// 设备实体(必须包含 Id) + /// 返回操作是否成功的 Result + Task> UpdateAsync(EquipmentEntity entity); + + /// + /// 软删除设备台账(置 IsDel = 1) + /// + /// 主键 Id + /// 返回操作是否成功的 Result + Task> DeleteEquipmentAsync(long id); + + /// + /// 变更设备状态,并记录状态变更历史(事务保证一致性) + /// + /// 设备 Id + /// 目标状态 + /// 操作人 + /// 变更原因/备注 + /// 返回操作是否成功的 Result + Task> ChangeStatusAsync(long equipmentId, EquipmentStatusEnum toStatus, string? operatorName, string? remark); + + /// + /// 查询指定设备的状态变更历史记录(按变更时间倒序) + /// + /// 设备 Id + /// 返回包含状态记录列表的 Result + Task>> GetStatusRecordsAsync(long equipmentId); + } +}