S8ManualReportService.cs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. using Admin.NET.Plugin.AiDOP.Dto.S8;
  2. using Admin.NET.Plugin.AiDOP.Entity.S0.Manufacturing;
  3. using Admin.NET.Plugin.AiDOP.Entity.S0.Warehouse;
  4. using Admin.NET.Plugin.AiDOP.Entity.S8;
  5. using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
  6. namespace Admin.NET.Plugin.AiDOP.Service.S8;
  7. public class S8ManualReportService : ITransient
  8. {
  9. private readonly SqlSugarRepository<AdoS8Exception> _rep;
  10. private readonly SqlSugarRepository<AdoS8ExceptionTimeline> _timelineRep;
  11. private readonly SqlSugarRepository<AdoS8Evidence> _evidenceRep;
  12. private readonly SqlSugarRepository<AdoS8SceneConfig> _sceneRep;
  13. private readonly SqlSugarRepository<AdoS0DepartmentMaster> _deptRep;
  14. private readonly SqlSugarRepository<AdoS0LineMaster> _lineRep;
  15. public S8ManualReportService(
  16. SqlSugarRepository<AdoS8Exception> rep,
  17. SqlSugarRepository<AdoS8ExceptionTimeline> timelineRep,
  18. SqlSugarRepository<AdoS8Evidence> evidenceRep,
  19. SqlSugarRepository<AdoS8SceneConfig> sceneRep,
  20. SqlSugarRepository<AdoS0DepartmentMaster> deptRep,
  21. SqlSugarRepository<AdoS0LineMaster> lineRep)
  22. {
  23. _rep = rep;
  24. _timelineRep = timelineRep;
  25. _evidenceRep = evidenceRep;
  26. _sceneRep = sceneRep;
  27. _deptRep = deptRep;
  28. _lineRep = lineRep;
  29. }
  30. public async Task<object> GetFormOptionsAsync(long tenantId, long factoryId)
  31. {
  32. var scenes = await _sceneRep.AsQueryable()
  33. .Where(x => x.TenantId == tenantId && x.FactoryId == factoryId && x.Enabled)
  34. .OrderBy(x => x.SortNo)
  35. .Select(x => new { value = x.SceneCode, label = x.SceneName })
  36. .ToListAsync();
  37. var departments = await _deptRep.AsQueryable()
  38. .Where(x => x.FactoryRefId == factoryId)
  39. .OrderBy(x => x.Department)
  40. .Take(500)
  41. .Select(x => new { value = x.Id, label = x.Descr ?? x.Department })
  42. .ToListAsync();
  43. var lines = await _lineRep.AsQueryable()
  44. .Where(x => x.FactoryRefId == factoryId)
  45. .OrderBy(x => x.Line)
  46. .Take(500)
  47. .Select(x => new { value = x.Id, label = x.Describe ?? x.Line })
  48. .ToListAsync();
  49. return new
  50. {
  51. scenes,
  52. severities = new[]
  53. {
  54. new { value = "CRITICAL", label = "紧急" },
  55. new { value = "HIGH", label = "高" },
  56. new { value = "MEDIUM", label = "中" },
  57. new { value = "LOW", label = "低" }
  58. },
  59. departments,
  60. lines,
  61. materials = Array.Empty<object>()
  62. };
  63. }
  64. public async Task<AdoS8ManualReportResultDto> CreateAsync(AdoS8ManualReportCreateDto dto)
  65. {
  66. if (string.IsNullOrWhiteSpace(dto.Title)) throw new S8BizException("标题必填");
  67. if (string.IsNullOrWhiteSpace(dto.SceneCode)) throw new S8BizException("场景必填");
  68. var code = $"EX-{DateTime.Now:yyyyMMdd}-{Guid.NewGuid().ToString("N")[..8].ToUpperInvariant()}";
  69. var entity = new AdoS8Exception
  70. {
  71. TenantId = dto.TenantId,
  72. FactoryId = dto.FactoryId,
  73. ExceptionCode = code,
  74. Title = dto.Title.Trim(),
  75. Description = dto.Description,
  76. SceneCode = dto.SceneCode.Trim(),
  77. SourceType = "MANUAL",
  78. Status = "NEW",
  79. Severity = string.IsNullOrWhiteSpace(dto.Severity) ? "MEDIUM" : dto.Severity,
  80. PriorityScore = 0,
  81. PriorityLevel = "P3",
  82. OccurrenceDeptId = dto.OccurrenceDeptId,
  83. ResponsibleDeptId = dto.ResponsibleDeptId,
  84. ReporterId = dto.ReporterId,
  85. CreatedAt = DateTime.Now,
  86. IsDeleted = false
  87. };
  88. await _rep.AsTenant().UseTranAsync(async () =>
  89. {
  90. entity = await _rep.AsInsertable(entity).ExecuteReturnEntityAsync();
  91. await _timelineRep.InsertAsync(new AdoS8ExceptionTimeline
  92. {
  93. ExceptionId = entity.Id,
  94. ActionCode = "CREATE",
  95. ActionLabel = "创建",
  96. FromStatus = null,
  97. ToStatus = "NEW",
  98. OperatorId = dto.ReporterId,
  99. ActionRemark = "主动提报",
  100. CreatedAt = DateTime.Now
  101. });
  102. }, ex => throw ex);
  103. return new AdoS8ManualReportResultDto
  104. {
  105. ExceptionId = entity.Id,
  106. ExceptionCode = entity.ExceptionCode,
  107. TaskId = entity.Id
  108. };
  109. }
  110. /// <summary>
  111. /// G01-06:自动建单分支(非第二套创建主链)。
  112. /// 这是本服务内的自动监控建单路径,与 <see cref="CreateAsync"/> 并列,
  113. /// 复用同一仓储(_rep / _timelineRep)、同一事务边界、同一 ExceptionCode 生成规则、
  114. /// 同一时间线主链(ActionCode="CREATE"、ToStatus="NEW");仅差异点:
  115. /// - SourceType 标识为自动监控来源
  116. /// - 填入 SourceRuleId / SourceDataSourceId / SourcePayload / RelatedObjectCode 追溯
  117. /// - ExceptionTypeCode 固定 EQUIP_FAULT(G-01 首版唯一映射)
  118. /// - SceneCode 固定 S2S6_PRODUCTION(G-01 首版唯一场景)
  119. /// 不做补偿、重试、对账;失败由调用方接住。
  120. /// </summary>
  121. public async Task<AdoS8Exception> CreateFromWatchAsync(S8WatchHitResult hit)
  122. {
  123. if (hit.SourceRuleId <= 0 || string.IsNullOrWhiteSpace(hit.RelatedObjectCode))
  124. throw new S8BizException("自动建单缺失追溯键");
  125. var code = $"EX-{DateTime.Now:yyyyMMdd}-{Guid.NewGuid().ToString("N")[..8].ToUpperInvariant()}";
  126. var title = $"[自动] 设备 {hit.RelatedObjectCode} {hit.TriggerCondition} {hit.ThresholdValue}(当前 {hit.CurrentValue})";
  127. var entity = new AdoS8Exception
  128. {
  129. // 租户/工厂:与 S8WatchSchedulerService.RunOnceAsync 当前固定上下文一致。
  130. TenantId = 1,
  131. FactoryId = 1,
  132. ExceptionCode = code,
  133. Title = title,
  134. Description = null,
  135. SceneCode = S8SceneCode.S2S6Production,
  136. // 首版自动监控建单来源标识(字符串值,先不抽常量类)。
  137. SourceType = "AUTO_WATCH",
  138. Status = "NEW",
  139. Severity = string.IsNullOrWhiteSpace(hit.Severity) ? "MEDIUM" : hit.Severity,
  140. PriorityScore = 0,
  141. PriorityLevel = "P3",
  142. // 首版兜底口径:Hit 未提供部门时置 0 仅为保证“能建成标准异常单并进入主链”,
  143. // 不是最终业务部门语义;后续需由上游查询结果提供,或在专项任务中补口径。
  144. OccurrenceDeptId = hit.OccurrenceDeptId ?? 0,
  145. ResponsibleDeptId = hit.ResponsibleDeptId ?? 0,
  146. ReporterId = null,
  147. CreatedAt = DateTime.Now,
  148. IsDeleted = false,
  149. // G-01 首版唯一异常类型映射(seed 已确认 EQUIP_FAULT 属 S2S6_PRODUCTION 场景)。
  150. ExceptionTypeCode = "EQUIP_FAULT",
  151. // ModuleCode:S2S6_PRODUCTION 场景对应 S2+S6 两个模块(见 S8ModuleCode.SceneOf),
  152. // 无稳定“scene → 单一 module”映射;首版置空,不靠经验写死。
  153. ModuleCode = null,
  154. ProcessNodeCode = null,
  155. // 追溯三件套(自动建单必填口径)。
  156. SourceRuleId = hit.SourceRuleId,
  157. SourceDataSourceId = hit.DataSourceId,
  158. SourcePayload = hit.SourcePayload,
  159. RelatedObjectCode = hit.RelatedObjectCode
  160. };
  161. await _rep.AsTenant().UseTranAsync(async () =>
  162. {
  163. entity = await _rep.AsInsertable(entity).ExecuteReturnEntityAsync();
  164. await _timelineRep.InsertAsync(new AdoS8ExceptionTimeline
  165. {
  166. ExceptionId = entity.Id,
  167. ActionCode = "CREATE",
  168. ActionLabel = "创建",
  169. FromStatus = null,
  170. ToStatus = "NEW",
  171. OperatorId = null,
  172. ActionRemark = "自动建单",
  173. CreatedAt = DateTime.Now
  174. });
  175. }, ex => throw ex);
  176. return entity;
  177. }
  178. public async Task<AdoS8Exception?> GetAsync(long id) =>
  179. await _rep.GetByIdAsync(id);
  180. public async Task<AdoS8Evidence> AddAttachmentAsync(long id, AdoS8AttachmentCreateDto dto)
  181. {
  182. var entity = await _rep.GetFirstAsync(x => x.Id == id && !x.IsDeleted)
  183. ?? throw new S8BizException("异常不存在");
  184. if (string.IsNullOrWhiteSpace(dto.FileName) || string.IsNullOrWhiteSpace(dto.FileUrl))
  185. throw new S8BizException("附件名称和地址必填");
  186. var evidence = new AdoS8Evidence
  187. {
  188. ExceptionId = id,
  189. EvidenceType = string.IsNullOrWhiteSpace(dto.EvidenceType) ? "file" : dto.EvidenceType,
  190. FileName = dto.FileName.Trim(),
  191. FileUrl = dto.FileUrl.Trim(),
  192. SourceSystem = dto.SourceSystem,
  193. UploadedBy = dto.UploadedBy,
  194. UploadedAt = DateTime.Now,
  195. IsDeleted = false
  196. };
  197. await _evidenceRep.InsertAsync(evidence);
  198. return evidence;
  199. }
  200. }