2022-02-12 11:28:16 +05:00
using AsbCloudApp.Data ;
using AsbCloudApp.Exceptions ;
using AsbCloudApp.Services ;
using AsbCloudDb.Model ;
using Mapster ;
using Microsoft.EntityFrameworkCore ;
2022-02-17 15:37:27 +05:00
using Microsoft.Extensions.Configuration ;
2022-02-12 11:28:16 +05:00
using System ;
using System.Collections.Generic ;
2022-02-17 15:37:27 +05:00
using System.IO ;
2022-02-12 11:28:16 +05:00
using System.Linq ;
using System.Threading ;
using System.Threading.Tasks ;
namespace AsbCloudInfrastructure.Services.DrillingProgram
{
public class DrillingProgramService : IDrillingProgramService
{
2022-02-28 14:44:15 +05:00
private static Dictionary < string , DrillingProgramCreateError > drillingProgramCreateErrors = new Dictionary < string , DrillingProgramCreateError > ( ) ;
2022-02-12 11:28:16 +05:00
private readonly IAsbCloudDbContext context ;
private readonly IFileService fileService ;
private readonly IUserService userService ;
2022-02-17 15:37:27 +05:00
private readonly IWellService wellService ;
2022-02-28 14:44:15 +05:00
private readonly IConfiguration configuration ;
2022-02-17 15:37:27 +05:00
private readonly IBackgroundWorkerService backgroundWorker ;
private readonly string connectionString ;
2022-02-12 11:28:16 +05:00
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 ;
2022-02-28 14:44:15 +05:00
private const int idStateError = 4 ;
2022-02-12 11:28:16 +05:00
2022-02-17 15:37:27 +05:00
public DrillingProgramService (
IAsbCloudDbContext context ,
IFileService fileService ,
IUserService userService ,
IWellService wellService ,
IConfiguration configuration ,
IBackgroundWorkerService backgroundWorker )
2022-02-12 11:28:16 +05:00
{
this . context = context ;
this . fileService = fileService ;
this . userService = userService ;
2022-02-17 15:37:27 +05:00
this . wellService = wellService ;
2022-02-28 14:44:15 +05:00
this . configuration = configuration ;
2022-02-17 15:37:27 +05:00
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 ;
2022-02-12 11:28:16 +05:00
}
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 ) ;
}
2022-02-17 15:37:27 +05:00
var state = new DrillingProgramStateDto
{
Parts = parts ,
Program = files . FirstOrDefault ( f = > f . IdCategory = = idFileCategoryDrillingProgram )
2022-02-24 09:52:11 +05:00
. Adapt < FileInfoDto > ( ) ,
PermissionToEdit = userService . HasPermission ( idUser , "DrillingProgram.edit" ) ,
2022-02-17 15:37:27 +05:00
} ;
2022-02-12 11:28:16 +05:00
if ( parts . Any ( ) )
{
if ( parts . All ( p = > p . IdState = = idPartStateApproved ) )
{
if ( state . Program is not null )
2022-02-28 14:44:15 +05:00
{
2022-02-12 11:28:16 +05:00
state . IdState = idStateReady ;
2022-02-28 14:44:15 +05:00
}
else
{
var workId = MakeWorkId ( idWell ) ;
if ( drillingProgramCreateErrors . ContainsKey ( workId ) )
{
state . IdState = idStateError ;
state . Error = drillingProgramCreateErrors [ workId ] ;
}
else
state . IdState = idStateCreating ;
}
2022-02-12 11:28:16 +05:00
}
else
state . IdState = idStateApproving ;
}
else
state . IdState = idStateNotInitialized ;
2022-02-17 15:37:27 +05:00
await TryEnqueueMakeProgramAsync ( idWell , state , token ) ;
2022-02-12 11:28:16 +05:00
return state ;
}
2022-02-18 14:16:35 +05:00
public async Task < int > AddFile ( int idWell , int idFileCategory , int idUser , string fileFullName , System . IO . Stream fileStream , CancellationToken token = default )
2022-02-12 11:28:16 +05:00
{
var part = await context . DrillingProgramParts
. Include ( p = > p . RelatedUsers )
2022-02-18 14:16:35 +05:00
. FirstOrDefaultAsync ( p = > p . IdWell = = idWell & & p . IdFileCategory = = idFileCategory , token ) ;
2022-02-12 11:28:16 +05:00
if ( part = = null )
2022-02-18 14:16:35 +05:00
throw new ArgumentInvalidException ( $"DrillingProgramPart id == {idFileCategory} does not exist" , nameof ( idFileCategory ) ) ;
2022-02-12 11:28:16 +05:00
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 ;
}
2022-02-17 15:37:27 +05:00
public async Task < int > AddPartsAsync ( int idWell , IEnumerable < int > idFileCategories , CancellationToken token = default )
2022-02-12 11:28:16 +05:00
{
2022-02-17 15:37:27 +05:00
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
2022-02-12 11:28:16 +05:00
{
IdWell = idWell ,
2022-02-17 15:37:27 +05:00
IdFileCategory = c ,
} ) ;
2022-02-12 11:28:16 +05:00
2022-02-17 15:37:27 +05:00
context . DrillingProgramParts . AddRange ( newParts ) ;
var affected = await context . SaveChangesAsync ( token ) ;
await RemoveDrillingProgramAsync ( idWell , token ) ;
return affected ;
2022-02-12 11:28:16 +05:00
}
2022-02-17 15:37:27 +05:00
public async Task < int > RemovePartsAsync ( int idWell , IEnumerable < int > idFileCategories , CancellationToken token = default )
2022-02-12 11:28:16 +05:00
{
var whereQuery = context . DrillingProgramParts
2022-02-17 15:37:27 +05:00
. Where ( p = > p . IdWell = = idWell & & idFileCategories . Contains ( p . IdFileCategory ) ) ;
2022-02-12 11:28:16 +05:00
context . DrillingProgramParts . RemoveRange ( whereQuery ) ;
await RemoveDrillingProgramAsync ( idWell , token ) ;
return await context . SaveChangesAsync ( token ) ;
}
2022-02-18 14:16:35 +05:00
public async Task < int > AddUserAsync ( int idWell , int idFileCategory , int idUser , int idUserRole , CancellationToken token = default )
2022-02-12 11:28:16 +05:00
{
var user = await userService . GetAsync ( idUser , token ) ;
if ( user is null )
throw new ArgumentInvalidException ( $"User id == {idUser} does not exist" , nameof ( idUser ) ) ;
2022-02-18 14:16:35 +05:00
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 ) ) ;
2022-02-12 11:28:16 +05:00
if ( idUserRole ! = idUserRoleApprover & & idUserRole ! = idUserRolePublisher )
2022-02-18 14:16:35 +05:00
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 ) ;
2022-02-12 11:28:16 +05:00
var newRelation = new RelationUserDrillingProgramPart
{
IdUser = idUser ,
2022-02-18 14:16:35 +05:00
IdDrillingProgramPart = part . Id ,
2022-02-12 11:28:16 +05:00
IdUserRole = idUserRole ,
} ;
context . RelationDrillingProgramPartUsers . Add ( newRelation ) ;
if ( idUserRole = = idUserRoleApprover )
2022-02-18 14:16:35 +05:00
await RemoveDrillingProgramAsync ( part . IdWell , token ) ;
2022-02-12 11:28:16 +05:00
return await context . SaveChangesAsync ( token ) ;
}
2022-02-18 14:16:35 +05:00
public async Task < int > RemoveUserAsync ( int idWell , int idFileCategory , int idUser , int idUserRole , CancellationToken token = default )
2022-02-12 11:28:16 +05:00
{
var whereQuery = context . RelationDrillingProgramPartUsers
2022-02-18 14:16:35 +05:00
. Include ( r = > r . DrillingProgramPart )
2022-02-12 11:28:16 +05:00
. Where ( r = > r . IdUser = = idUser & &
2022-02-18 14:16:35 +05:00
r . IdUserRole = = idUserRole & &
r . DrillingProgramPart . IdWell = = idWell & &
r . DrillingProgramPart . IdFileCategory = = idFileCategory ) ;
2022-02-12 11:28:16 +05:00
context . RelationDrillingProgramPartUsers . RemoveRange ( whereQuery ) ;
2022-02-18 14:16:35 +05:00
2022-02-12 11:28:16 +05:00
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
2022-02-17 15:37:27 +05:00
. Include ( p = > p . RelatedUsers )
2022-02-12 11:28:16 +05:00
. 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 ) ;
2022-02-17 15:37:27 +05:00
if ( fileMarkDto . IdMarkType = = idMarkTypeReject )
2022-02-12 11:28:16 +05:00
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 ;
}
2022-02-17 15:37:27 +05:00
private static DrillingProgramPartDto ConvertPart ( int idUser , List < FileCategory > fileCategories , List < AsbCloudDb . Model . FileInfo > files , DrillingProgramPart partEntity )
2022-02-12 11:28:16 +05:00
{
var part = new DrillingProgramPartDto
{
IdFileCategory = partEntity . IdFileCategory ,
Name = fileCategories . FirstOrDefault ( c = > c . Id = = partEntity . IdFileCategory ) . Name ,
Approvers = partEntity . RelatedUsers
2022-02-17 15:37:27 +05:00
. Where ( r = > r . IdUserRole = = idUserRoleApprover )
. Select ( r = > r . User . Adapt < UserDto > ( ) ) ,
2022-02-12 11:28:16 +05:00
Publishers = partEntity . RelatedUsers
2022-02-17 15:37:27 +05:00
. Where ( r = > r . IdUserRole = = idUserRolePublisher )
. Select ( r = > r . User . Adapt < UserDto > ( ) ) ,
2022-02-12 11:28:16 +05:00
PermissionToApprove = partEntity . RelatedUsers
2022-02-17 15:37:27 +05:00
. Any ( r = > r . IdUserRole = = idUserRoleApprover & & r . IdUser = = idUser ) ,
2022-02-12 11:28:16 +05:00
PermissionToUpload = partEntity . RelatedUsers
2022-02-17 15:37:27 +05:00
. Any ( r = > r . IdUserRole = = idUserRolePublisher & & r . IdUser = = idUser ) ,
2022-02-12 11:28:16 +05:00
} ;
var fileEntity = files . LastOrDefault ( f = > f . IdCategory = = partEntity . IdFileCategory ) ;
if ( fileEntity is not null )
{
part . File = fileEntity . Adapt < FileInfoDto > ( ) ;
2022-02-21 12:04:26 +05:00
if ( part . File . FileMarks is not null )
part . File . FileMarks = part . File . FileMarks . Where ( m = > ! m . IsDeleted ) ;
2022-02-17 15:37:27 +05:00
part . IdState = idPartStateApproving ;
var marks = fileEntity . FileMarks . Where ( m = > ! m . IsDeleted ) ;
var hasReject = marks . Any ( m = > m . IdMarkType = = idMarkTypeReject ) ;
if ( ! hasReject )
2022-02-12 11:28:16 +05:00
{
2022-02-17 15:37:27 +05:00
var allAproved = part . Approvers . All ( a = > marks . Any ( m = > m . IdUser = = a . Id & & m . IdMarkType = = idMarkTypeApprove ) ) ;
if ( allAproved )
part . IdState = idPartStateApproved ;
2022-02-12 11:28:16 +05:00
}
}
else
part . IdState = idPartStateNoFile ;
return part ;
}
2022-02-17 15:37:27 +05:00
private async Task TryEnqueueMakeProgramAsync ( int idWell , DrillingProgramStateDto state , CancellationToken token )
2022-02-12 11:28:16 +05:00
{
2022-02-17 15:37:27 +05:00
if ( state . IdState = = idStateCreating )
2022-02-12 11:28:16 +05:00
{
2022-02-17 15:37:27 +05:00
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 ) ;
2022-02-28 14:44:15 +05:00
var mailService = new EmailService ( backgroundWorker , configuration ) ;
2022-02-17 15:37:27 +05:00
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 ) ;
}
2022-02-28 14:44:15 +05:00
Task funcOnErrorProgramMake ( string workId , Exception exception , CancellationToken token ) {
var message = $"Н е удалось сформировать программу бурения по скважине {well?.Caption}" ;
drillingProgramCreateErrors [ workId ] = new ( ) {
Message = message ,
Exception = exception . Message ,
} ;
return Task . CompletedTask ;
}
backgroundWorker . Enqueue ( workId , funcProgramMake , funcOnErrorProgramMake ) ;
2022-02-17 15:37:27 +05:00
}
2022-02-12 11:28:16 +05:00
}
}
2022-02-28 14:44:15 +05:00
public void ClearError ( int idWell )
{
var workId = MakeWorkId ( idWell ) ;
drillingProgramCreateErrors . Remove ( workId ) ;
}
2022-02-12 11:28:16 +05:00
private async Task < int > RemoveDrillingProgramAsync ( int idWell , CancellationToken token )
{
2022-02-17 15:37:27 +05:00
var workId = MakeWorkId ( idWell ) ;
backgroundWorker . TryRemove ( workId ) ;
2022-02-12 11:28:16 +05:00
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 ;
}
2022-02-17 15:37:27 +05:00
private static string MakeWorkId ( int idWell )
= > $"Make drilling program for wellId {idWell}" ;
2022-02-12 11:28:16 +05:00
}
}