using Admin.NET.Plugin.AiDOP.Dto.S8; using Admin.NET.Plugin.AiDOP.Entity.S0.Manufacturing; using Admin.NET.Plugin.AiDOP.Entity.S0.Warehouse; using Admin.NET.Plugin.AiDOP.Entity.S8; using Admin.NET.Plugin.AiDOP.Infrastructure.S8; namespace Admin.NET.Plugin.AiDOP.Service.S8; public class S8ManualReportService : ITransient { private readonly SqlSugarRepository _rep; private readonly SqlSugarRepository _timelineRep; private readonly SqlSugarRepository _evidenceRep; private readonly SqlSugarRepository _sceneRep; private readonly SqlSugarRepository _deptRep; private readonly SqlSugarRepository _lineRep; public S8ManualReportService( SqlSugarRepository rep, SqlSugarRepository timelineRep, SqlSugarRepository evidenceRep, SqlSugarRepository sceneRep, SqlSugarRepository deptRep, SqlSugarRepository lineRep) { _rep = rep; _timelineRep = timelineRep; _evidenceRep = evidenceRep; _sceneRep = sceneRep; _deptRep = deptRep; _lineRep = lineRep; } public async Task GetFormOptionsAsync(long tenantId, long factoryId) { var scenes = await _sceneRep.AsQueryable() .Where(x => x.TenantId == tenantId && x.FactoryId == factoryId && x.Enabled) .OrderBy(x => x.SortNo) .Select(x => new { value = x.SceneCode, label = x.SceneName }) .ToListAsync(); var departments = await _deptRep.AsQueryable() .Where(x => x.FactoryRefId == factoryId) .OrderBy(x => x.Department) .Take(500) .Select(x => new { value = x.Id, label = x.Descr ?? x.Department }) .ToListAsync(); var lines = await _lineRep.AsQueryable() .Where(x => x.FactoryRefId == factoryId) .OrderBy(x => x.Line) .Take(500) .Select(x => new { value = x.Id, label = x.Describe ?? x.Line }) .ToListAsync(); return new { scenes, severities = new[] { new { value = "CRITICAL", label = "紧急" }, new { value = "HIGH", label = "高" }, new { value = "MEDIUM", label = "中" }, new { value = "LOW", label = "低" } }, departments, lines, materials = Array.Empty() }; } public async Task CreateAsync(AdoS8ManualReportCreateDto dto) { if (string.IsNullOrWhiteSpace(dto.Title)) throw new S8BizException("标题必填"); if (string.IsNullOrWhiteSpace(dto.SceneCode)) throw new S8BizException("场景必填"); var code = $"EX-{DateTime.Now:yyyyMMdd}-{Guid.NewGuid().ToString("N")[..8].ToUpperInvariant()}"; var entity = new AdoS8Exception { TenantId = dto.TenantId, FactoryId = dto.FactoryId, ExceptionCode = code, Title = dto.Title.Trim(), Description = dto.Description, SceneCode = dto.SceneCode.Trim(), SourceType = "MANUAL", Status = "NEW", Severity = string.IsNullOrWhiteSpace(dto.Severity) ? "MEDIUM" : dto.Severity, PriorityScore = 0, PriorityLevel = "P3", OccurrenceDeptId = dto.OccurrenceDeptId, ResponsibleDeptId = dto.ResponsibleDeptId, ReporterId = dto.ReporterId, CreatedAt = DateTime.Now, IsDeleted = false }; await _rep.AsTenant().UseTranAsync(async () => { entity = await _rep.AsInsertable(entity).ExecuteReturnEntityAsync(); await _timelineRep.InsertAsync(new AdoS8ExceptionTimeline { ExceptionId = entity.Id, ActionCode = "CREATE", ActionLabel = "创建", FromStatus = null, ToStatus = "NEW", OperatorId = dto.ReporterId, ActionRemark = "主动提报", CreatedAt = DateTime.Now }); }, ex => throw ex); return new AdoS8ManualReportResultDto { ExceptionId = entity.Id, ExceptionCode = entity.ExceptionCode, TaskId = entity.Id }; } /// /// G01-06:自动建单分支(非第二套创建主链)。 /// 这是本服务内的自动监控建单路径,与 并列, /// 复用同一仓储(_rep / _timelineRep)、同一事务边界、同一 ExceptionCode 生成规则、 /// 同一时间线主链(ActionCode="CREATE"、ToStatus="NEW");仅差异点: /// - SourceType 标识为自动监控来源 /// - 填入 SourceRuleId / SourceDataSourceId / SourcePayload / RelatedObjectCode 追溯 /// - ExceptionTypeCode 固定 EQUIP_FAULT(G-01 首版唯一映射) /// - SceneCode 固定 S2S6_PRODUCTION(G-01 首版唯一场景) /// 不做补偿、重试、对账;失败由调用方接住。 /// public async Task CreateFromWatchAsync(S8WatchHitResult hit) { if (hit.SourceRuleId <= 0 || string.IsNullOrWhiteSpace(hit.RelatedObjectCode)) throw new S8BizException("自动建单缺失追溯键"); var code = $"EX-{DateTime.Now:yyyyMMdd}-{Guid.NewGuid().ToString("N")[..8].ToUpperInvariant()}"; var title = $"[自动] 设备 {hit.RelatedObjectCode} {hit.TriggerCondition} {hit.ThresholdValue}(当前 {hit.CurrentValue})"; var entity = new AdoS8Exception { // 租户/工厂:与 S8WatchSchedulerService.RunOnceAsync 当前固定上下文一致。 TenantId = 1, FactoryId = 1, ExceptionCode = code, Title = title, Description = null, SceneCode = S8SceneCode.S2S6Production, // 首版自动监控建单来源标识(字符串值,先不抽常量类)。 SourceType = "AUTO_WATCH", Status = "NEW", Severity = string.IsNullOrWhiteSpace(hit.Severity) ? "MEDIUM" : hit.Severity, PriorityScore = 0, PriorityLevel = "P3", // 首版兜底口径:Hit 未提供部门时置 0 仅为保证“能建成标准异常单并进入主链”, // 不是最终业务部门语义;后续需由上游查询结果提供,或在专项任务中补口径。 OccurrenceDeptId = hit.OccurrenceDeptId ?? 0, ResponsibleDeptId = hit.ResponsibleDeptId ?? 0, ReporterId = null, CreatedAt = DateTime.Now, IsDeleted = false, // G-01 首版唯一异常类型映射(seed 已确认 EQUIP_FAULT 属 S2S6_PRODUCTION 场景)。 ExceptionTypeCode = "EQUIP_FAULT", // ModuleCode:S2S6_PRODUCTION 场景对应 S2+S6 两个模块(见 S8ModuleCode.SceneOf), // 无稳定“scene → 单一 module”映射;首版置空,不靠经验写死。 ModuleCode = null, ProcessNodeCode = null, // 追溯三件套(自动建单必填口径)。 SourceRuleId = hit.SourceRuleId, SourceDataSourceId = hit.DataSourceId, SourcePayload = hit.SourcePayload, RelatedObjectCode = hit.RelatedObjectCode }; await _rep.AsTenant().UseTranAsync(async () => { entity = await _rep.AsInsertable(entity).ExecuteReturnEntityAsync(); await _timelineRep.InsertAsync(new AdoS8ExceptionTimeline { ExceptionId = entity.Id, ActionCode = "CREATE", ActionLabel = "创建", FromStatus = null, ToStatus = "NEW", OperatorId = null, ActionRemark = "自动建单", CreatedAt = DateTime.Now }); }, ex => throw ex); return entity; } public async Task GetAsync(long id) => await _rep.GetByIdAsync(id); public async Task AddAttachmentAsync(long id, AdoS8AttachmentCreateDto dto) { var entity = await _rep.GetFirstAsync(x => x.Id == id && !x.IsDeleted) ?? throw new S8BizException("异常不存在"); if (string.IsNullOrWhiteSpace(dto.FileName) || string.IsNullOrWhiteSpace(dto.FileUrl)) throw new S8BizException("附件名称和地址必填"); var evidence = new AdoS8Evidence { ExceptionId = id, EvidenceType = string.IsNullOrWhiteSpace(dto.EvidenceType) ? "file" : dto.EvidenceType, FileName = dto.FileName.Trim(), FileUrl = dto.FileUrl.Trim(), SourceSystem = dto.SourceSystem, UploadedBy = dto.UploadedBy, UploadedAt = DateTime.Now, IsDeleted = false }; await _evidenceRep.InsertAsync(evidence); return evidence; } }