DD.WellWorkover.Cloud/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs
2022-02-21 12:04:26 +05:00

393 lines
17 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using AsbCloudApp.Data;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
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 readonly IWellService wellService;
private readonly IBackgroundWorkerService backgroundWorker;
private readonly string connectionString;
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,
IWellService wellService,
IConfiguration configuration,
IBackgroundWorkerService backgroundWorker)
{
this.context = context;
this.fileService = fileService;
this.userService = userService;
this.wellService = wellService;
this.backgroundWorker = backgroundWorker;
this.connectionString = configuration.GetConnectionString("DefaultConnection");
}
public async Task<IEnumerable<UserDto>> GetAvailableUsers(int idWell, CancellationToken token = default)
{
var users = await context.RelationCompaniesWells
.Include(r => r.Company)
.ThenInclude(c => c.Users)
.Where(r => r.IdWell == idWell)
.SelectMany(r => r.Company.Users)
.Where(u => u != null && !string.IsNullOrEmpty(u.Email))
.ToListAsync(token);
var usersDto = users.Adapt<UserDto>();
return usersDto;
}
public async Task<IEnumerable<FileCategoryDto>> 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<FileCategoryDto>());
}
public async Task<DrillingProgramStateDto> 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<DrillingProgramPartDto>(partEntities.Count);
foreach (var partEntity in partEntities)
{
var part = ConvertPart(idUser, fileCategories, files, partEntity);
parts.Add(part);
}
var state = new DrillingProgramStateDto
{
Parts = parts,
Program = files.FirstOrDefault(f => f.IdCategory == idFileCategoryDrillingProgram)
.Adapt<FileInfoDto>()
};
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;
await TryEnqueueMakeProgramAsync(idWell, state, token);
return state;
}
public async Task<int> AddFile(int idWell, int idFileCategory, int idUser, string fileFullName, System.IO.Stream fileStream, CancellationToken token = default)
{
var part = await context.DrillingProgramParts
.Include(p => p.RelatedUsers)
.FirstOrDefaultAsync(p => p.IdWell == idWell && p.IdFileCategory == idFileCategory, token);
if (part == null)
throw new ArgumentInvalidException($"DrillingProgramPart id == {idFileCategory} does not exist", nameof(idFileCategory));
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<int> AddPartsAsync(int idWell, IEnumerable<int> idFileCategories, CancellationToken token = default)
{
if (!idFileCategories.Any())
return 0;
var existingCategories = await context.DrillingProgramParts
.Where(p => p.IdWell == idWell)
.Select(p => p.IdFileCategory)
.ToListAsync(token);
var newParts = idFileCategories
.Where(c => !existingCategories.Any(e => e == c))
.Select(c => new DrillingProgramPart
{
IdWell = idWell,
IdFileCategory = c,
});
context.DrillingProgramParts.AddRange(newParts);
var affected = await context.SaveChangesAsync(token);
await RemoveDrillingProgramAsync(idWell, token);
return affected;
}
public async Task<int> RemovePartsAsync(int idWell, IEnumerable<int> idFileCategories, CancellationToken token = default)
{
var whereQuery = context.DrillingProgramParts
.Where(p => p.IdWell == idWell && idFileCategories.Contains(p.IdFileCategory));
context.DrillingProgramParts.RemoveRange(whereQuery);
await RemoveDrillingProgramAsync(idWell, token);
return await context.SaveChangesAsync(token);
}
public async Task<int> AddUserAsync(int idWell, int idFileCategory, int idUser, 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 part = await context.DrillingProgramParts
.FirstOrDefaultAsync(p => p.IdWell == idWell && p.IdFileCategory == idFileCategory, token);
if (part is null)
throw new ArgumentInvalidException($"DrillingProgramPart idFileCategory == {idFileCategory} does not exist", nameof(idFileCategory));
if (idUserRole != idUserRoleApprover && idUserRole != idUserRolePublisher)
throw new ArgumentInvalidException($"idUserRole ({idUserRole}), should be approver ({idUserRoleApprover}) or publisher ({idUserRolePublisher})", nameof(idUserRole));
var oldRelation = await context.RelationDrillingProgramPartUsers
.FirstOrDefaultAsync(r => r.IdUser == idUser && r.IdDrillingProgramPart == part.Id, token);
if(oldRelation is not null)
context.RelationDrillingProgramPartUsers.Remove(oldRelation);
var newRelation = new RelationUserDrillingProgramPart
{
IdUser = idUser,
IdDrillingProgramPart = part.Id,
IdUserRole = idUserRole,
};
context.RelationDrillingProgramPartUsers.Add(newRelation);
if(idUserRole == idUserRoleApprover)
await RemoveDrillingProgramAsync(part.IdWell, token);
return await context.SaveChangesAsync(token);
}
public async Task<int> RemoveUserAsync(int idWell, int idFileCategory, int idUser, int idUserRole, CancellationToken token = default)
{
var whereQuery = context.RelationDrillingProgramPartUsers
.Include(r => r.DrillingProgramPart)
.Where(r => r.IdUser == idUser &&
r.IdUserRole == idUserRole &&
r.DrillingProgramPart.IdWell == idWell &&
r.DrillingProgramPart.IdFileCategory == idFileCategory);
context.RelationDrillingProgramPartUsers.RemoveRange(whereQuery);
return await context.SaveChangesAsync(token);
}
public async Task<int> 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
.Include(p => p.RelatedUsers)
.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 == idMarkTypeReject)
await RemoveDrillingProgramAsync(fileInfo.IdWell, token);
return result;
}
public async Task<int> 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<FileCategory> fileCategories, List<AsbCloudDb.Model.FileInfo> files, DrillingProgramPart partEntity)
{
var part = new DrillingProgramPartDto
{
IdFileCategory = partEntity.IdFileCategory,
Name = fileCategories.FirstOrDefault(c => c.Id == partEntity.IdFileCategory).Name,
Approvers = partEntity.RelatedUsers
.Where(r => r.IdUserRole == idUserRoleApprover)
.Select(r => r.User.Adapt<UserDto>()),
Publishers = partEntity.RelatedUsers
.Where(r => r.IdUserRole == idUserRolePublisher)
.Select(r => r.User.Adapt<UserDto>()),
PermissionToApprove = partEntity.RelatedUsers
.Any(r => r.IdUserRole == idUserRoleApprover && r.IdUser == idUser),
PermissionToUpload = partEntity.RelatedUsers
.Any(r => r.IdUserRole == idUserRolePublisher && r.IdUser == idUser),
};
var fileEntity = files.LastOrDefault(f => f.IdCategory == partEntity.IdFileCategory);
if (fileEntity is not null)
{
part.File = fileEntity.Adapt<FileInfoDto>();
if (part.File.FileMarks is not null)
part.File.FileMarks = part.File.FileMarks.Where(m => !m.IsDeleted);
part.IdState = idPartStateApproving;
var marks = fileEntity.FileMarks.Where(m => !m.IsDeleted);
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 = idPartStateNoFile;
return part;
}
private async Task TryEnqueueMakeProgramAsync(int idWell, DrillingProgramStateDto state, CancellationToken token)
{
if (state.IdState == idStateCreating)
{
var workId = MakeWorkId(idWell);
if (!backgroundWorker.Contains(workId))
{
var well = await wellService.GetAsync(idWell, token);
var resultFileName = $"Программа бурения {well.Cluster} {well.Caption}.xlsx";
var tempResultFilePath = Path.Combine(Path.GetTempPath(), "drillingProgram", resultFileName);
async Task funcProgramMake(string id, CancellationToken token)
{
var contextOptions = new DbContextOptionsBuilder<AsbCloudDbContext>()
.UseNpgsql(connectionString)
.Options;
using var context = new AsbCloudDbContext(contextOptions);
var fileService = new FileService(context);
var files = state.Parts.Select(p => fileService.GetUrl(p.File));
DrillingProgramMaker.UniteExcelFiles(files, tempResultFilePath);
await fileService.MoveAsync(idWell, null, idFileCategoryDrillingProgram, resultFileName, tempResultFilePath, token);
}
backgroundWorker.Enqueue(funcProgramMake);
}
}
}
private async Task<int> RemoveDrillingProgramAsync(int idWell, CancellationToken token)
{
var workId = MakeWorkId(idWell);
backgroundWorker.TryRemove(workId);
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;
}
private static string MakeWorkId(int idWell)
=> $"Make drilling program for wellId {idWell}";
}
}