using AsbCloudApp.Data; using AsbCloudApp.Exceptions; using AsbCloudApp.Services; using AsbCloudDb.Model; using AsbCloudInfrastructure.EfCache; using AsbCloudInfrastructure.Repository; using Mapster; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services { public class WellService : CrudCacheServiceBase, IWellService { private const string relationCompaniesWellsCacheTag = "RelationCompaniesWells"; private static readonly TimeSpan relationCompaniesWellsCacheObsolence = TimeSpan.FromMinutes(15); private readonly ITelemetryService telemetryService; private readonly ICrudService companyTypesService; private readonly ITimezoneService timezoneService; private readonly IWellOperationService wellOperationService; public ITelemetryService TelemetryService => telemetryService; private static IQueryable MakeQueryWell(DbSet dbSet) => dbSet .Include(w => w.Cluster) .ThenInclude(c => c.Deposit) .Include(w => w.Telemetry) .Include(w => w.WellType) .Include(w => w.RelationCompaniesWells) .ThenInclude(r => r.Company); public WellService(IAsbCloudDbContext db, IMemoryCache memoryCache, ITelemetryService telemetryService, ITimezoneService timezoneService) : base(db, memoryCache, MakeQueryWell) { this.telemetryService = telemetryService; this.timezoneService = timezoneService; this.wellOperationService = new WellOperationService.WellOperationService(db, memoryCache, this); companyTypesService = new CrudCacheServiceBase(dbContext, memoryCache); } private IEnumerable GetCacheRelationCompanyWell() => dbContext.RelationCompaniesWells .Include(r => r.Company) .Include(r => r.Well) .FromCache(relationCompaniesWellsCacheTag, relationCompaniesWellsCacheObsolence); private Task> GetCacheRelationCompanyWellAsync(CancellationToken token) => dbContext.RelationCompaniesWells .Include(r => r.Company) .Include(r => r.Well) .FromCacheAsync(relationCompaniesWellsCacheTag, relationCompaniesWellsCacheObsolence, token); private void DropCacheRelationCompanyWell() => dbContext.RelationCompaniesWells.DropCache(relationCompaniesWellsCacheTag); public DateTimeOffset GetLastTelemetryDate(int idWell) { var well = GetOrDefault(idWell); if (well?.IdTelemetry is null) return DateTimeOffset.MinValue; var lastTelemetryDate = telemetryService.GetLastTelemetryDate((int)well.IdTelemetry); return lastTelemetryDate; } public async Task> GetWellsByCompanyAsync(int idCompany, CancellationToken token) { var relationsCache = await GetCacheRelationCompanyWellAsync(token); var wellsIds = relationsCache .Where(r => r.IdCompany == idCompany) .Select(r => r.IdWell); var wellsDtos = (await GetCacheAsync(token)) .Where(w => wellsIds.Contains(w.Id)); return wellsDtos.ToList(); } public override async Task InsertAsync(WellDto dto, CancellationToken token = default) { if (dto.IdWellType is < 1 or > 2) throw new ArgumentInvalidException("Тип скважины указан неправильно.", nameof(dto)); if (dto.IdState is < 0 or > 2) throw new ArgumentInvalidException("Текущее состояние работы скважины указано неправильно.", nameof(dto)); if (dto.Id != 0 && (await GetCacheAsync(token)).Any(w => w.Id == dto.Id)) throw new ArgumentInvalidException($"Нельзя повторно добавить скважину с id: {dto.Id}", nameof(dto)); var entity = Convert(dto); var result = await base.InsertAsync(dto, token); if (dto.Companies.Any()) { var newRelations = dto.Companies.Select(c => new RelationCompanyWell { IdWell = result, IdCompany = c.Id }); dbContext.RelationCompaniesWells.AddRange(newRelations); await dbContext.SaveChangesAsync(token); DropCacheRelationCompanyWell(); } return result; } public override Task InsertRangeAsync(IEnumerable dtos, CancellationToken token) { throw new NotImplementedException(); } public override async Task UpdateAsync(WellDto dto, CancellationToken token = default) { if (dto.IdWellType is < 1 or > 2) throw new ArgumentInvalidException("Тип скважины указан неправильно.", nameof(dto)); if (dto.IdState is < 0 or > 2) throw new ArgumentInvalidException("Текущее состояние работы скважины указано неправильно.", nameof(dto)); var oldRelations = (await GetCacheRelationCompanyWellAsync(token)) .Where(r => r.IdWell == dto.Id); if (dto.Companies.Count() != oldRelations.Count() || dto.Companies.Any(c => !oldRelations.Any(oldC => oldC.IdCompany == c.Id))) { dbContext.RelationCompaniesWells .RemoveRange(dbContext.RelationCompaniesWells .Where(r => r.IdWell == dto.Id)); var newRelations = dto.Companies.Select(c => new RelationCompanyWell { IdWell = dto.Id, IdCompany = c.Id }); dbContext.RelationCompaniesWells.AddRange(newRelations); } var result = await base.UpdateAsync(dto, token); return result; } public bool IsCompanyInvolvedInWell(int idCompany, int idWell) => GetCacheRelationCompanyWell() .Any(r => r.IdWell == idWell && r.IdCompany == idCompany); public async Task IsCompanyInvolvedInWellAsync(int idCompany, int idWell, CancellationToken token) => (await GetCacheRelationCompanyWellAsync(token)) .Any(r => r.IdWell == idWell && r.IdCompany == idCompany); public async Task GetWellCaptionByIdAsync(int idWell, CancellationToken token) { var entity = await GetOrDefaultAsync(idWell, token).ConfigureAwait(false); var dto = Convert(entity); return dto.Caption; } public async Task> GetCompaniesAsync(int idWell, CancellationToken token) { var relations = (await GetCacheRelationCompanyWellAsync(token)) .Where(r => r.IdWell == idWell); var dtos = relations.Select(r => Convert(r.Company)); return dtos; } public string GetStateText(int state) { return state switch { 1 => "В работе", 2 => "Завершена", _ => "Неизвестно", }; } public async Task> GetClusterWellsIdsAsync(int idWell, CancellationToken token) { var well = await GetOrDefaultAsync(idWell, token); if (well is null) return null; var cache = await GetCacheAsync(token); var clusterWellsIds = cache .Where((w) => w.IdCluster == well.IdCluster) .Select(w => w.Id); return clusterWellsIds; } protected override Well Convert(WellDto dto) { var entity = dto.Adapt(); entity.IdTelemetry = entity.IdTelemetry ?? dto.IdTelemetry ?? dto.Telemetry?.Id; if (dto.Timezone is null) entity.Timezone = GetTimezone(dto.Id) .Adapt(); return entity; } protected override WellDto Convert(Well entity) { if (entity is null) return null; var dto = base.Convert(entity); if (entity.Timezone is null) dto.Timezone = GetTimezone(entity.Id); dto.StartDate = wellOperationService.FirstOperationDate(entity.Id)?.ToRemoteDateTime(dto.Timezone.Hours); dto.WellType = entity.WellType?.Caption; dto.Cluster = entity.Cluster?.Caption; dto.Deposit = entity.Cluster?.Deposit?.Caption; if (entity.IdTelemetry is not null) dto.LastTelemetryDate = telemetryService.GetLastTelemetryDate((int)entity.IdTelemetry); dto.Companies = entity.RelationCompaniesWells .Select(r => Convert(r.Company)) .ToList(); return dto; } private CompanyDto Convert(Company entity) { var dto = entity.Adapt(); dto.CompanyTypeCaption = entity.CompanyType?.Caption ?? companyTypesService.GetOrDefault(entity.IdCompanyType).Caption; return dto; } public async Task EnshureTimezonesIsSetAsync(CancellationToken token) { var cache = await GetCacheAsync(token); if (!cache.Any(w => w.Timezone is null)) return; var defaultTimeZone = new SimpleTimezone { Hours = 5, IsOverride = false, TimezoneId = "Assumed", }; await dbSet.Where(w => w.Timezone == null) .ForEachAsync(w => w.Timezone = defaultTimeZone, token); await dbContext.SaveChangesAsync(token); DropCache(); } public SimpleTimezoneDto GetTimezone(int idWell) { var well = GetOrDefault(idWell); if (well == null) throw new ArgumentInvalidException($"idWell: {idWell} does not exist.", nameof(idWell)); return GetTimezone(well); } private SimpleTimezoneDto GetTimezone(WellDto wellDto) { if (wellDto.Timezone is not null) return wellDto.Timezone; if (wellDto.Telemetry is not null) { var timezone = telemetryService.GetTimezone(wellDto.Telemetry.Id); if (timezone is not null) return timezone; } var well = GetQuery().FirstOrDefault(w => w.Id == wellDto.Id); var point = GetCoordinates(well); if (point is not null) { if (point.Timezone is not null) { return point.Timezone.Adapt(); } if (point.Latitude is not null & point.Longitude is not null) { var timezone = timezoneService.GetByCoordinates((double)point.Latitude, (double)point.Longitude); if (timezone is not null) { return timezone; } } } throw new Exception($"Can't find timezone for well {wellDto.Caption} id: {wellDto.Id}"); } private static AsbCloudDb.Model.IMapPoint GetCoordinates(Well well) { if (well is null) throw new ArgumentNullException(nameof(well)); if (well.Latitude is not null & well.Longitude is not null) return well; if (well.IdCluster is null) throw new Exception($"Can't find coordinates of well {well.Caption} id: {well.Id}"); var cluster = well.Cluster; if (cluster.Latitude is not null & cluster.Longitude is not null) return cluster; if (cluster.Deposit is null) throw new Exception($"Can't find coordinates of well by cluster {cluster.Caption} id: {cluster.Id}"); var deposit = cluster.Deposit; if (deposit.Latitude is not null & deposit.Longitude is not null) return deposit; throw new Exception($"Can't find coordinates of well by deposit {deposit.Caption} id: {deposit.Id}"); } public DatesRangeDto GetDatesRange(int idWell) { var well = GetOrDefault(idWell); if (well is null) throw new Exception($"Well id: {idWell} does not exist."); if (well.IdTelemetry is null) throw new Exception($"Well id: {idWell} does not contain telemetry."); return telemetryService.GetDatesRange((int)well.IdTelemetry); } } }