| | 1 | | using LGDXRobotCloud.API.Exceptions; |
| | 2 | | using LGDXRobotCloud.API.Services.Common; |
| | 3 | | using LGDXRobotCloud.API.Services.Navigation; |
| | 4 | | using LGDXRobotCloud.Data.DbContexts; |
| | 5 | | using LGDXRobotCloud.Data.Entities; |
| | 6 | | using LGDXRobotCloud.Data.Models.Business.Automation; |
| | 7 | | using LGDXRobotCloud.Data.Models.Business.Navigation; |
| | 8 | | using LGDXRobotCloud.Utilities.Enums; |
| | 9 | | using LGDXRobotCloud.Utilities.Helpers; |
| | 10 | | using MassTransit; |
| | 11 | | using Microsoft.EntityFrameworkCore; |
| | 12 | | using Microsoft.Extensions.Caching.Memory; |
| | 13 | |
|
| | 14 | | namespace LGDXRobotCloud.API.Services.Automation; |
| | 15 | |
|
| | 16 | | public interface IAutoTaskService |
| | 17 | | { |
| | 18 | | Task<(IEnumerable<AutoTaskListBusinessModel>, PaginationHelper)> GetAutoTasksAsync(int? realmId, string? name, AutoTas |
| | 19 | | Task<AutoTaskBusinessModel> GetAutoTaskAsync(int autoTaskId); |
| | 20 | | Task<AutoTaskBusinessModel> CreateAutoTaskAsync(AutoTaskCreateBusinessModel autoTaskCreateBusinessModel); |
| | 21 | | Task<bool> UpdateAutoTaskAsync(int autoTaskId, AutoTaskUpdateBusinessModel autoTaskUpdateBusinessModel); |
| | 22 | | Task<bool> DeleteAutoTaskAsync(int autoTaskId); |
| | 23 | |
|
| | 24 | | Task AbortAutoTaskAsync(int autoTaskId); |
| | 25 | | Task AutoTaskNextApiAsync(Guid robotId, int taskId, string token); |
| | 26 | |
|
| | 27 | | Task<AutoTaskStatisticsBusinessModel> GetAutoTaskStatisticsAsync(int realmId); |
| | 28 | | Task<AutoTaskListBusinessModel?> GetRobotCurrentTaskAsync(Guid robotId); |
| | 29 | | } |
| | 30 | |
|
| 35 | 31 | | public class AutoTaskService( |
| 35 | 32 | | LgdxContext context, |
| 35 | 33 | | IAutoTaskSchedulerService autoTaskSchedulerService, |
| 35 | 34 | | IBus bus, |
| 35 | 35 | | IEventService eventService, |
| 35 | 36 | | IMemoryCache memoryCache, |
| 35 | 37 | | IOnlineRobotsService onlineRobotsService |
| 35 | 38 | | ) : IAutoTaskService |
| | 39 | | { |
| 35 | 40 | | private readonly IOnlineRobotsService _onlineRobotsService = onlineRobotsService ?? throw new ArgumentNullException(na |
| 35 | 41 | | private readonly IMemoryCache _memoryCache = memoryCache ?? throw new ArgumentNullException(nameof(memoryCache)); |
| 35 | 42 | | private readonly LgdxContext _context = context ?? throw new ArgumentNullException(nameof(context)); |
| 35 | 43 | | private readonly IAutoTaskSchedulerService _autoTaskSchedulerService = autoTaskSchedulerService ?? throw new ArgumentN |
| 35 | 44 | | private readonly IBus _bus = bus ?? throw new ArgumentNullException(nameof(bus)); |
| | 45 | |
|
| | 46 | | public async Task<(IEnumerable<AutoTaskListBusinessModel>, PaginationHelper)> GetAutoTasksAsync(int? realmId, string? |
| 9 | 47 | | { |
| 9 | 48 | | var query = _context.AutoTasks as IQueryable<AutoTask>; |
| 9 | 49 | | if (!string.IsNullOrWhiteSpace(name)) |
| 3 | 50 | | { |
| 3 | 51 | | name = name.Trim(); |
| 3 | 52 | | query = query.Where(t => t.Name != null && t.Name.ToLower().Contains(name.ToLower())); |
| 3 | 53 | | } |
| 9 | 54 | | if (realmId != null) |
| 9 | 55 | | { |
| 9 | 56 | | query = query.Where(t => t.RealmId == realmId); |
| 9 | 57 | | } |
| 9 | 58 | | switch (autoTaskCatrgory) |
| | 59 | | { |
| | 60 | | case AutoTaskCatrgory.Template: |
| 1 | 61 | | query = query.Where(t => t.CurrentProgressId == (int)ProgressState.Template); |
| 1 | 62 | | break; |
| | 63 | | case AutoTaskCatrgory.Waiting: |
| 1 | 64 | | query = query.Where(t => t.CurrentProgressId == (int)ProgressState.Waiting); |
| 1 | 65 | | break; |
| | 66 | | case AutoTaskCatrgory.Completed: |
| 1 | 67 | | query = query.Where(t => t.CurrentProgressId == (int)ProgressState.Completed); |
| 1 | 68 | | break; |
| | 69 | | case AutoTaskCatrgory.Aborted: |
| 1 | 70 | | query = query.Where(t => t.CurrentProgressId == (int)ProgressState.Aborted); |
| 1 | 71 | | break; |
| | 72 | | case AutoTaskCatrgory.Running: |
| 1 | 73 | | query = query.Where(t => t.CurrentProgressId > (int)ProgressState.Aborted); |
| 1 | 74 | | break; |
| | 75 | | } |
| 9 | 76 | | var itemCount = await query.CountAsync(); |
| 9 | 77 | | var PaginationHelper = new PaginationHelper(itemCount, pageNumber, pageSize); |
| 9 | 78 | | var autoTasks = await query.AsNoTracking() |
| 9 | 79 | | .OrderByDescending(t => t.Priority) |
| 9 | 80 | | .ThenBy(t => t.Id) |
| 9 | 81 | | .Skip(pageSize * (pageNumber - 1)) |
| 9 | 82 | | .Take(pageSize) |
| 9 | 83 | | .Select(t => new AutoTaskListBusinessModel |
| 9 | 84 | | { |
| 9 | 85 | | Id = t.Id, |
| 9 | 86 | | Name = t.Name, |
| 9 | 87 | | Priority = t.Priority, |
| 9 | 88 | | FlowId = t.Flow!.Id, |
| 9 | 89 | | FlowName = t.Flow.Name, |
| 9 | 90 | | RealmId = t.Realm.Id, |
| 9 | 91 | | RealmName = t.Realm.Name, |
| 9 | 92 | | AssignedRobotId = t.AssignedRobotId, |
| 9 | 93 | | AssignedRobotName = t.AssignedRobot!.Name, |
| 9 | 94 | | CurrentProgressId = t.CurrentProgressId, |
| 9 | 95 | | CurrentProgressName = t.CurrentProgress.Name, |
| 9 | 96 | | }) |
| 9 | 97 | | .AsSplitQuery() |
| 9 | 98 | | .ToListAsync(); |
| 9 | 99 | | return (autoTasks, PaginationHelper); |
| 9 | 100 | | } |
| | 101 | |
|
| | 102 | | public async Task<AutoTaskBusinessModel> GetAutoTaskAsync(int autoTaskId) |
| 2 | 103 | | { |
| 2 | 104 | | return await _context.AutoTasks.AsNoTracking() |
| 2 | 105 | | .Where(t => t.Id == autoTaskId) |
| 2 | 106 | | .Include(t => t.AutoTaskDetails |
| 2 | 107 | | .OrderBy(td => td.Order)) |
| 2 | 108 | | .ThenInclude(td => td.Waypoint) |
| 2 | 109 | | .Include(t => t.AutoTaskJourneys) |
| 2 | 110 | | .ThenInclude(tj => tj.CurrentProgress) |
| 2 | 111 | | .Include(t => t.AssignedRobot) |
| 2 | 112 | | .Include(t => t.CurrentProgress) |
| 2 | 113 | | .Include(t => t.Flow) |
| 2 | 114 | | .Include(t => t.Realm) |
| 2 | 115 | | .AsSplitQuery() |
| 2 | 116 | | .Select(t => new AutoTaskBusinessModel |
| 2 | 117 | | { |
| 2 | 118 | | Id = t.Id, |
| 2 | 119 | | Name = t.Name, |
| 2 | 120 | | Priority = t.Priority, |
| 2 | 121 | | FlowId = t.Flow!.Id, |
| 2 | 122 | | FlowName = t.Flow.Name, |
| 2 | 123 | | RealmId = t.Realm.Id, |
| 2 | 124 | | RealmName = t.Realm.Name, |
| 2 | 125 | | AssignedRobotId = t.AssignedRobotId, |
| 2 | 126 | | AssignedRobotName = t.AssignedRobot!.Name, |
| 2 | 127 | | CurrentProgressId = t.CurrentProgressId, |
| 2 | 128 | | CurrentProgressName = t.CurrentProgress.Name, |
| 2 | 129 | | AutoTaskJourneys = t.AutoTaskJourneys.Select(tj => new AutoTaskJourneyBusinessModel |
| 2 | 130 | | { |
| 2 | 131 | | Id = tj.Id, |
| 2 | 132 | | CurrentProcessId = tj.CurrentProgressId, |
| 2 | 133 | | CurrentProcessName = tj.CurrentProgress == null ? null : tj.CurrentProgress.Name, |
| 2 | 134 | | CreatedAt = tj.CreatedAt, |
| 2 | 135 | | }) |
| 2 | 136 | | .OrderBy(tj => tj.Id) |
| 2 | 137 | | .ToList(), |
| 2 | 138 | | AutoTaskDetails = t.AutoTaskDetails.Select(td => new AutoTaskDetailBusinessModel |
| 2 | 139 | | { |
| 2 | 140 | | Id = td.Id, |
| 2 | 141 | | Order = td.Order, |
| 2 | 142 | | CustomX = td.CustomX, |
| 2 | 143 | | CustomY = td.CustomY, |
| 2 | 144 | | CustomRotation = td.CustomRotation, |
| 2 | 145 | | Waypoint = td.Waypoint == null ? null : new WaypointBusinessModel |
| 2 | 146 | | { |
| 2 | 147 | | Id = td.Waypoint.Id, |
| 2 | 148 | | Name = td.Waypoint.Name, |
| 2 | 149 | | RealmId = t.Realm.Id, |
| 2 | 150 | | RealmName = t.Realm.Name, |
| 2 | 151 | | X = td.Waypoint.X, |
| 2 | 152 | | Y = td.Waypoint.Y, |
| 2 | 153 | | Rotation = td.Waypoint.Rotation, |
| 2 | 154 | | IsParking = td.Waypoint.IsParking, |
| 2 | 155 | | HasCharger = td.Waypoint.HasCharger, |
| 2 | 156 | | IsReserved = td.Waypoint.IsReserved, |
| 2 | 157 | | }, |
| 2 | 158 | | }) |
| 2 | 159 | | .OrderBy(td => td.Order) |
| 2 | 160 | | .ToList() |
| 2 | 161 | | }) |
| 2 | 162 | | .FirstOrDefaultAsync() |
| 2 | 163 | | ?? throw new LgdxNotFound404Exception(); |
| 1 | 164 | | } |
| | 165 | |
|
| | 166 | | private async Task ValidateAutoTask(HashSet<int> waypointIds, int flowId, int realmId, Guid? robotId) |
| 7 | 167 | | { |
| | 168 | | // Validate the Waypoint IDs |
| 7 | 169 | | var waypointDict = await _context.Waypoints.AsNoTracking() |
| 7 | 170 | | .Where(w => waypointIds.Contains(w.Id)) |
| 7 | 171 | | .Where(w => w.RealmId == realmId) |
| 11 | 172 | | .ToDictionaryAsync(w => w.Id, w => w); |
| 26 | 173 | | foreach (var waypointId in waypointIds) |
| 3 | 174 | | { |
| 3 | 175 | | if (!waypointDict.ContainsKey(waypointId)) |
| 1 | 176 | | { |
| 1 | 177 | | throw new LgdxValidation400Expection(nameof(Waypoint), $"The Waypoint ID {waypointId} is invalid."); |
| | 178 | | } |
| 2 | 179 | | } |
| | 180 | | // Validate the Flow ID |
| 6 | 181 | | var flow = await _context.Flows.Where(f => f.Id == flowId).AnyAsync(); |
| 6 | 182 | | if (flow == false) |
| 1 | 183 | | { |
| 1 | 184 | | throw new LgdxValidation400Expection(nameof(Flow), $"The Flow ID {flowId} is invalid."); |
| | 185 | | } |
| | 186 | | // Validate the Realm ID |
| 5 | 187 | | var realm = await _context.Realms.Where(r => r.Id == realmId).AnyAsync(); |
| 5 | 188 | | if (realm == false) |
| 1 | 189 | | { |
| 1 | 190 | | throw new LgdxValidation400Expection(nameof(Realm), $"The Realm ID {realmId} is invalid."); |
| | 191 | | } |
| | 192 | | // Validate the Assigned Robot ID |
| 4 | 193 | | if (robotId != null) |
| 4 | 194 | | { |
| 4 | 195 | | var robot = await _context |
| 4 | 196 | | .Robots |
| 4 | 197 | | .Where(r => r.Id == robotId) |
| 4 | 198 | | .Where(r => r.RealmId == realmId) |
| 4 | 199 | | .AnyAsync(); |
| 4 | 200 | | if (robot == false) |
| 1 | 201 | | { |
| 1 | 202 | | throw new LgdxValidation400Expection(nameof(Robot), $"Robot ID: {robotId} is invalid."); |
| | 203 | | } |
| 3 | 204 | | } |
| 3 | 205 | | } |
| | 206 | |
|
| | 207 | | public async Task<AutoTaskBusinessModel> CreateAutoTaskAsync(AutoTaskCreateBusinessModel autoTaskCreateBusinessModel) |
| 6 | 208 | | { |
| 6 | 209 | | HashSet<int> waypointIds = autoTaskCreateBusinessModel.AutoTaskDetails |
| 3 | 210 | | .Where(d => d.WaypointId != null) |
| 3 | 211 | | .Select(d => d.WaypointId!.Value) |
| 6 | 212 | | .ToHashSet(); |
| | 213 | |
|
| 6 | 214 | | await ValidateAutoTask(waypointIds, |
| 6 | 215 | | autoTaskCreateBusinessModel.FlowId, |
| 6 | 216 | | autoTaskCreateBusinessModel.RealmId, |
| 6 | 217 | | autoTaskCreateBusinessModel.AssignedRobotId); |
| | 218 | |
|
| 2 | 219 | | var autoTask = new AutoTask |
| 2 | 220 | | { |
| 2 | 221 | | Name = autoTaskCreateBusinessModel.Name, |
| 2 | 222 | | Priority = autoTaskCreateBusinessModel.Priority, |
| 2 | 223 | | FlowId = autoTaskCreateBusinessModel.FlowId, |
| 2 | 224 | | RealmId = autoTaskCreateBusinessModel.RealmId, |
| 2 | 225 | | AssignedRobotId = autoTaskCreateBusinessModel.AssignedRobotId, |
| 2 | 226 | | CurrentProgressId = autoTaskCreateBusinessModel.IsTemplate |
| 2 | 227 | | ? (int)ProgressState.Template |
| 2 | 228 | | : (int)ProgressState.Waiting, |
| 2 | 229 | | AutoTaskDetails = autoTaskCreateBusinessModel.AutoTaskDetails.Select(td => new AutoTaskDetail |
| 2 | 230 | | { |
| 2 | 231 | | Order = td.Order, |
| 2 | 232 | | CustomX = td.CustomX, |
| 2 | 233 | | CustomY = td.CustomY, |
| 2 | 234 | | CustomRotation = td.CustomRotation, |
| 2 | 235 | | WaypointId = td.WaypointId, |
| 2 | 236 | | }) |
| 0 | 237 | | .OrderBy(td => td.Order) |
| 2 | 238 | | .ToList(), |
| 2 | 239 | | }; |
| 2 | 240 | | await _context.AutoTasks.AddAsync(autoTask); |
| 2 | 241 | | await _context.SaveChangesAsync(); |
| 2 | 242 | | _autoTaskSchedulerService.ResetIgnoreRobot(autoTask.RealmId); |
| 2 | 243 | | eventService.AutoTaskHasCreated(); |
| | 244 | |
|
| 2 | 245 | | var autoTaskBusinessModel = await _context.AutoTasks.AsNoTracking() |
| 2 | 246 | | .Where(t => t.Id == autoTask.Id) |
| 2 | 247 | | .Select(t => new AutoTaskBusinessModel |
| 2 | 248 | | { |
| 2 | 249 | | Id = t.Id, |
| 2 | 250 | | Name = t.Name, |
| 2 | 251 | | Priority = t.Priority, |
| 2 | 252 | | FlowId = t.Flow!.Id, |
| 2 | 253 | | FlowName = t.Flow.Name, |
| 2 | 254 | | RealmId = t.Realm.Id, |
| 2 | 255 | | RealmName = t.Realm.Name, |
| 2 | 256 | | AssignedRobotId = t.AssignedRobotId, |
| 2 | 257 | | AssignedRobotName = t.AssignedRobot!.Name, |
| 2 | 258 | | CurrentProgressId = t.CurrentProgressId, |
| 2 | 259 | | CurrentProgressName = t.CurrentProgress.Name, |
| 2 | 260 | | AutoTaskDetails = t.AutoTaskDetails.Select(td => new AutoTaskDetailBusinessModel |
| 2 | 261 | | { |
| 2 | 262 | | Id = td.Id, |
| 2 | 263 | | Order = td.Order, |
| 2 | 264 | | CustomX = td.CustomX, |
| 2 | 265 | | CustomY = td.CustomY, |
| 2 | 266 | | CustomRotation = td.CustomRotation, |
| 2 | 267 | | Waypoint = td.Waypoint == null ? null : new WaypointBusinessModel |
| 2 | 268 | | { |
| 2 | 269 | | Id = td.Waypoint.Id, |
| 2 | 270 | | Name = td.Waypoint.Name, |
| 2 | 271 | | RealmId = t.Realm.Id, |
| 2 | 272 | | RealmName = t.Realm.Name, |
| 2 | 273 | | X = td.Waypoint.X, |
| 2 | 274 | | Y = td.Waypoint.Y, |
| 2 | 275 | | Rotation = td.Waypoint.Rotation, |
| 2 | 276 | | IsParking = td.Waypoint.IsParking, |
| 2 | 277 | | HasCharger = td.Waypoint.HasCharger, |
| 2 | 278 | | IsReserved = td.Waypoint.IsReserved, |
| 2 | 279 | | }, |
| 2 | 280 | | }) |
| 2 | 281 | | .OrderBy(td => td.Order) |
| 2 | 282 | | .ToList() |
| 2 | 283 | | }) |
| 2 | 284 | | .FirstOrDefaultAsync() |
| 2 | 285 | | ?? throw new LgdxNotFound404Exception(); |
| | 286 | |
|
| 2 | 287 | | if (autoTask.CurrentProgressId == (int)ProgressState.Waiting) |
| 1 | 288 | | { |
| 1 | 289 | | var autoTaskJourney = new AutoTaskJourney |
| 1 | 290 | | { |
| 1 | 291 | | AutoTaskId = autoTaskBusinessModel.Id, |
| 1 | 292 | | CurrentProgressId = autoTask.CurrentProgressId |
| 1 | 293 | | }; |
| 1 | 294 | | await _context.AutoTasksJourney.AddAsync(autoTaskJourney); |
| 1 | 295 | | await _context.SaveChangesAsync(); |
| | 296 | |
|
| 1 | 297 | | await _bus.Publish(autoTaskBusinessModel.ToContract()); |
| 1 | 298 | | } |
| | 299 | |
|
| 2 | 300 | | return autoTaskBusinessModel; |
| 2 | 301 | | } |
| | 302 | |
|
| | 303 | | public async Task<bool> UpdateAutoTaskAsync(int autoTaskId, AutoTaskUpdateBusinessModel autoTaskUpdateBusinessModel) |
| 4 | 304 | | { |
| 4 | 305 | | var task = await _context.AutoTasks |
| 4 | 306 | | .Where(t => t.Id == autoTaskId) |
| 4 | 307 | | .Include(t => t.AutoTaskDetails |
| 4 | 308 | | .OrderBy(td => td.Order)) |
| 4 | 309 | | .FirstOrDefaultAsync() |
| 4 | 310 | | ?? throw new LgdxNotFound404Exception(); |
| | 311 | |
|
| 3 | 312 | | if (task.CurrentProgressId != (int)ProgressState.Template) |
| 1 | 313 | | { |
| 1 | 314 | | throw new LgdxValidation400Expection("AutoTaskId", "Only AutoTask Templates are editable."); |
| | 315 | | } |
| | 316 | | // Ensure the input task does not include Detail ID from other task |
| 6 | 317 | | HashSet<int> dbDetailIds = task.AutoTaskDetails.Select(d => d.Id).ToHashSet(); |
| 9 | 318 | | foreach (var bmDetailId in autoTaskUpdateBusinessModel.AutoTaskDetails.Where(d => d.Id != null).Select(d => d.Id)) |
| 1 | 319 | | { |
| 1 | 320 | | if (bmDetailId != null && !dbDetailIds.Contains((int)bmDetailId)) |
| 1 | 321 | | { |
| 1 | 322 | | throw new LgdxValidation400Expection("AutoTaskDetailId", $"The Task Detail ID {(int)bmDetailId} is belongs to ot |
| | 323 | | } |
| 0 | 324 | | } |
| 1 | 325 | | HashSet<int> waypointIds = autoTaskUpdateBusinessModel.AutoTaskDetails |
| 0 | 326 | | .Where(d => d.WaypointId != null) |
| 0 | 327 | | .Select(d => d.WaypointId!.Value) |
| 1 | 328 | | .ToHashSet(); |
| 1 | 329 | | await ValidateAutoTask(waypointIds, |
| 1 | 330 | | autoTaskUpdateBusinessModel.FlowId, |
| 1 | 331 | | task.RealmId, |
| 1 | 332 | | autoTaskUpdateBusinessModel.AssignedRobotId); |
| | 333 | |
|
| 1 | 334 | | task.Name = autoTaskUpdateBusinessModel.Name; |
| 1 | 335 | | task.Priority = autoTaskUpdateBusinessModel.Priority; |
| 1 | 336 | | task.FlowId = autoTaskUpdateBusinessModel.FlowId; |
| 1 | 337 | | task.AssignedRobotId = autoTaskUpdateBusinessModel.AssignedRobotId; |
| 1 | 338 | | task.AutoTaskDetails = autoTaskUpdateBusinessModel.AutoTaskDetails.Select(td => new AutoTaskDetail |
| 1 | 339 | | { |
| 1 | 340 | | Id = (int)td.Id!, |
| 1 | 341 | | Order = td.Order, |
| 1 | 342 | | CustomX = td.CustomX, |
| 1 | 343 | | CustomY = td.CustomY, |
| 1 | 344 | | CustomRotation = td.CustomRotation, |
| 1 | 345 | | WaypointId = td.WaypointId, |
| 1 | 346 | | }).ToList(); |
| 1 | 347 | | await _context.SaveChangesAsync(); |
| 1 | 348 | | return true; |
| 1 | 349 | | } |
| | 350 | |
|
| | 351 | | public async Task<bool> DeleteAutoTaskAsync(int autoTaskId) |
| 6 | 352 | | { |
| 6 | 353 | | var autoTask = await _context.AutoTasks.AsNoTracking() |
| 6 | 354 | | .Where(t => t.Id == autoTaskId) |
| 6 | 355 | | .FirstOrDefaultAsync() |
| 6 | 356 | | ?? throw new LgdxNotFound404Exception(); |
| 5 | 357 | | if (autoTask.CurrentProgressId != (int)ProgressState.Template) |
| 4 | 358 | | { |
| 4 | 359 | | throw new LgdxValidation400Expection("AutoTaskId", "Cannot delete the task not in running status."); |
| | 360 | | } |
| | 361 | |
|
| 1 | 362 | | _context.AutoTasks.Remove(autoTask); |
| 1 | 363 | | await _context.SaveChangesAsync(); |
| | 364 | |
|
| 1 | 365 | | return true; |
| 1 | 366 | | } |
| | 367 | |
|
| | 368 | | public async Task AbortAutoTaskAsync(int autoTaskId) |
| 6 | 369 | | { |
| 6 | 370 | | var autoTask = await _context.AutoTasks.AsNoTracking() |
| 6 | 371 | | .Where(t => t.Id == autoTaskId) |
| 6 | 372 | | .FirstOrDefaultAsync() |
| 6 | 373 | | ?? throw new LgdxNotFound404Exception(); |
| | 374 | |
|
| 5 | 375 | | if (autoTask.CurrentProgressId == (int)ProgressState.Template || |
| 5 | 376 | | autoTask.CurrentProgressId == (int)ProgressState.Completed || |
| 5 | 377 | | autoTask.CurrentProgressId == (int)ProgressState.Aborted) |
| 3 | 378 | | { |
| 3 | 379 | | throw new LgdxValidation400Expection(nameof(autoTaskId), "Cannot abort the task not in running status."); |
| | 380 | | } |
| 2 | 381 | | if (autoTask.CurrentProgressId != (int)ProgressState.Waiting && |
| 2 | 382 | | autoTask.AssignedRobotId != null && |
| 2 | 383 | | await _onlineRobotsService.SetAbortTaskAsync((Guid)autoTask.AssignedRobotId!, true)) |
| 1 | 384 | | { |
| | 385 | | // If the robot is online, abort the task from the robot |
| 1 | 386 | | return; |
| | 387 | | } |
| | 388 | | else |
| 1 | 389 | | { |
| 1 | 390 | | await _autoTaskSchedulerService.AutoTaskAbortApiAsync(autoTask.Id); |
| 1 | 391 | | } |
| 2 | 392 | | } |
| | 393 | |
|
| | 394 | | public async Task AutoTaskNextApiAsync(Guid robotId, int taskId, string token) |
| 2 | 395 | | { |
| 2 | 396 | | var result = await _autoTaskSchedulerService.AutoTaskNextApiAsync(robotId, taskId, token) |
| 2 | 397 | | ?? throw new LgdxValidation400Expection(nameof(token), "The next token is invalid."); |
| 1 | 398 | | _onlineRobotsService.SetAutoTaskNextApi(robotId, result); |
| 1 | 399 | | } |
| | 400 | |
|
| | 401 | | public async Task<AutoTaskStatisticsBusinessModel> GetAutoTaskStatisticsAsync(int realmId) |
| 0 | 402 | | { |
| 0 | 403 | | _memoryCache.TryGetValue("Automation_Statistics", out AutoTaskStatisticsBusinessModel? autoTaskStatistics); |
| 0 | 404 | | if (autoTaskStatistics != null) |
| 0 | 405 | | { |
| 0 | 406 | | return autoTaskStatistics; |
| | 407 | | } |
| | 408 | |
|
| | 409 | | /* |
| | 410 | | * Get queuing tasks and running tasks (total) |
| | 411 | | */ |
| 0 | 412 | | DateTime CurrentDate = DateTime.UtcNow; |
| 0 | 413 | | var waitingTaskCount = await _context.AutoTasks.AsNoTracking() |
| 0 | 414 | | .Where(t => t.CurrentProgressId == (int)ProgressState.Waiting) |
| 0 | 415 | | .CountAsync(); |
| 0 | 416 | | var waitingTaskPerHour = await _context.AutoTasksJourney.AsNoTracking() |
| 0 | 417 | | .Where(t => t.CurrentProgressId == (int)ProgressState.Waiting && t.CreatedAt > DateTime.UtcNow.AddHours(-23)) |
| 0 | 418 | | .GroupBy(t => new |
| 0 | 419 | | { |
| 0 | 420 | | t.CreatedAt.Year, |
| 0 | 421 | | t.CreatedAt.Month, |
| 0 | 422 | | t.CreatedAt.Day, |
| 0 | 423 | | t.CreatedAt.Hour |
| 0 | 424 | | }) |
| 0 | 425 | | .Select(g => new |
| 0 | 426 | | { |
| 0 | 427 | | Time = new DateTime(g.Key.Year, g.Key.Month, g.Key.Day, g.Key.Hour, 0, 0), |
| 0 | 428 | | TasksCompleted = g.Count() |
| 0 | 429 | | }) |
| 0 | 430 | | .OrderBy(g => g.Time) |
| 0 | 431 | | .ToListAsync(); |
| 0 | 432 | | Dictionary<int, int> waitingTaskPerHourDict = []; |
| 0 | 433 | | foreach (var taskCount in waitingTaskPerHour) |
| 0 | 434 | | { |
| 0 | 435 | | waitingTaskPerHourDict.Add((int)(CurrentDate - taskCount.Time).TotalHours, taskCount.TasksCompleted); |
| 0 | 436 | | } |
| | 437 | |
|
| 0 | 438 | | var runningTaskCount = await _context.AutoTasks.AsNoTracking() |
| 0 | 439 | | .Where(t => !LgdxHelper.AutoTaskStaticStates.Contains(t.CurrentProgressId!)) |
| 0 | 440 | | .CountAsync(); |
| 0 | 441 | | var runningTaskPerHour = await _context.AutoTasksJourney.AsNoTracking() |
| 0 | 442 | | .Where(t => !LgdxHelper.AutoTaskStaticStates.Contains((int)t.CurrentProgressId!) && t.CreatedAt > DateTime.UtcNow.Ad |
| 0 | 443 | | .GroupBy(t => new |
| 0 | 444 | | { |
| 0 | 445 | | t.CreatedAt.Year, |
| 0 | 446 | | t.CreatedAt.Month, |
| 0 | 447 | | t.CreatedAt.Day, |
| 0 | 448 | | t.CreatedAt.Hour |
| 0 | 449 | | }) |
| 0 | 450 | | .Select(g => new |
| 0 | 451 | | { |
| 0 | 452 | | Time = new DateTime(g.Key.Year, g.Key.Month, g.Key.Day, g.Key.Hour, 0, 0), |
| 0 | 453 | | TasksCompleted = g.Count() |
| 0 | 454 | | }) |
| 0 | 455 | | .OrderBy(g => g.Time) |
| 0 | 456 | | .ToListAsync(); |
| 0 | 457 | | Dictionary<int, int> runningTaskPerHourDict = []; |
| 0 | 458 | | foreach (var taskCount in runningTaskPerHour) |
| 0 | 459 | | { |
| 0 | 460 | | runningTaskPerHourDict.Add((int)(CurrentDate - taskCount.Time).TotalHours, taskCount.TasksCompleted); |
| 0 | 461 | | } |
| | 462 | |
|
| | 463 | | /* |
| | 464 | | * Get task completed / aborted in the last 24 hours |
| | 465 | | */ |
| 0 | 466 | | var completedTaskPerHour = await _context.AutoTasksJourney.AsNoTracking() |
| 0 | 467 | | .Where(t => t.CurrentProgressId == (int)ProgressState.Completed && t.CreatedAt > DateTime.UtcNow.AddHours(-23)) |
| 0 | 468 | | .GroupBy(t => new |
| 0 | 469 | | { |
| 0 | 470 | | t.CreatedAt.Year, |
| 0 | 471 | | t.CreatedAt.Month, |
| 0 | 472 | | t.CreatedAt.Day, |
| 0 | 473 | | t.CreatedAt.Hour |
| 0 | 474 | | }) |
| 0 | 475 | | .Select(g => new |
| 0 | 476 | | { |
| 0 | 477 | | Time = new DateTime(g.Key.Year, g.Key.Month, g.Key.Day, g.Key.Hour, 0, 0), |
| 0 | 478 | | TasksCompleted = g.Count() |
| 0 | 479 | | }) |
| 0 | 480 | | .OrderBy(g => g.Time) |
| 0 | 481 | | .ToListAsync(); |
| 0 | 482 | | Dictionary<int, int> completedTaskPerHourDict = []; |
| 0 | 483 | | foreach (var taskCount in completedTaskPerHour) |
| 0 | 484 | | { |
| 0 | 485 | | completedTaskPerHourDict.Add((int)(CurrentDate - taskCount.Time).TotalHours, taskCount.TasksCompleted); |
| 0 | 486 | | } |
| | 487 | |
|
| 0 | 488 | | var abortedTaskPerHour = await _context.AutoTasksJourney.AsNoTracking() |
| 0 | 489 | | .Where(t => t.CurrentProgressId == (int)ProgressState.Aborted && t.CreatedAt > DateTime.UtcNow.AddHours(-23)) |
| 0 | 490 | | .GroupBy(t => new |
| 0 | 491 | | { |
| 0 | 492 | | t.CreatedAt.Year, |
| 0 | 493 | | t.CreatedAt.Month, |
| 0 | 494 | | t.CreatedAt.Day, |
| 0 | 495 | | t.CreatedAt.Hour |
| 0 | 496 | | }) |
| 0 | 497 | | .Select(g => new |
| 0 | 498 | | { |
| 0 | 499 | | Time = new DateTime(g.Key.Year, g.Key.Month, g.Key.Day, g.Key.Hour, 0, 0), |
| 0 | 500 | | TasksCompleted = g.Count() |
| 0 | 501 | | }) |
| 0 | 502 | | .OrderBy(g => g.Time) |
| 0 | 503 | | .ToListAsync(); |
| 0 | 504 | | Dictionary<int, int> abortedTaskPerHourDict = []; |
| 0 | 505 | | foreach (var taskCount in abortedTaskPerHour) |
| 0 | 506 | | { |
| 0 | 507 | | abortedTaskPerHourDict.Add((int)(CurrentDate - taskCount.Time).TotalHours, taskCount.TasksCompleted); |
| 0 | 508 | | } |
| | 509 | |
|
| 0 | 510 | | AutoTaskStatisticsBusinessModel statistics = new() |
| 0 | 511 | | { |
| 0 | 512 | | LastUpdatedAt = DateTime.Now, |
| 0 | 513 | | WaitingTasks = waitingTaskCount, |
| 0 | 514 | | RunningTasks = runningTaskCount, |
| 0 | 515 | | }; |
| | 516 | |
|
| | 517 | | /* |
| | 518 | | * Get trends |
| | 519 | | * Note that the completed tasks and aborted tasks are looked up in the last 24 hours |
| | 520 | | */ |
| | 521 | | // Now = total tasks |
| 0 | 522 | | statistics.WaitingTasksTrend.Add(waitingTaskCount); |
| 0 | 523 | | statistics.RunningTasksTrend.Add(runningTaskCount); |
| 0 | 524 | | for (int i = 0; i < 23; i++) |
| 0 | 525 | | { |
| 0 | 526 | | statistics.WaitingTasksTrend.Add(waitingTaskPerHourDict.TryGetValue(i, out int count) ? count : 0); |
| 0 | 527 | | statistics.RunningTasksTrend.Add(runningTaskPerHourDict.TryGetValue(i, out count) ? count : 0); |
| 0 | 528 | | statistics.CompletedTasksTrend.Add(completedTaskPerHourDict.TryGetValue(i, out count) ? count : 0); |
| 0 | 529 | | statistics.AbortedTasksTrend.Add(abortedTaskPerHourDict.TryGetValue(i, out count) ? count : 0); |
| 0 | 530 | | } |
| 0 | 531 | | statistics.WaitingTasksTrend.Reverse(); |
| 0 | 532 | | statistics.RunningTasksTrend.Reverse(); |
| 0 | 533 | | statistics.CompletedTasksTrend.Reverse(); |
| 0 | 534 | | statistics.CompletedTasks = statistics.CompletedTasksTrend.Sum(); |
| 0 | 535 | | statistics.AbortedTasksTrend.Reverse(); |
| 0 | 536 | | statistics.AbortedTasks = statistics.AbortedTasksTrend.Sum(); |
| 0 | 537 | | _memoryCache.Set("Automation_Statistics", statistics, TimeSpan.FromMinutes(5)); |
| | 538 | |
|
| 0 | 539 | | return statistics; |
| 0 | 540 | | } |
| | 541 | |
|
| | 542 | | public async Task<AutoTaskListBusinessModel?> GetRobotCurrentTaskAsync(Guid robotId) |
| 0 | 543 | | { |
| 0 | 544 | | var autoTask = await _context.AutoTasks.AsNoTracking() |
| 0 | 545 | | .Where(t => !LgdxHelper.AutoTaskStaticStates.Contains(t.CurrentProgressId!) && t.AssignedRobotId == robotId) |
| 0 | 546 | | .Select(t => new AutoTaskListBusinessModel |
| 0 | 547 | | { |
| 0 | 548 | | Id = t.Id, |
| 0 | 549 | | Name = t.Name, |
| 0 | 550 | | Priority = t.Priority, |
| 0 | 551 | | FlowId = t.Flow!.Id, |
| 0 | 552 | | FlowName = t.Flow.Name, |
| 0 | 553 | | RealmId = t.Realm.Id, |
| 0 | 554 | | RealmName = t.Realm.Name, |
| 0 | 555 | | AssignedRobotId = t.AssignedRobotId, |
| 0 | 556 | | AssignedRobotName = t.AssignedRobot!.Name, |
| 0 | 557 | | CurrentProgressId = t.CurrentProgressId, |
| 0 | 558 | | CurrentProgressName = t.CurrentProgress.Name, |
| 0 | 559 | | }) |
| 0 | 560 | | .FirstOrDefaultAsync(); |
| 0 | 561 | | return autoTask; |
| 0 | 562 | | } |
| | 563 | | } |