using AsbCloudApp.Data.ProcessMapPlan; using AsbCloudApp.Exceptions; using AsbCloudApp.Repositories; using AsbCloudApp.Requests; using AsbCloudApp.Services; using AsbCloudDb.Model; using AsbCloudDb.Model.ProcessMapPlan; using AsbCloudDb.Model.WellSections; using Mapster; using Microsoft.EntityFrameworkCore; using Npgsql; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace AsbCloudInfrastructure.Repository; public class ProcessMapPlanBaseRepository : IProcessMapPlanBaseRepository where TDto : ProcessMapPlanBaseDto where TEntity : ProcessMapPlanBase { private readonly IAsbCloudDbContext context; private readonly IWellService wellService; public ProcessMapPlanBaseRepository(IAsbCloudDbContext context, IWellService wellService) { this.context = context; this.wellService = wellService; } public async Task InsertRange(int idUser, IEnumerable dtos, CancellationToken token) { var result = 0; if (dtos.Any()) { var entities = dtos.Select(Convert); var creation = DateTimeOffset.UtcNow; var dbSet = context.Set(); foreach (var entity in entities) { entity.Id = default; entity.IdAuthor = idUser; entity.Creation = creation; entity.IdState = ChangeLogAbstract.IdStateActual; entity.IdEditor = null; entity.Editor = null; entity.IdPrevious = null; entity.Obsolete = null; dbSet.Add(entity); } result += await SaveChangesWithExceptionHandling(token); } return result; } public async Task ClearAndInsertRange(int idUser, int idWell, IEnumerable dtos, CancellationToken token) { if (dtos.Any(d => d.IdWell != idWell)) throw new ArgumentInvalidException(nameof(dtos), $"Все записи должны относиться к скважине idWell = {idWell}"); using var transaction = context.Database.BeginTransaction(); var result = 0; var dbSet = context.Set(); var entitiesToMarkDeleted = dbSet .Where(e => e.IdWell == idWell) .Where(e => e.Obsolete == null); var obsolete = DateTimeOffset.UtcNow; foreach (var entity in entitiesToMarkDeleted) { entity.IdState = ChangeLogAbstract.IdClearedOnImport; entity.Obsolete = obsolete; entity.IdEditor = idUser; } result += await SaveChangesWithExceptionHandling(token); result += await InsertRange(idUser, dtos, token); await transaction.CommitAsync(token); return result; } public async Task DeleteRange(int idUser, IEnumerable ids, CancellationToken token) { var dbSet = context.Set(); var entitiesToMarkDeleted = dbSet .Where(e => ids.Contains(e.Id)) .Where(e => e.Obsolete == null); var obsolete = DateTimeOffset.UtcNow; foreach (var entity in entitiesToMarkDeleted) { entity.IdState = ChangeLogAbstract.IdStateDeleted; entity.Obsolete = obsolete; entity.IdEditor = idUser; } var result = await SaveChangesWithExceptionHandling(token); return result; } public async Task> Get(int idWell, ProcessMapPlanBaseRequest request, CancellationToken token) { var timezone = wellService.GetTimezone(idWell); var offset = TimeSpan.FromHours(timezone.Hours); var query = context .Set() .Include(e => e.Author) .Include(e => e.Editor) .Where(e => e.IdWell == idWell); if(request.IdWellSectionType.HasValue) query = query.Where(e => e.IdWellSectionType == request.IdWellSectionType); if (request.UpdateFrom.HasValue) { var from = request.UpdateFrom.Value.ToUniversalTime(); query = query.Where(e => e.Creation >= from || e.Obsolete >= from); } if (request.Moment.HasValue) { var moment = request.Moment.Value.ToUniversalTime(); query = query .Where(e => e.Creation <= moment) .Where(e => e.Obsolete == null || e.Obsolete >= moment); } var entities = await query.ToArrayAsync(token); var dtos = entities.Select(e => Convert(e, offset)); return dtos; } public async Task> GetChangeLog(int idWell, DateOnly? date, CancellationToken token) { var query = context .Set() .Include(e => e.Author) .Include(e => e.Editor) .Where(e => e.IdWell == idWell); var timezone = wellService.GetTimezone(idWell); var offset = TimeSpan.FromHours(timezone.Hours); if (date.HasValue) { var min = new DateTimeOffset(date.Value.Year, date.Value.Month, date.Value.Day, 0, 0, 0, offset).ToUniversalTime(); var max = min.AddDays(1); var createdQuery = query.Where(e => e.Creation >= min && e.Creation <= max); var editedQuery = query.Where(e => e.Obsolete != null && e.Obsolete >= min && e.Obsolete <= max); query = createdQuery.Union(editedQuery); } var entities = await query.ToListAsync(token); var dtos = entities.Select(e => Convert(e, offset)); return dtos; } public async Task> GetDatesChange(int idWell, CancellationToken token) { var wellEntitiesQuery = context .Set() .Where(e => e.IdWell == idWell); var datesCreateQuery = wellEntitiesQuery .Select(e => e.Creation) .Distinct(); var datesCreate = await datesCreateQuery.ToArrayAsync(token); var datesUpdateQuery = wellEntitiesQuery .Where(e => e.Obsolete != null) .Select(e => e.Obsolete!.Value) .Distinct(); var datesUpdate = await datesUpdateQuery.ToArrayAsync(token); var timezone = wellService.GetTimezone(idWell); var offset = TimeSpan.FromHours(timezone.Hours); var dates = Enumerable.Concat( datesCreate, datesUpdate); dates = dates.Select(date => date.ToOffset(offset)); var datesOnly = dates .Select(d => new DateOnly(d.Year, d.Month, d.Day)) .Distinct(); return datesOnly; } public async Task UpdateRange(int idUser, IEnumerable dtos, CancellationToken token) { if (dtos.Any(d => d.Id == 0)) throw new ArgumentInvalidException(nameof(dtos), "Отредактированные значения должны иметь id больше 0"); if (!dtos.Any()) return 0; using var transaction = context.Database.BeginTransaction(); var result = 0; var ids = dtos.Select(d => d.Id); var dbSet = context.Set(); var entitiesToDelete = dbSet .Where(e => ids.Contains(e.Id)); var updateTime = DateTimeOffset.UtcNow; foreach (var entity in entitiesToDelete) { if(entity.Obsolete is not null) throw new ArgumentInvalidException(nameof(dtos), "Недопустимо редактировать устаревшие записи"); entity.IdState = ChangeLogAbstract.IdStateReplaced; entity.Obsolete = updateTime; entity.IdEditor = idUser; } result += await context.SaveChangesAsync(token); var entitiesNew = dtos.Select(Convert); foreach (var entity in entitiesNew) { entity.IdPrevious = entity.Id; entity.Id = default; entity.Creation = updateTime; entity.IdAuthor = idUser; entity.Obsolete = null; entity.IdEditor = null; entity.IdState = ChangeLogAbstract.IdStateActual; dbSet.Add(entity); } result += await SaveChangesWithExceptionHandling(token); await transaction.CommitAsync(token); return result; } protected virtual TEntity Convert(TDto dto) { var entity = dto.Adapt(); entity.Creation = entity.Creation.ToUniversalTime(); if(entity.Obsolete.HasValue) entity.Obsolete = entity.Obsolete.Value.ToUniversalTime(); return entity; } protected virtual TDto Convert(TEntity entity, TimeSpan offset) { var dto = entity.Adapt(); dto.Creation = entity.Creation.ToOffset(offset); if (entity.Obsolete.HasValue) dto.Obsolete = entity.Obsolete.Value.ToOffset(offset); return dto; } private async Task SaveChangesWithExceptionHandling(CancellationToken token) { try { var result = await context.SaveChangesAsync(token); return result; } catch (DbUpdateException ex) { if (ex.InnerException is PostgresException pgException) TryConvertPostgresExceptionToValidateException(pgException); throw; } } private static void TryConvertPostgresExceptionToValidateException(PostgresException pgException) { if (pgException.SqlState == PostgresErrorCodes.ForeignKeyViolation) throw new ArgumentInvalidException("dtos", pgException.Message + "\r\n" + pgException.Detail); } }