using AsbCloudApp.Data; using AsbCloudApp.Exceptions; using AsbCloudApp.Services; using AsbCloudDb.Model; using Mapster; using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services.DrillingProgram { public class DrillingProgramService : IDrillingProgramService { private readonly IAsbCloudDbContext context; private readonly IFileService fileService; private readonly IUserService userService; private const int idFileCategoryDrillingProgram = 1000; private const int idFileCategoryDrillingProgramPartsStart = 1001; private const int idFileCategoryDrillingProgramPartsEnd = 1100; private const int idPartStateNoFile = 0; private const int idPartStateApproving = 1; private const int idPartStateApproved = 2; private const int idMarkTypeReject = 0; private const int idMarkTypeApprove = 1; private const int idUserRolePublisher = 1; private const int idUserRoleApprover = 2; private const int idStateNotInitialized = 0; private const int idStateApproving = 1; private const int idStateCreating = 2; private const int idStateReady = 3; public DrillingProgramService(IAsbCloudDbContext context, IFileService fileService, IUserService userService) { this.context = context; this.fileService = fileService; this.userService = userService; } public async Task> GetCategoriesAsync(CancellationToken token = default) { var result = await context.FileCategories .Where(c=>c.Id > idFileCategoryDrillingProgramPartsStart && c.Id < idFileCategoryDrillingProgramPartsEnd) .ToListAsync(token); return result.Select(c => c.Adapt()); } public async Task GetStateAsync(int idWell, int idUser, CancellationToken token = default) { // Задание от геологов // Профиль ствола скважины(ННБ) // Технологические расчеты(ННБ) // Долотная программа // Программа по растворам // Программа геофизических исследований // Планы спусков обсадных колонн // Программы цементирования обсадных колонн var fileCategories = await context.FileCategories .Where(c => c.Id >= idFileCategoryDrillingProgramPartsStart && c.Id < idFileCategoryDrillingProgramPartsEnd) .ToListAsync(token); var files = await context.Files .Include(f => f.FileMarks) .ThenInclude(m => m.User) .Include(f => f.Author) .Include(f => f.FileCategory) .Where(f => f.IdWell == idWell && f.IdCategory >= idFileCategoryDrillingProgram && f.IdCategory < idFileCategoryDrillingProgramPartsEnd && f.IsDeleted == false) .OrderBy(f => f.UploadDate) .ToListAsync(token); var partEntities = await context.DrillingProgramParts .Include(p => p.RelatedUsers) .ThenInclude(r => r.User) .Where(p => p.IdWell == idWell) .ToListAsync(token); var parts = new List(partEntities.Count); foreach (var partEntity in partEntities) { var part = ConvertPart(idUser, fileCategories, files, partEntity); parts.Add(part); } var state = new DrillingProgramStateDto(); state.Parts = parts; state.Program = files.FirstOrDefault(f => f.IdCategory == idFileCategoryDrillingProgram) .Adapt(); if (parts.Any()) { if(parts.All(p=>p.IdState == idPartStateApproved)) { if (state.Program is not null) state.IdState = idStateReady; else state.IdState = idStateCreating; } else state.IdState = idStateApproving; } else state.IdState = idStateNotInitialized; return state; } public async Task AddFile(int idPart, int idUser, string fileFullName, System.IO.Stream fileStream, CancellationToken token = default) { var part = await context.DrillingProgramParts .Include(p => p.RelatedUsers) .FirstOrDefaultAsync(p => p.Id == idPart); if (part == null) throw new ArgumentInvalidException($"DrillingProgramPart id == {idPart} does not exist", nameof(idPart)); if (! part.RelatedUsers.Any(r => r.IdUser == idUser && r.IdUserRole == idUserRolePublisher)) throw new ForbidException($"User {idUser} is not in the publisher list."); var result = await fileService.SaveAsync( part.IdWell, idUser, part.IdFileCategory, fileFullName, fileStream, token); await RemoveDrillingProgramAsync(part.IdWell, token); return result.Id; } public async Task AddPartAsync(int idWell, int idFileCategory, CancellationToken token = default) { var part = new DrillingProgramPart { IdWell = idWell, IdFileCategory = idFileCategory, }; var entry = context.DrillingProgramParts.Add(part); await context.SaveChangesAsync(token); await RemoveDrillingProgramAsync(part.IdWell, token); return entry.Entity.Id; } public async Task RemovePartAsync(int idWell, int idFileCategory, CancellationToken token = default) { var whereQuery = context.DrillingProgramParts .Where(r => r.IdWell == idWell && r.IdFileCategory == idFileCategory); context.DrillingProgramParts.RemoveRange(whereQuery); await RemoveDrillingProgramAsync(idWell, token); return await context.SaveChangesAsync(token); } public async Task AddUserAsync(int idUser, int idPart, int idUserRole, CancellationToken token = default) { var user = await userService.GetAsync(idUser, token); if (user is null) throw new ArgumentInvalidException($"User id == {idUser} does not exist", nameof(idUser)); var drillingProgramPart = await context.DrillingProgramParts.FirstOrDefaultAsync(p => p.Id == idPart, token); if (drillingProgramPart is null) throw new ArgumentInvalidException($"DrillingProgramPart id == {idPart} does not exist", nameof(idPart)); if (idUserRole != idUserRoleApprover && idUserRole != idUserRolePublisher) throw new ArgumentInvalidException($"idUserRole ({idPart}), should be approver ({idUserRoleApprover}) or publisher({idUserRolePublisher})", nameof(idPart)); var newRelation = new RelationUserDrillingProgramPart { IdUser = idUser, IdDrillingProgramPart = idPart, IdUserRole = idUserRole, }; context.RelationDrillingProgramPartUsers.Add(newRelation); if(idUserRole == idUserRoleApprover) await RemoveDrillingProgramAsync(drillingProgramPart.IdWell, token); return await context.SaveChangesAsync(token); } public async Task RemoveUserAsync(int idUser, int idPart, int idUserRole, CancellationToken token = default) { var whereQuery = context.RelationDrillingProgramPartUsers .Where(r => r.IdUser == idUser && r.IdDrillingProgramPart == idPart && r.IdUserRole == idUserRole); context.RelationDrillingProgramPartUsers.RemoveRange(whereQuery); if (idUserRole == idUserRoleApprover) { var part = await context.DrillingProgramParts.FirstOrDefaultAsync(p => p.Id == idPart, token); await CheckAndEnqueueMakeProgramAsync(part.IdWell, token); } return await context.SaveChangesAsync(token); } public async Task AddOrReplaceFileMarkAsync(FileMarkDto fileMarkDto, int idUser, CancellationToken token) { if(fileMarkDto.IdMarkType != idMarkTypeApprove && fileMarkDto.IdMarkType != idMarkTypeReject) throw new ArgumentInvalidException($"В этом методе допустимы только отметки о принятии или отклонении.", nameof(fileMarkDto)); var fileInfo = await fileService.GetInfoAsync(fileMarkDto.IdFile, token) .ConfigureAwait(false); if (fileInfo is null) throw new ArgumentInvalidException($"Файла для такой отметки не существует.", nameof(fileMarkDto)); if (fileInfo.IdCategory < idFileCategoryDrillingProgramPartsStart || fileInfo.IdCategory > idFileCategoryDrillingProgramPartsEnd) throw new ArgumentInvalidException($"Этот метод допустим только для файлов-частей программы бурения.", nameof(fileMarkDto)); var part = await context.DrillingProgramParts .FirstOrDefaultAsync(p => p.IdWell == fileInfo.IdWell && p.IdFileCategory == fileInfo.IdCategory, token); if (!part.RelatedUsers.Any(r => r.IdUser == idUser && r.IdUserRole == idUserRoleApprover)) throw new ForbidException($"User {idUser} is not in the approvers list."); var oldMarksIds = fileInfo.FileMarks ?.Where(m => m.User.Id == idUser) .Select(m => m.Id); if(oldMarksIds?.Any() == true) await fileService.MarkFileMarkAsDeletedAsync(oldMarksIds, token); var result = await fileService.CreateFileMarkAsync(fileMarkDto, idUser, token) .ConfigureAwait(false); if(fileMarkDto.IdMarkType == idMarkTypeApprove) await CheckAndEnqueueMakeProgramAsync(fileInfo.IdWell, token); else await RemoveDrillingProgramAsync(fileInfo.IdWell, token); return result; } public async Task MarkAsDeletedFileMarkAsync(int idMark, CancellationToken token) { var fileInfo = await fileService.GetByMarkId(idMark, token) .ConfigureAwait(false); if (fileInfo.IdCategory < idFileCategoryDrillingProgramPartsStart || fileInfo.IdCategory > idFileCategoryDrillingProgramPartsEnd) throw new ArgumentInvalidException($"Этот метод допустим только для файлов-частей программы бурения.", nameof(idMark)); var result = await fileService.MarkFileMarkAsDeletedAsync(idMark, token) .ConfigureAwait(false); await RemoveDrillingProgramAsync(fileInfo.IdWell, token); return result; } private static DrillingProgramPartDto ConvertPart(int idUser, List fileCategories, List files, DrillingProgramPart partEntity) { var part = new DrillingProgramPartDto { IdFileCategory = partEntity.IdFileCategory, Name = fileCategories.FirstOrDefault(c => c.Id == partEntity.IdFileCategory).Name, Approvers = partEntity.RelatedUsers .Where(u => u.IdUserRole == idUserRoleApprover) .Select(u => u.Adapt()), Publishers = partEntity.RelatedUsers .Where(u => u.IdUserRole == idUserRolePublisher) .Select(u => u.Adapt()), PermissionToApprove = partEntity.RelatedUsers .Any(u => u.IdUserRole == idUserRoleApprover && u.IdUser == idUser), PermissionToUpload = partEntity.RelatedUsers .Any(u => u.IdUserRole == idUserRolePublisher && u.IdUser == idUser), }; var fileEntity = files.LastOrDefault(f => f.IdCategory == partEntity.IdFileCategory); if (fileEntity is not null) { part.File = fileEntity.Adapt(); var marks = fileEntity.FileMarks.Where(m => !m.IsDeleted); if (marks.Any()) { var hasReject = marks.Any(m => m.IdMarkType == idMarkTypeReject); if (!hasReject) { var allAproved = part.Approvers.All(a => marks.Any(m => m.IdUser == a.Id && m.IdMarkType == idMarkTypeApprove)); if (allAproved) part.IdState = idPartStateApproved; } } else part.IdState = idPartStateApproving; } else part.IdState = idPartStateNoFile; return part; } private async Task CheckAndEnqueueMakeProgramAsync(int idWell, CancellationToken token) { var state = await GetStateAsync(idWell, 0, token); if(state.IdState == idStateCreating) { // TODO: check if task is running else enqueue task throw new NotImplementedException(); } } private async Task RemoveDrillingProgramAsync(int idWell, CancellationToken token) { // TODO: dequeue task if it exist var filesIds = await context.Files .Where(f => f.IdWell == idWell && f.IdCategory == idFileCategoryDrillingProgram) .Select(f => f.Id) .ToListAsync(token); if (filesIds.Any()) return await fileService.DeleteAsync(filesIds, token); else return 0; } } }