|  |  | 1 |  | using LGDXRobotCloud.API.Exceptions; | 
|  |  | 2 |  | using LGDXRobotCloud.API.Services.Administration; | 
|  |  | 3 |  | using LGDXRobotCloud.Data.DbContexts; | 
|  |  | 4 |  | using LGDXRobotCloud.Data.Entities; | 
|  |  | 5 |  | using LGDXRobotCloud.Data.Models.Business.Administration; | 
|  |  | 6 |  | using LGDXRobotCloud.Data.Models.Business.Automation; | 
|  |  | 7 |  | using LGDXRobotCloud.Utilities.Enums; | 
|  |  | 8 |  | using LGDXRobotCloud.Utilities.Helpers; | 
|  |  | 9 |  | using Microsoft.EntityFrameworkCore; | 
|  |  | 10 |  |  | 
|  |  | 11 |  | namespace LGDXRobotCloud.API.Services.Automation; | 
|  |  | 12 |  |  | 
|  |  | 13 |  | public interface IFlowService | 
|  |  | 14 |  | { | 
|  |  | 15 |  |   Task<(IEnumerable<FlowListBusinessModel>, PaginationHelper)> GetFlowsAsync(string? name, int pageNumber, int pageSize) | 
|  |  | 16 |  |   Task<FlowBusinessModel> GetFlowAsync(int flowId); | 
|  |  | 17 |  |   Task<FlowBusinessModel> CreateFlowAsync(FlowCreateBusinessModel flowCreateBusinessModel); | 
|  |  | 18 |  |   Task<bool> UpdateFlowAsync(int flowId, FlowUpdateBusinessModel flowUpdateBusinessModel); | 
|  |  | 19 |  |   Task<bool> TestDeleteFlowAsync(int flowId); | 
|  |  | 20 |  |   Task<bool> DeleteFlowAsync(int flowId); | 
|  |  | 21 |  |  | 
|  |  | 22 |  |   Task<IEnumerable<FlowSearchBusinessModel>> SearchFlowsAsync(string? name); | 
|  |  | 23 |  | } | 
|  |  | 24 |  |  | 
|  | 19 | 25 |  | public class FlowService( | 
|  | 19 | 26 |  |     IActivityLogService activityLogService, | 
|  | 19 | 27 |  |     LgdxContext context | 
|  | 19 | 28 |  |   ) : IFlowService | 
|  |  | 29 |  | { | 
|  | 19 | 30 |  |   private readonly IActivityLogService _activityLogService = activityLogService ?? throw new ArgumentNullException(nameo | 
|  | 19 | 31 |  |   private readonly LgdxContext _context = context ?? throw new ArgumentNullException(nameof(context)); | 
|  |  | 32 |  |  | 
|  |  | 33 |  |   public async Task<(IEnumerable<FlowListBusinessModel>, PaginationHelper)> GetFlowsAsync(string? name, int pageNumber,  | 
|  | 4 | 34 |  |   { | 
|  | 4 | 35 |  |     var query = _context.Flows as IQueryable<Flow>; | 
|  | 4 | 36 |  |       if(!string.IsNullOrWhiteSpace(name)) | 
|  | 3 | 37 |  |       { | 
|  | 3 | 38 |  |         name = name.Trim(); | 
|  | 3 | 39 |  |         query = query.Where(f => f.Name.ToLower().Contains(name.ToLower())); | 
|  | 3 | 40 |  |       } | 
|  | 4 | 41 |  |       var itemCount = await query.CountAsync(); | 
|  | 4 | 42 |  |       var PaginationHelper = new PaginationHelper(itemCount, pageNumber, pageSize); | 
|  | 4 | 43 |  |       var flows = await query.AsNoTracking() | 
|  | 4 | 44 |  |         .OrderBy(t => t.Id) | 
|  | 4 | 45 |  |         .Skip(pageSize * (pageNumber - 1)) | 
|  | 4 | 46 |  |         .Take(pageSize) | 
|  | 4 | 47 |  |         .Select(t => new FlowListBusinessModel { | 
|  | 4 | 48 |  |           Id = t.Id, | 
|  | 4 | 49 |  |           Name = t.Name, | 
|  | 4 | 50 |  |         }) | 
|  | 4 | 51 |  |         .ToListAsync(); | 
|  | 4 | 52 |  |       return (flows, PaginationHelper); | 
|  | 4 | 53 |  |   } | 
|  |  | 54 |  |  | 
|  |  | 55 |  |   public async Task<FlowBusinessModel> GetFlowAsync(int flowId) | 
|  | 2 | 56 |  |   { | 
|  | 2 | 57 |  |     return await _context.Flows.Where(f => f.Id == flowId) | 
|  | 2 | 58 |  |       .Include(f => f.FlowDetails | 
|  | 2 | 59 |  |         .OrderBy(fd => fd.Order)) | 
|  | 2 | 60 |  |         .ThenInclude(fd => fd.Progress) | 
|  | 2 | 61 |  |       .Include(f => f.FlowDetails) | 
|  | 2 | 62 |  |         .ThenInclude(fd => fd.Trigger) | 
|  | 2 | 63 |  |       .Select(f => new FlowBusinessModel { | 
|  | 2 | 64 |  |         Id = f.Id, | 
|  | 2 | 65 |  |         Name = f.Name, | 
|  | 2 | 66 |  |         FlowDetails = f.FlowDetails.Select(fd => new FlowDetailBusinessModel { | 
|  | 2 | 67 |  |           Id = fd.Id, | 
|  | 2 | 68 |  |           Order = fd.Order, | 
|  | 2 | 69 |  |           ProgressId = fd.Progress.Id, | 
|  | 2 | 70 |  |           ProgressName = fd.Progress.Name, | 
|  | 2 | 71 |  |           AutoTaskNextControllerId = fd.AutoTaskNextControllerId, | 
|  | 2 | 72 |  |           TriggerId = fd.Trigger!.Id, | 
|  | 2 | 73 |  |           TriggerName = fd.Trigger!.Name, | 
|  | 2 | 74 |  |         }).OrderBy(fd => fd.Order).ToList(), | 
|  | 2 | 75 |  |       }) | 
|  | 2 | 76 |  |       .FirstOrDefaultAsync() | 
|  | 2 | 77 |  |         ?? throw new LgdxNotFound404Exception(); | 
|  | 1 | 78 |  |   } | 
|  |  | 79 |  |  | 
|  |  | 80 |  |   private async Task ValidateFlow(HashSet<int> progressIds, HashSet<int> triggerIds) | 
|  | 5 | 81 |  |   { | 
|  | 8 | 82 |  |     var progresses = await _context.Progresses.Where(p => progressIds.Contains(p.Id)).ToDictionaryAsync(p => p.Id); | 
|  | 21 | 83 |  |     foreach (var progressId in progressIds) | 
|  | 4 | 84 |  |     { | 
|  | 4 | 85 |  |       if (progresses.TryGetValue(progressId, out var progress)) | 
|  | 3 | 86 |  |       { | 
|  | 3 | 87 |  |         if (progress.Reserved) | 
|  | 1 | 88 |  |         { | 
|  | 1 | 89 |  |           throw new LgdxValidation400Expection("Progress", $"The Progress ID: {progressId} is reserved."); | 
|  |  | 90 |  |         } | 
|  | 2 | 91 |  |       } | 
|  |  | 92 |  |       else | 
|  | 1 | 93 |  |       { | 
|  | 1 | 94 |  |         throw new LgdxValidation400Expection("Progress", $"The Progress Id: {progressId} is invalid."); | 
|  |  | 95 |  |       } | 
|  | 2 | 96 |  |     } | 
|  | 4 | 97 |  |     var triggers = await _context.Triggers.Where(t => triggerIds.Contains(t.Id)).ToDictionaryAsync(t => t.Id); | 
|  | 12 | 98 |  |     foreach (var triggerId in triggerIds) | 
|  | 2 | 99 |  |     { | 
|  | 2 | 100 |  |       if (!triggers.TryGetValue(triggerId, out var trigger)) | 
|  | 1 | 101 |  |       { | 
|  | 1 | 102 |  |         throw new LgdxValidation400Expection("Trigger", $"The Trigger ID: {triggerId} is invalid."); | 
|  |  | 103 |  |       } | 
|  | 1 | 104 |  |     } | 
|  | 2 | 105 |  |   } | 
|  |  | 106 |  |  | 
|  |  | 107 |  |   public async Task<FlowBusinessModel> CreateFlowAsync(FlowCreateBusinessModel flowCreateBusinessModel) | 
|  | 4 | 108 |  |   { | 
|  | 8 | 109 |  |     HashSet<int> progressIds = flowCreateBusinessModel.FlowDetails.Select(d => d.ProgressId).ToHashSet(); | 
|  | 12 | 110 |  |     HashSet<int> triggerIds = flowCreateBusinessModel.FlowDetails.Where(d => d.TriggerId != null).Select(d => d.TriggerI | 
|  | 4 | 111 |  |     await ValidateFlow(progressIds, triggerIds); | 
|  | 1 | 112 |  |     var flow = new Flow { | 
|  | 1 | 113 |  |       Name = flowCreateBusinessModel.Name, | 
|  | 1 | 114 |  |       FlowDetails = flowCreateBusinessModel.FlowDetails.Select(fd => new FlowDetail { | 
|  | 1 | 115 |  |         Order = fd.Order, | 
|  | 1 | 116 |  |         ProgressId = fd.ProgressId, | 
|  | 1 | 117 |  |         AutoTaskNextControllerId = fd.AutoTaskNextControllerId, | 
|  | 1 | 118 |  |         TriggerId = fd.TriggerId, | 
|  | 1 | 119 |  |       }).ToList(), | 
|  | 1 | 120 |  |     }; | 
|  | 1 | 121 |  |     await _context.Flows.AddAsync(flow); | 
|  | 1 | 122 |  |     await _context.SaveChangesAsync(); | 
|  |  | 123 |  |  | 
|  | 1 | 124 |  |     await _activityLogService.CreateActivityLogAsync(new ActivityLogCreateBusinessModel | 
|  | 1 | 125 |  |     { | 
|  | 1 | 126 |  |       EntityName = nameof(Flow), | 
|  | 1 | 127 |  |       EntityId = flow.Id.ToString(), | 
|  | 1 | 128 |  |       Action = ActivityAction.Create, | 
|  | 1 | 129 |  |     }); | 
|  |  | 130 |  |  | 
|  | 1 | 131 |  |     return new FlowBusinessModel | 
|  | 1 | 132 |  |     { | 
|  | 1 | 133 |  |       Id = flow.Id, | 
|  | 1 | 134 |  |       Name = flow.Name, | 
|  | 1 | 135 |  |       FlowDetails = flow.FlowDetails.Select(fd => new FlowDetailBusinessModel | 
|  | 1 | 136 |  |       { | 
|  | 1 | 137 |  |         Id = fd.Id, | 
|  | 1 | 138 |  |         Order = fd.Order, | 
|  | 1 | 139 |  |         ProgressId = fd.ProgressId, | 
|  | 1 | 140 |  |         ProgressName = fd.Progress.Name, | 
|  | 1 | 141 |  |         AutoTaskNextControllerId = fd.AutoTaskNextControllerId, | 
|  | 1 | 142 |  |         TriggerId = fd.TriggerId, | 
|  | 1 | 143 |  |         TriggerName = fd.Trigger?.Name, | 
|  | 1 | 144 |  |       }).ToList(), | 
|  | 1 | 145 |  |     }; | 
|  | 1 | 146 |  |   } | 
|  |  | 147 |  |  | 
|  |  | 148 |  |   public async Task<bool> UpdateFlowAsync(int flowId, FlowUpdateBusinessModel flowUpdateBusinessModel) | 
|  | 2 | 149 |  |   { | 
|  | 2 | 150 |  |     var flow = await _context.Flows.Where(f => f.Id == flowId) | 
|  | 2 | 151 |  |       .Include(f => f.FlowDetails | 
|  | 2 | 152 |  |         .OrderBy(fd => fd.Order)) | 
|  | 2 | 153 |  |         .ThenInclude(fd => fd.Progress) | 
|  | 2 | 154 |  |       .Include(f => f.FlowDetails) | 
|  | 2 | 155 |  |         .ThenInclude(fd => fd.Trigger) | 
|  | 2 | 156 |  |       .FirstOrDefaultAsync() | 
|  | 2 | 157 |  |         ?? throw new LgdxNotFound404Exception(); | 
|  |  | 158 |  |  | 
|  | 1 | 159 |  |     HashSet<int?> dbDetailIds = flowUpdateBusinessModel.FlowDetails.Where(d => d.Id != null).Select(d => d.Id).ToHashSet | 
|  | 3 | 160 |  |     foreach(var dtoDetailId in flowUpdateBusinessModel.FlowDetails.Where(d => d.Id != null).Select(d => d.Id)) | 
|  | 0 | 161 |  |     { | 
|  | 0 | 162 |  |       if(!dbDetailIds.Contains((int)dtoDetailId!)) | 
|  | 0 | 163 |  |       { | 
|  | 0 | 164 |  |         throw new LgdxValidation400Expection("FlowDetails", $"The Flow Detail ID {(int)dtoDetailId} is belongs to other  | 
|  |  | 165 |  |       } | 
|  | 0 | 166 |  |     } | 
|  | 1 | 167 |  |     HashSet<int> progressIds = flowUpdateBusinessModel.FlowDetails.Select(d => d.ProgressId).ToHashSet(); | 
|  | 1 | 168 |  |     HashSet<int> triggerIds = flowUpdateBusinessModel.FlowDetails.Where(d => d.TriggerId != null).Select(d => d.TriggerI | 
|  | 1 | 169 |  |     await ValidateFlow(progressIds, triggerIds); | 
|  |  | 170 |  |  | 
|  | 1 | 171 |  |     flow.Name = flowUpdateBusinessModel.Name; | 
|  | 1 | 172 |  |     flow.FlowDetails = flowUpdateBusinessModel.FlowDetails.Select(fd => new FlowDetail { | 
|  | 1 | 173 |  |         Id = (int)fd.Id!, | 
|  | 1 | 174 |  |         Order = fd.Order, | 
|  | 1 | 175 |  |         ProgressId = fd.ProgressId, | 
|  | 1 | 176 |  |         AutoTaskNextControllerId = fd.AutoTaskNextControllerId, | 
|  | 1 | 177 |  |         TriggerId = fd.TriggerId, | 
|  | 1 | 178 |  |       }).ToList(); | 
|  | 1 | 179 |  |     await _context.SaveChangesAsync(); | 
|  |  | 180 |  |  | 
|  | 1 | 181 |  |     await _activityLogService.CreateActivityLogAsync(new ActivityLogCreateBusinessModel | 
|  | 1 | 182 |  |     { | 
|  | 1 | 183 |  |       EntityName = nameof(Flow), | 
|  | 1 | 184 |  |       EntityId = flowId.ToString(), | 
|  | 1 | 185 |  |       Action = ActivityAction.Update, | 
|  | 1 | 186 |  |     }); | 
|  |  | 187 |  |  | 
|  | 1 | 188 |  |     return true; | 
|  | 1 | 189 |  |   } | 
|  |  | 190 |  |  | 
|  |  | 191 |  |   public async Task<bool> TestDeleteFlowAsync(int flowId) | 
|  | 2 | 192 |  |   { | 
|  | 2 | 193 |  |     var dependencies = await _context.AutoTasks | 
|  | 2 | 194 |  |       .Where(a => a.FlowId == flowId) | 
|  | 2 | 195 |  |       .Where(a => a.CurrentProgressId != (int)ProgressState.Completed && a.CurrentProgressId != (int)ProgressState.Abort | 
|  | 2 | 196 |  |       .CountAsync(); | 
|  | 2 | 197 |  |     if (dependencies > 0) | 
|  | 1 | 198 |  |     { | 
|  | 1 | 199 |  |       throw new LgdxValidation400Expection(nameof(flowId), $"This flow has been used by {dependencies} running/waiting/t | 
|  |  | 200 |  |     } | 
|  | 1 | 201 |  |     return true; | 
|  | 1 | 202 |  |   } | 
|  |  | 203 |  |  | 
|  |  | 204 |  |   public async Task<bool> DeleteFlowAsync(int flowId) | 
|  | 0 | 205 |  |   { | 
|  | 0 | 206 |  |     var result = await _context.Flows.Where(f => f.Id == flowId) | 
|  | 0 | 207 |  |       .ExecuteDeleteAsync() == 1; | 
|  |  | 208 |  |  | 
|  | 0 | 209 |  |     if (result) | 
|  | 0 | 210 |  |     { | 
|  | 0 | 211 |  |       await _activityLogService.CreateActivityLogAsync(new ActivityLogCreateBusinessModel | 
|  | 0 | 212 |  |       { | 
|  | 0 | 213 |  |         EntityName = nameof(Flow), | 
|  | 0 | 214 |  |         EntityId = flowId.ToString(), | 
|  | 0 | 215 |  |         Action = ActivityAction.Delete, | 
|  | 0 | 216 |  |       }); | 
|  | 0 | 217 |  |     } | 
|  | 0 | 218 |  |     return result; | 
|  | 0 | 219 |  |   } | 
|  |  | 220 |  |  | 
|  |  | 221 |  |   public async Task<IEnumerable<FlowSearchBusinessModel>> SearchFlowsAsync(string? name) | 
|  | 5 | 222 |  |   { | 
|  | 5 | 223 |  |     var n = name ?? string.Empty; | 
|  | 5 | 224 |  |     return await _context.Flows.AsNoTracking() | 
|  | 5 | 225 |  |       .Where(w => w.Name.ToLower().Contains(n.ToLower())) | 
|  | 5 | 226 |  |       .Take(10) | 
|  | 5 | 227 |  |       .Select(t => new FlowSearchBusinessModel { | 
|  | 5 | 228 |  |         Id = t.Id, | 
|  | 5 | 229 |  |         Name = t.Name, | 
|  | 5 | 230 |  |       }) | 
|  | 5 | 231 |  |       .ToListAsync(); | 
|  | 5 | 232 |  |   } | 
|  |  | 233 |  | } |