< Summary

Information
Class: LGDXRobotCloud.API.Services.Automation.AutoTaskService
Assembly: LGDXRobotCloud.API
File(s): /builds/yukaitung/lgdxrobot2-cloud/LGDXRobotCloud.API/Services/Automation/AutoTaskService.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 287
Coverable lines: 287
Total lines: 364
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 68
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)0%7280%
GetAutoTasksAsync()0%156120%
GetAutoTaskAsync()0%620%
ValidateAutoTask()0%156120%
CreateAutoTaskAsync()0%4260%
UpdateAutoTaskAsync()0%110100%
DeleteAutoTaskAsync()0%2040%
AbortAutoTaskAsync()0%210140%
AutoTaskNextApiAsync()0%620%

File(s)

/builds/yukaitung/lgdxrobot2-cloud/LGDXRobotCloud.API/Services/Automation/AutoTaskService.cs

#LineLine coverage
 1using LGDXRobotCloud.API.Exceptions;
 2using LGDXRobotCloud.API.Services.Common;
 3using LGDXRobotCloud.API.Services.Navigation;
 4using LGDXRobotCloud.Data.DbContexts;
 5using LGDXRobotCloud.Data.Entities;
 6using LGDXRobotCloud.Data.Models.Business.Automation;
 7using LGDXRobotCloud.Data.Models.Business.Navigation;
 8using LGDXRobotCloud.Utilities.Enums;
 9using LGDXRobotCloud.Utilities.Helpers;
 10using MassTransit;
 11using Microsoft.EntityFrameworkCore;
 12
 13namespace LGDXRobotCloud.API.Services.Automation;
 14
 15public interface IAutoTaskService
 16{
 17  Task<(IEnumerable<AutoTaskListBusinessModel>, PaginationHelper)> GetAutoTasksAsync(int? realmId, string? name, AutoTas
 18  Task<AutoTaskBusinessModel> GetAutoTaskAsync(int autoTaskId);
 19  Task<AutoTaskBusinessModel> CreateAutoTaskAsync(AutoTaskCreateBusinessModel autoTaskCreateBusinessModel);
 20  Task<bool> UpdateAutoTaskAsync(int autoTaskId, AutoTaskUpdateBusinessModel autoTaskUpdateBusinessModel);
 21  Task<bool> DeleteAutoTaskAsync(int autoTaskId);
 22
 23  Task AbortAutoTaskAsync(int autoTaskId);
 24  Task AutoTaskNextApiAsync(Guid robotId, int taskId, string token);
 25}
 26
 027public class AutoTaskService(
 028    LgdxContext context,
 029    IAutoTaskSchedulerService autoTaskSchedulerService,
 030    IBus bus,
 031    IEventService eventService,
 032    IOnlineRobotsService onlineRobotsService
 033  ) : IAutoTaskService
 34{
 035  private readonly IOnlineRobotsService _onlineRobotsService = onlineRobotsService ?? throw new ArgumentNullException(na
 036  private readonly LgdxContext _context = context ?? throw new ArgumentNullException(nameof(context));
 037  private readonly IAutoTaskSchedulerService _autoTaskSchedulerService = autoTaskSchedulerService ?? throw new ArgumentN
 038  private readonly IBus _bus = bus ?? throw new ArgumentNullException(nameof(bus));
 39
 40  public async Task<(IEnumerable<AutoTaskListBusinessModel>, PaginationHelper)> GetAutoTasksAsync(int? realmId, string? 
 041  {
 042    var query = _context.AutoTasks as IQueryable<AutoTask>;
 043    if (!string.IsNullOrWhiteSpace(name))
 044    {
 045      name = name.Trim();
 046      query = query.Where(t => t.Name != null && t.Name.Contains(name));
 047    }
 048    if (realmId != null)
 049    {
 050      query = query.Where(t => t.RealmId == realmId);
 051    }
 052    switch (autoTaskCatrgory)
 53    {
 54      case AutoTaskCatrgory.Template:
 055        query = query.Where(t => t.CurrentProgressId == (int)ProgressState.Template);
 056        break;
 57      case AutoTaskCatrgory.Waiting:
 058        query = query.Where(t => t.CurrentProgressId == (int)ProgressState.Waiting);
 059        break;
 60      case AutoTaskCatrgory.Completed:
 061        query = query.Where(t => t.CurrentProgressId == (int)ProgressState.Completed);
 062        break;
 63      case AutoTaskCatrgory.Aborted:
 064        query = query.Where(t => t.CurrentProgressId == (int)ProgressState.Aborted);
 065        break;
 66      case AutoTaskCatrgory.Running:
 067        query = query.Where(t => t.CurrentProgressId > (int)ProgressState.Aborted);
 068        break;
 69    }
 070    var itemCount = await query.CountAsync();
 071    var PaginationHelper = new PaginationHelper(itemCount, pageNumber, pageSize);
 072    var autoTasks = await query.AsNoTracking()
 073      .OrderByDescending(t => t.Priority)
 074      .ThenBy(t => t.Id)
 075      .Skip(pageSize * (pageNumber - 1))
 076      .Take(pageSize)
 077      .Select(t => new AutoTaskListBusinessModel {
 078        Id = t.Id,
 079        Name = t.Name,
 080        Priority = t.Priority,
 081        FlowId = t.Flow!.Id,
 082        FlowName = t.Flow.Name,
 083        RealmId = t.Realm.Id,
 084        RealmName = t.Realm.Name,
 085        AssignedRobotId = t.AssignedRobotId,
 086        AssignedRobotName = t.AssignedRobot!.Name,
 087        CurrentProgressId = t.CurrentProgressId,
 088        CurrentProgressName = t.CurrentProgress.Name,
 089      })
 090      .AsSplitQuery()
 091      .ToListAsync();
 092    return (autoTasks, PaginationHelper);
 093  }
 94
 95  public async Task<AutoTaskBusinessModel> GetAutoTaskAsync(int autoTaskId)
 096  {
 097    return await _context.AutoTasks.AsNoTracking()
 098      .Where(t => t.Id == autoTaskId)
 099      .Include(t => t.AutoTaskDetails
 0100        .OrderBy(td => td.Order))
 0101        .ThenInclude(td => td.Waypoint)
 0102      .Include(t => t.AssignedRobot)
 0103      .Include(t => t.CurrentProgress)
 0104      .Include(t => t.Flow)
 0105      .Include(t => t.Realm)
 0106      .Select(t => new AutoTaskBusinessModel {
 0107        Id = t.Id,
 0108        Name = t.Name,
 0109        Priority = t.Priority,
 0110        FlowId = t.Flow!.Id,
 0111        FlowName = t.Flow.Name,
 0112        RealmId = t.Realm.Id,
 0113        RealmName = t.Realm.Name,
 0114        AssignedRobotId = t.AssignedRobotId,
 0115        AssignedRobotName = t.AssignedRobot!.Name,
 0116        CurrentProgressId = t.CurrentProgressId,
 0117        CurrentProgressName = t.CurrentProgress.Name,
 0118        AutoTaskDetails = t.AutoTaskDetails.Select(td => new AutoTaskDetailBusinessModel {
 0119          Id = td.Id,
 0120          Order = td.Order,
 0121          CustomX = td.CustomX,
 0122          CustomY = td.CustomY,
 0123          CustomRotation = td.CustomRotation,
 0124          Waypoint = td.Waypoint == null ? null : new WaypointBusinessModel {
 0125            Id = td.Waypoint.Id,
 0126            Name = td.Waypoint.Name,
 0127            RealmId = t.Realm.Id,
 0128            RealmName = t.Realm.Name,
 0129            X = td.Waypoint.X,
 0130            Y = td.Waypoint.Y,
 0131            Rotation = td.Waypoint.Rotation,
 0132            IsParking = td.Waypoint.IsParking,
 0133            HasCharger = td.Waypoint.HasCharger,
 0134            IsReserved = td.Waypoint.IsReserved,
 0135          },
 0136        })
 0137        .OrderBy(td => td.Order)
 0138        .ToList()
 0139      })
 0140      .FirstOrDefaultAsync()
 0141        ?? throw new LgdxNotFound404Exception();
 0142  }
 143
 144  private async Task ValidateAutoTask(HashSet<int> waypointIds, int flowId, int realmId, Guid? robotId)
 0145  {
 146    // Validate the Waypoint IDs
 0147    var waypointDict = await _context.Waypoints.AsNoTracking()
 0148      .Where(w => waypointIds.Contains(w.Id))
 0149      .Where(w => w.RealmId == realmId)
 0150      .ToDictionaryAsync(w => w.Id, w => w);
 0151    foreach(var waypointId in waypointIds)
 0152    {
 0153      if (!waypointDict.ContainsKey(waypointId))
 0154      {
 0155        throw new LgdxValidation400Expection(nameof(Waypoint), $"The Waypoint ID {waypointId} is invalid.");
 156      }
 0157    }
 158    // Validate the Flow ID
 0159    var flow = await _context.Flows.Where(f => f.Id == flowId).AnyAsync();
 0160    if (flow == false)
 0161    {
 0162      throw new LgdxValidation400Expection(nameof(Flow), $"The Flow ID {flowId} is invalid.");
 163    }
 164    // Validate the Realm ID
 0165    var realm = await _context.Realms.Where(r => r.Id == realmId).AnyAsync();
 0166    if (realm == false)
 0167    {
 0168      throw new LgdxValidation400Expection(nameof(Realm), $"The Realm ID {realmId} is invalid.");
 169    }
 170    // Validate the Assigned Robot ID
 0171    if (robotId != null)
 0172    {
 0173      var robot = await _context
 0174        .Robots
 0175        .Where(r => r.Id == robotId)
 0176        .Where(r => r.RealmId == realmId)
 0177        .AnyAsync();
 0178      if (robot == false)
 0179      {
 0180        throw new LgdxValidation400Expection(nameof(Robot), $"Robot ID: {robotId} is invalid.");
 181      }
 0182    }
 0183  }
 184
 185  public async Task<AutoTaskBusinessModel> CreateAutoTaskAsync(AutoTaskCreateBusinessModel autoTaskCreateBusinessModel)
 0186  {
 0187    HashSet<int> waypointIds = autoTaskCreateBusinessModel.AutoTaskDetails
 0188      .Where(d => d.WaypointId != null)
 0189      .Select(d => d.WaypointId!.Value)
 0190      .ToHashSet();
 191
 0192    await ValidateAutoTask(waypointIds,
 0193      autoTaskCreateBusinessModel.FlowId,
 0194      autoTaskCreateBusinessModel.RealmId,
 0195      autoTaskCreateBusinessModel.AssignedRobotId);
 196
 0197    var autoTask = new AutoTask {
 0198      Name = autoTaskCreateBusinessModel.Name,
 0199      Priority = autoTaskCreateBusinessModel.Priority,
 0200      FlowId = autoTaskCreateBusinessModel.FlowId,
 0201      RealmId = autoTaskCreateBusinessModel.RealmId,
 0202      AssignedRobotId = autoTaskCreateBusinessModel.AssignedRobotId,
 0203      CurrentProgressId = autoTaskCreateBusinessModel.IsTemplate
 0204        ? (int)ProgressState.Template
 0205        : (int)ProgressState.Waiting,
 0206      AutoTaskDetails = autoTaskCreateBusinessModel.AutoTaskDetails.Select(td => new AutoTaskDetail {
 0207        Order = td.Order,
 0208        CustomX = td.CustomX,
 0209        CustomY = td.CustomY,
 0210        CustomRotation = td.CustomRotation,
 0211        WaypointId = td.WaypointId,
 0212      })
 0213      .OrderBy(td => td.Order)
 0214      .ToList(),
 0215    };
 0216    await _context.AutoTasks.AddAsync(autoTask);
 0217    await _context.SaveChangesAsync();
 0218    _autoTaskSchedulerService.ResetIgnoreRobot(autoTask.RealmId);
 0219    eventService.AutoTaskHasCreated();
 220
 0221    var autoTaskBusinessModel = await _context.AutoTasks.AsNoTracking()
 0222      .Where(t => t.Id == autoTask.Id)
 0223      .Select(t => new AutoTaskBusinessModel {
 0224        Id = t.Id,
 0225        Name = t.Name,
 0226        Priority = t.Priority,
 0227        FlowId = t.Flow!.Id,
 0228        FlowName = t.Flow.Name,
 0229        RealmId = t.Realm.Id,
 0230        RealmName = t.Realm.Name,
 0231        AssignedRobotId = t.AssignedRobotId,
 0232        AssignedRobotName = t.AssignedRobot!.Name,
 0233        CurrentProgressId = t.CurrentProgressId,
 0234        CurrentProgressName = t.CurrentProgress.Name,
 0235        AutoTaskDetails = t.AutoTaskDetails.Select(td => new AutoTaskDetailBusinessModel {
 0236          Id = td.Id,
 0237          Order = td.Order,
 0238          CustomX = td.CustomX,
 0239          CustomY = td.CustomY,
 0240          CustomRotation = td.CustomRotation,
 0241          Waypoint = td.Waypoint == null ? null : new WaypointBusinessModel {
 0242            Id = td.Waypoint.Id,
 0243            Name = td.Waypoint.Name,
 0244            RealmId = t.Realm.Id,
 0245            RealmName = t.Realm.Name,
 0246            X = td.Waypoint.X,
 0247            Y = td.Waypoint.Y,
 0248            Rotation = td.Waypoint.Rotation,
 0249            IsParking = td.Waypoint.IsParking,
 0250            HasCharger = td.Waypoint.HasCharger,
 0251            IsReserved = td.Waypoint.IsReserved,
 0252          },
 0253        })
 0254        .OrderBy(td => td.Order)
 0255        .ToList()
 0256      })
 0257      .FirstOrDefaultAsync()
 0258        ?? throw new LgdxNotFound404Exception();
 259
 0260    if (autoTask.CurrentProgressId == (int)ProgressState.Waiting)
 0261    {
 0262      await _bus.Publish(autoTaskBusinessModel.ToContract());
 0263    }
 264
 0265    return autoTaskBusinessModel;
 0266  }
 267
 268  public async Task<bool> UpdateAutoTaskAsync(int autoTaskId, AutoTaskUpdateBusinessModel autoTaskUpdateBusinessModel)
 0269  {
 0270    var task = await _context.AutoTasks
 0271      .Where(t => t.Id == autoTaskId)
 0272      .Include(t => t.AutoTaskDetails
 0273        .OrderBy(td => td.Order))
 0274      .FirstOrDefaultAsync()
 0275      ?? throw new LgdxNotFound404Exception();
 276
 0277    if (task.CurrentProgressId != (int)ProgressState.Template)
 0278    {
 0279      throw new LgdxValidation400Expection("AutoTaskId", "Only AutoTask Templates are editable.");
 280    }
 281    // Ensure the input task does not include Detail ID from other task
 0282    HashSet<int> dbDetailIds = task.AutoTaskDetails.Select(d => d.Id).ToHashSet();
 0283    foreach(var bmDetailId in autoTaskUpdateBusinessModel.AutoTaskDetails.Where(d => d.Id != null).Select(d => d.Id))
 0284    {
 0285      if (bmDetailId != null && !dbDetailIds.Contains((int)bmDetailId))
 0286      {
 0287        throw new LgdxValidation400Expection("AutoTaskDetailId", $"The Task Detail ID {(int)bmDetailId} is belongs to ot
 288      }
 0289    }
 0290    HashSet<int> waypointIds = autoTaskUpdateBusinessModel.AutoTaskDetails
 0291      .Where(d => d.WaypointId != null)
 0292      .Select(d => d.WaypointId!.Value)
 0293      .ToHashSet();
 0294    await ValidateAutoTask(waypointIds,
 0295      autoTaskUpdateBusinessModel.FlowId,
 0296      task.RealmId,
 0297      autoTaskUpdateBusinessModel.AssignedRobotId);
 298
 0299    task.Name = autoTaskUpdateBusinessModel.Name;
 0300    task.Priority = autoTaskUpdateBusinessModel.Priority;
 0301    task.FlowId = autoTaskUpdateBusinessModel.FlowId;
 0302    task.AssignedRobotId = autoTaskUpdateBusinessModel.AssignedRobotId;
 0303    task.AutoTaskDetails = autoTaskUpdateBusinessModel.AutoTaskDetails.Select(td => new AutoTaskDetail {
 0304      Id = (int)td.Id!,
 0305      Order = td.Order,
 0306      CustomX = td.CustomX,
 0307      CustomY = td.CustomY,
 0308      CustomRotation = td.CustomRotation,
 0309      WaypointId = td.WaypointId,
 0310    }).ToList();
 0311    await _context.SaveChangesAsync();
 0312    return true;
 0313  }
 314
 315  public async Task<bool> DeleteAutoTaskAsync(int autoTaskId)
 0316  {
 0317    var autoTask = await _context.AutoTasks.AsNoTracking()
 0318      .Where(t => t.Id == autoTaskId)
 0319      .FirstOrDefaultAsync()
 0320      ?? throw new LgdxNotFound404Exception();
 0321    if (autoTask.CurrentProgressId != (int)ProgressState.Template)
 0322    {
 0323      throw new LgdxValidation400Expection("AutoTaskId", "Cannot delete the task not in running status.");
 324    }
 325
 0326    _context.AutoTasks.Remove(autoTask);
 0327    await _context.SaveChangesAsync();
 328
 0329    return true;
 0330  }
 331
 332  public async Task AbortAutoTaskAsync(int autoTaskId)
 0333  {
 0334    var autoTask = await _context.AutoTasks.AsNoTracking()
 0335      .Where(t => t.Id == autoTaskId)
 0336      .FirstOrDefaultAsync()
 0337      ?? throw new LgdxNotFound404Exception();
 338
 0339    if (autoTask.CurrentProgressId == (int)ProgressState.Template ||
 0340        autoTask.CurrentProgressId == (int)ProgressState.Completed ||
 0341        autoTask.CurrentProgressId == (int)ProgressState.Aborted)
 0342    {
 0343      throw new LgdxValidation400Expection(nameof(autoTaskId), "Cannot abort the task not in running status.");
 344    }
 0345    if (autoTask.CurrentProgressId != (int)ProgressState.Waiting &&
 0346        autoTask.AssignedRobotId != null &&
 0347        await _onlineRobotsService.SetAbortTaskAsync((Guid)autoTask.AssignedRobotId!, true))
 0348    {
 349      // If the robot is online, abort the task from the robot
 0350      return;
 351    }
 352    else
 0353    {
 0354      await _autoTaskSchedulerService.AutoTaskAbortApiAsync(autoTask.Id);
 0355    }
 0356  }
 357
 358  public async Task AutoTaskNextApiAsync(Guid robotId, int taskId, string token)
 0359  {
 0360    var result = await _autoTaskSchedulerService.AutoTaskNextApiAsync(robotId, taskId, token)
 0361      ?? throw new LgdxValidation400Expection(nameof(token), "The next token is invalid.");
 0362    _onlineRobotsService.SetAutoTaskNextApi(robotId, result);
 0363  }
 364}