478 lines
18 KiB
C#
478 lines
18 KiB
C#
using Infrastructure;
|
||
using Infrastructure.Attribute;
|
||
using ZR.Model;
|
||
using ZR.Model.Business;
|
||
using ZR.Model.Business.Dto;
|
||
using ZR.Model.Dto;
|
||
using ZR.Repository;
|
||
using ZR.Service.Business.IBusinessService;
|
||
|
||
namespace ZR.Service.Business
|
||
{
|
||
/// <summary>
|
||
/// 干线故障Service业务层处理
|
||
/// </summary>
|
||
[AppService(ServiceType = typeof(IOdfCableFaultsService), ServiceLifetime = LifeTime.Transient)]
|
||
public class OdfCableFaultsService : BaseService<OdfCableFaults>, IOdfCableFaultsService
|
||
{
|
||
/// <summary>
|
||
/// 按 CableId 分页查询故障列表,联查光缆名称,按 FaultTime DESC 排序
|
||
/// </summary>
|
||
public PagedInfo<dynamic> GetList(OdfCableFaultsQueryDto parm)
|
||
{
|
||
var predicate = QueryExp(parm);
|
||
|
||
var total = 0;
|
||
var list = Queryable()
|
||
.Where(predicate.ToExpression())
|
||
.LeftJoin<OdfCables>((f, c) => f.CableId == c.Id)
|
||
.Select((f, c) => new
|
||
{
|
||
f.Id,
|
||
f.CableId,
|
||
f.FaultTime,
|
||
f.Personnel,
|
||
f.FaultReason,
|
||
f.Mileage,
|
||
f.MileageCorrection,
|
||
f.Location,
|
||
f.Latitude,
|
||
f.Longitude,
|
||
f.Remark,
|
||
f.CreatedAt,
|
||
f.FaultCount,
|
||
CableName = c.CableName
|
||
})
|
||
.MergeTable()
|
||
.OrderByDescending(it => it.FaultTime)
|
||
.ToPageList(parm.PageNum, parm.PageSize, ref total);
|
||
|
||
// 批量查询频次时间记录
|
||
var faultIds = list.Select(x => x.Id).ToList();
|
||
var allFaultTimes = GetFaultTimesByFaultIds(faultIds);
|
||
|
||
var resultList = list.Select(item =>
|
||
{
|
||
var times = new List<string>();
|
||
if (allFaultTimes.ContainsKey(item.Id))
|
||
{
|
||
times = allFaultTimes[item.Id].Select(t => t.ToString("yyyy-MM-dd HH:mm:ss")).ToList();
|
||
}
|
||
return new
|
||
{
|
||
item.Id,
|
||
item.CableId,
|
||
item.FaultTime,
|
||
item.Personnel,
|
||
item.FaultReason,
|
||
item.Mileage,
|
||
item.MileageCorrection,
|
||
item.Location,
|
||
item.Latitude,
|
||
item.Longitude,
|
||
item.Remark,
|
||
item.CreatedAt,
|
||
item.FaultCount,
|
||
item.CableName,
|
||
FaultTimes = times
|
||
};
|
||
}).ToList();
|
||
|
||
return new PagedInfo<dynamic>
|
||
{
|
||
Result = resultList.Cast<dynamic>().ToList(),
|
||
TotalNum = total,
|
||
PageIndex = parm.PageNum,
|
||
PageSize = parm.PageSize
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查询故障详情,联查光缆名称和图片列表
|
||
/// </summary>
|
||
public object GetDetail(int id)
|
||
{
|
||
var fault = Queryable()
|
||
.LeftJoin<OdfCables>((f, c) => f.CableId == c.Id)
|
||
.Where((f, c) => f.Id == id)
|
||
.Select((f, c) => new
|
||
{
|
||
f.Id,
|
||
f.CableId,
|
||
f.FaultTime,
|
||
f.Personnel,
|
||
f.FaultReason,
|
||
f.Mileage,
|
||
f.MileageCorrection,
|
||
f.Location,
|
||
f.Latitude,
|
||
f.Longitude,
|
||
f.Remark,
|
||
f.UserId,
|
||
f.CreatedAt,
|
||
CableName = c.CableName
|
||
})
|
||
.First();
|
||
|
||
if (fault == null)
|
||
{
|
||
throw new CustomException("故障记录不存在");
|
||
}
|
||
|
||
// 查询关联图片列表
|
||
var images = Context.Queryable<OdfCableFaultImages>()
|
||
.Where(img => img.FaultId == id)
|
||
.OrderBy(img => img.Id)
|
||
.Select(img => new { img.Id, img.ImageUrl })
|
||
.ToList();
|
||
|
||
var faultTimes = Context.Queryable<OdfCableFaultTimes>()
|
||
.Where(t => t.FaultId == id)
|
||
.OrderBy(t => t.FaultTime)
|
||
.Select(t => new { t.Id, t.FaultTime })
|
||
.ToList();
|
||
|
||
return new
|
||
{
|
||
fault.Id,
|
||
fault.CableId,
|
||
fault.FaultTime,
|
||
fault.Personnel,
|
||
fault.FaultReason,
|
||
fault.Mileage,
|
||
fault.MileageCorrection,
|
||
fault.Location,
|
||
fault.Latitude,
|
||
fault.Longitude,
|
||
fault.Remark,
|
||
fault.CableName,
|
||
fault.CreatedAt,
|
||
FaultCount = Context.Queryable<OdfCableFaults>().Where(f => f.Id == id).First()?.FaultCount ?? 1,
|
||
FaultTimes = faultTimes,
|
||
Images = images
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 新增故障(图片已上传至 COS,仅保存 URL)
|
||
/// </summary>
|
||
public async Task<int> AddFault(OdfCableFaultAddDto dto)
|
||
{
|
||
// 校验 CableId 存在
|
||
var cable = Context.Queryable<OdfCables>()
|
||
.Where(c => c.Id == dto.CableId)
|
||
.First();
|
||
if (cable == null)
|
||
{
|
||
throw new CustomException("光缆不存在");
|
||
}
|
||
|
||
// 校验至少 1 张图片
|
||
if (dto.ImageUrls == null || dto.ImageUrls.Length == 0)
|
||
{
|
||
throw new CustomException("请至少上传一张图片");
|
||
}
|
||
|
||
// 插入故障记录
|
||
var model = new OdfCableFaults
|
||
{
|
||
CableId = dto.CableId,
|
||
FaultTime = DateTime.Parse(dto.FaultTime),
|
||
Personnel = dto.Personnel,
|
||
FaultReason = dto.FaultReason,
|
||
Mileage = dto.Mileage,
|
||
MileageCorrection = dto.MileageCorrection,
|
||
Location = dto.Location,
|
||
Latitude = dto.Latitude,
|
||
Longitude = dto.Longitude,
|
||
Remark = dto.Remark,
|
||
UserId = dto.UserId,
|
||
CreatedAt = DateTime.Now,
|
||
UpdatedAt = DateTime.Now
|
||
};
|
||
|
||
var faultEntity = Insertable(model).ExecuteReturnEntity();
|
||
int faultId = faultEntity.Id;
|
||
|
||
// 插入图片记录(COS URL)
|
||
foreach (var imageUrl in dto.ImageUrls)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(imageUrl)) continue;
|
||
|
||
var imageRecord = new OdfCableFaultImages
|
||
{
|
||
FaultId = faultId,
|
||
ImageUrl = imageUrl,
|
||
CreatedAt = DateTime.Now
|
||
};
|
||
|
||
Context.Insertable(imageRecord).ExecuteCommand();
|
||
}
|
||
|
||
return faultId;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除故障记录并级联删除关联图片和频次时间记录
|
||
/// </summary>
|
||
public int Delete(int id)
|
||
{
|
||
// 删除关联频次时间记录
|
||
Context.Deleteable<OdfCableFaultTimes>()
|
||
.Where(t => t.FaultId == id)
|
||
.ExecuteCommand();
|
||
|
||
// 删除关联图片记录
|
||
Context.Deleteable<OdfCableFaultImages>()
|
||
.Where(img => img.FaultId == id)
|
||
.ExecuteCommand();
|
||
|
||
// 删除故障记录
|
||
return base.Delete(id);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 导出故障列表
|
||
/// </summary>
|
||
public List<OdfCableFaultExportDto> ExportList(OdfCableFaultsQueryDto parm)
|
||
{
|
||
var predicate = QueryExp(parm);
|
||
|
||
return Queryable()
|
||
.Where(predicate.ToExpression())
|
||
.LeftJoin<OdfCables>((f, c) => f.CableId == c.Id)
|
||
.Select((f, c) => new OdfCableFaultExportDto
|
||
{
|
||
Id = f.Id,
|
||
CableId = f.CableId,
|
||
FaultTime = f.FaultTime,
|
||
Personnel = f.Personnel,
|
||
FaultReason = f.FaultReason,
|
||
Mileage = f.Mileage,
|
||
MileageCorrection = f.MileageCorrection,
|
||
Location = f.Location,
|
||
Latitude = f.Latitude,
|
||
Longitude = f.Longitude,
|
||
Remark = f.Remark,
|
||
CreatedAt = f.CreatedAt,
|
||
FaultCount = f.FaultCount,
|
||
CableName = c.CableName
|
||
})
|
||
.MergeTable()
|
||
.OrderByDescending(it => it.FaultTime)
|
||
.ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 增加故障频次:FaultCount+1,同时插入一条时间记录
|
||
/// </summary>
|
||
public object IncrementFaultCount(int id)
|
||
{
|
||
var fault = GetFirst(f => f.Id == id);
|
||
if (fault == null)
|
||
{
|
||
throw new CustomException("故障记录不存在");
|
||
}
|
||
|
||
fault.FaultCount += 1;
|
||
fault.UpdatedAt = DateTime.Now;
|
||
Update(fault, true);
|
||
|
||
var now = DateTime.Now;
|
||
var timeRecord = new OdfCableFaultTimes
|
||
{
|
||
FaultId = id,
|
||
FaultTime = now,
|
||
CreatedAt = now
|
||
};
|
||
Context.Insertable(timeRecord).ExecuteCommand();
|
||
|
||
var faultTimes = Context.Queryable<OdfCableFaultTimes>()
|
||
.Where(t => t.FaultId == id)
|
||
.OrderBy(t => t.FaultTime)
|
||
.Select(t => new { t.Id, t.FaultTime })
|
||
.ToList();
|
||
|
||
return new
|
||
{
|
||
FaultCount = fault.FaultCount,
|
||
FaultTimes = faultTimes
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 批量查询故障频次时间记录,按故障ID分组
|
||
/// </summary>
|
||
public Dictionary<int, List<DateTime>> GetFaultTimesByFaultIds(List<int> faultIds)
|
||
{
|
||
if (faultIds == null || faultIds.Count == 0)
|
||
return new Dictionary<int, List<DateTime>>();
|
||
|
||
var records = Context.Queryable<OdfCableFaultTimes>()
|
||
.Where(t => faultIds.Contains(t.FaultId))
|
||
.OrderBy(t => t.FaultTime)
|
||
.ToList();
|
||
|
||
return records
|
||
.GroupBy(t => t.FaultId)
|
||
.ToDictionary(g => g.Key, g => g.Select(t => t.FaultTime).ToList());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查询表达式
|
||
/// </summary>
|
||
private static Expressionable<OdfCableFaults> QueryExp(OdfCableFaultsQueryDto parm)
|
||
{
|
||
var predicate = Expressionable.Create<OdfCableFaults>();
|
||
|
||
predicate = predicate.AndIF(parm.CableId != null, it => it.CableId == parm.CableId);
|
||
predicate = predicate.AndIF(parm.BeginFaultTime != null, it => it.FaultTime >= parm.BeginFaultTime);
|
||
predicate = predicate.AndIF(parm.EndFaultTime != null, it => it.FaultTime <= parm.EndFaultTime);
|
||
predicate = predicate.AndIF(!string.IsNullOrEmpty(parm.FaultReason), it => it.FaultReason.Contains(parm.FaultReason));
|
||
|
||
return predicate;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 批量导入故障(导出的文件可直接导入)
|
||
/// </summary>
|
||
public (int successCount, int errorCount, string errorMsg) ImportFaults(List<OdfCableFaultImportDto> list)
|
||
{
|
||
var logger = NLog.LogManager.GetCurrentClassLogger();
|
||
int successCount = 0;
|
||
int errorCount = 0;
|
||
var errorMsgs = new List<string>();
|
||
|
||
// 预查所有光缆,用于按名称匹配CableId
|
||
var cableNames = list.Where(x => !string.IsNullOrWhiteSpace(x.CableName)).Select(x => x.CableName.Trim()).Distinct().ToList();
|
||
var cables = Context.Queryable<OdfCables>().Where(c => cableNames.Contains(c.CableName)).ToList();
|
||
|
||
for (int i = 0; i < list.Count; i++)
|
||
{
|
||
var item = list[i];
|
||
int rowNum = i + 2; // Excel行号(第1行是表头)
|
||
try
|
||
{
|
||
// 确定CableId:优先用光缆编号,其次按所属光缆名称匹配
|
||
int? cableId = item.CableId;
|
||
if ((cableId == null || cableId == 0) && !string.IsNullOrWhiteSpace(item.CableName))
|
||
{
|
||
var cable = cables.Find(c => c.CableName == item.CableName.Trim());
|
||
if (cable != null)
|
||
{
|
||
cableId = cable.Id;
|
||
}
|
||
}
|
||
if (cableId == null || cableId == 0)
|
||
{
|
||
errorCount++;
|
||
errorMsgs.Add($"第{rowNum}行:未找到匹配的光缆");
|
||
continue;
|
||
}
|
||
|
||
// 解析故障时间:导出时可能是多行时间拼接(\n分隔),需解析所有有效时间
|
||
var allTimes = new List<DateTime>();
|
||
if (!string.IsNullOrWhiteSpace(item.FaultTime))
|
||
{
|
||
var rawFaultTime = item.FaultTime;
|
||
var escapedRaw = rawFaultTime.Replace("\r", "\\r").Replace("\n", "\\n");
|
||
logger.Info($"[导入调试] 行{rowNum} FaultTime原始值=[{escapedRaw}] 长度={rawFaultTime.Length}");
|
||
|
||
// 打印每个字符的Unicode编码,排查隐藏字符
|
||
var charCodes = string.Join(",", rawFaultTime.Select(c => $"U+{(int)c:X4}"));
|
||
logger.Info($"[导入调试] 行{rowNum} FaultTime字符编码=[{charCodes}]");
|
||
|
||
var timeParts = item.FaultTime.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||
logger.Info($"[导入调试] 行{rowNum} Split后得到 {timeParts.Length} 段");
|
||
for (int k = 0; k < timeParts.Length; k++)
|
||
{
|
||
logger.Info($"[导入调试] 行{rowNum} 时间段[{k}]=[{timeParts[k].Trim()}]");
|
||
if (DateTime.TryParse(timeParts[k].Trim(), out var parsed))
|
||
{
|
||
allTimes.Add(parsed);
|
||
logger.Info($"[导入调试] 行{rowNum} 时间段[{k}] 解析成功={parsed:yyyy-MM-dd HH:mm:ss}");
|
||
}
|
||
else
|
||
{
|
||
logger.Info($"[导入调试] 行{rowNum} 时间段[{k}] 解析失败");
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
logger.Info($"[导入调试] 行{rowNum} FaultTime为空");
|
||
}
|
||
|
||
logger.Info($"[导入调试] 行{rowNum} 共解析到 {allTimes.Count} 个有效时间");
|
||
|
||
// 按时间排序,第一个作为主故障时间
|
||
allTimes.Sort();
|
||
DateTime faultTime = allTimes.Count > 0 ? allTimes[0] : DateTime.Now;
|
||
|
||
// FaultCount:以解析到的时间数量为准,若只有1个或0个则取导入值或默认1
|
||
int faultCount = allTimes.Count > 1 ? allTimes.Count : (item.FaultCount ?? 1);
|
||
|
||
var model = new OdfCableFaults
|
||
{
|
||
CableId = cableId.Value,
|
||
FaultTime = faultTime,
|
||
Personnel = item.Personnel,
|
||
FaultReason = item.FaultReason,
|
||
Mileage = item.Mileage,
|
||
MileageCorrection = item.MileageCorrection,
|
||
Location = item.Location,
|
||
Latitude = item.Latitude ?? 0,
|
||
Longitude = item.Longitude ?? 0,
|
||
Remark = item.Remark,
|
||
FaultCount = faultCount,
|
||
CreatedAt = DateTime.Now,
|
||
UpdatedAt = DateTime.Now
|
||
};
|
||
|
||
var faultEntity = Insertable(model).ExecuteReturnEntity();
|
||
|
||
// 将第2个及之后的时间插入频次时间记录表
|
||
if (allTimes.Count > 1)
|
||
{
|
||
for (int j = 1; j < allTimes.Count; j++)
|
||
{
|
||
var timeRecord = new OdfCableFaultTimes
|
||
{
|
||
FaultId = faultEntity.Id,
|
||
FaultTime = allTimes[j],
|
||
CreatedAt = DateTime.Now
|
||
};
|
||
Context.Insertable(timeRecord).ExecuteCommand();
|
||
}
|
||
}
|
||
successCount++;
|
||
}
|
||
catch (Exception)
|
||
{
|
||
errorCount++;
|
||
errorMsgs.Add($"第{rowNum}行:导入失败");
|
||
}
|
||
}
|
||
|
||
string errorMsg = errorMsgs.Count > 0 ? string.Join(";", errorMsgs.Take(10)) : "";
|
||
return (successCount, errorCount, errorMsg);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新表显里程矫正
|
||
/// </summary>
|
||
public int UpdateMileageCorrection(int id, string mileageCorrection)
|
||
{
|
||
var fault = GetFirst(f => f.Id == id);
|
||
if (fault == null)
|
||
{
|
||
throw new CustomException("故障记录不存在");
|
||
}
|
||
|
||
fault.MileageCorrection = mileageCorrection;
|
||
fault.UpdatedAt = DateTime.Now;
|
||
return Update(fault, true);
|
||
}
|
||
}
|
||
}
|