diff --git a/AsbCloudApp/Repositories/IChangeLogRepository.cs b/AsbCloudApp/Repositories/IChangeLogRepository.cs
index 9a5ab8c9..44a6faed 100644
--- a/AsbCloudApp/Repositories/IChangeLogRepository.cs
+++ b/AsbCloudApp/Repositories/IChangeLogRepository.cs
@@ -1,15 +1,18 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
+using AsbCloudApp.Requests;
namespace AsbCloudApp.Repositories;
///
/// Репозиторий для записей с историей
///
-public interface IChangeLogRepository
- where T : ChangeLogAbstract
+public interface IChangeLogRepository
+ where TDto : ChangeLogAbstract
+ where TRequest : ChangeLogBaseRequest
{
///
/// Добавление записей
@@ -18,7 +21,7 @@ public interface IChangeLogRepository
///
///
///
- Task InsertRange(int idUser, IEnumerable dtos, CancellationToken token);
+ Task InsertRange(int idUser, IEnumerable dtos, CancellationToken token);
///
/// Редактирование записей
@@ -27,15 +30,69 @@ public interface IChangeLogRepository
///
///
///
- Task UpdateRange(int idUser, IEnumerable dtos, CancellationToken token);
+ Task UpdateRange(int idUser, IEnumerable dtos, CancellationToken token);
+
+ ///
+ /// Добавляет Dto у которых id == 0, изменяет dto у которых id != 0
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task UpdateOrInsertRange(int idUser, IEnumerable dtos, CancellationToken token);
+
+ ///
+ /// Добавление записей с удалением старых (для импорта)
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task Clear(int idUser, TRequest request, CancellationToken token);
+
+ ///
+ /// Очистить и добавить новые
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task ClearAndInsertRange(int idUser, TRequest request, IEnumerable dtos, CancellationToken token);
+
+ ///
+ /// Удаление записей
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task DeleteRange(int idUser, IEnumerable ids, CancellationToken token);
+
+ ///
+ /// Получение дат изменений записей
+ ///
+ ///
+ ///
+ ///
+ Task> GetDatesChange(TRequest request, CancellationToken token);
+
+ ///
+ /// Получение журнала изменений
+ ///
+ ///
+ /// Фильтр по дате. Если null - вернет все
+ ///
+ ///
+ Task> GetChangeLog(TRequest request, DateOnly? date, CancellationToken token);
+
+ ///
+ /// Получение записей по параметрам
+ ///
+ ///
+ ///
+ ///
+ Task> Get(TRequest request, CancellationToken token);
- ///
- /// Удаление записей
- ///
- ///
- ///
- ///
- ///
- Task DeleteRange(int idUser, IEnumerable ids, CancellationToken token);
}
diff --git a/AsbCloudApp/Repositories/IProcessMapPlanBaseRepository.cs b/AsbCloudApp/Repositories/IProcessMapPlanBaseRepository.cs
deleted file mode 100644
index 7524002a..00000000
--- a/AsbCloudApp/Repositories/IProcessMapPlanBaseRepository.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-using AsbCloudApp.Data.ProcessMapPlan;
-using AsbCloudApp.Requests;
-
-namespace AsbCloudApp.Repositories;
-
-///
-/// Общий интерфейс для РТК план с учетом истории изменений
-///
-///
-public interface IProcessMapPlanBaseRepository: IChangeLogRepository
- where T: ProcessMapPlanBaseDto
-{
- ///
- /// Добавление записей с удалением старых (для импорта)
- ///
- ///
- ///
- ///
- ///
- ///
- Task ClearAndInsertRange(int idUser, int idWell, IEnumerable dtos, CancellationToken token);
-
- ///
- /// Получение дат изменений записей
- ///
- ///
- ///
- ///
- Task> GetDatesChange(int idWell, CancellationToken token);
-
- ///
- /// Получение журнала изменений
- ///
- ///
- ///
- ///
- ///
- Task> GetChangeLog(int idWell, DateOnly? date, CancellationToken token);
-
- ///
- /// Получение записей по параметрам
- ///
- ///
- ///
- ///
- ///
- Task> Get(int idWell, ProcessMapPlanBaseRequest request, CancellationToken token);
-}
\ No newline at end of file
diff --git a/AsbCloudApp/Repositories/IProcessMapPlanRepository.cs b/AsbCloudApp/Repositories/IProcessMapPlanRepository.cs
index 1828b3ed..7e41cb58 100644
--- a/AsbCloudApp/Repositories/IProcessMapPlanRepository.cs
+++ b/AsbCloudApp/Repositories/IProcessMapPlanRepository.cs
@@ -4,12 +4,14 @@ using System.Threading;
using AsbCloudApp.Data.ProcessMaps;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
+using System;
namespace AsbCloudApp.Repositories;
///
/// РТК план
///
+[Obsolete]
public interface IProcessMapPlanRepository : IRepositoryWellRelated
where TDto : ProcessMapPlanBaseDto
{
diff --git a/AsbCloudApp/Requests/ProcessMapPlanBaseRequest.cs b/AsbCloudApp/Requests/ProcessMapPlanBaseRequest.cs
index 74fc0ec4..006c26e5 100644
--- a/AsbCloudApp/Requests/ProcessMapPlanBaseRequest.cs
+++ b/AsbCloudApp/Requests/ProcessMapPlanBaseRequest.cs
@@ -18,4 +18,37 @@ public class ProcessMapPlanBaseRequest: ChangeLogBaseRequest
/// Вернуть данные, которые поменялись с указанной даты
///
public DateTimeOffset? UpdateFrom { get; set; }
+}
+
+///
+/// Запрос для получения РТК план по скважине
+///
+public class ProcessMapPlanBaseRequestWithWell: ProcessMapPlanBaseRequest
+{
+ ///
+ /// Запрос для получения РТК план по скважине
+ ///
+ ///
+ public ProcessMapPlanBaseRequestWithWell(int idWell)
+ {
+ IdWell = idWell;
+ }
+
+ ///
+ /// Запрос для получения РТК план по скважине
+ ///
+ ///
+ ///
+ public ProcessMapPlanBaseRequestWithWell(ProcessMapPlanBaseRequest request, int idWell)
+ {
+ IdWell=idWell;
+ IdWellSectionType=request.IdWellSectionType;
+ UpdateFrom = request.UpdateFrom;
+ Moment = request.Moment;
+ }
+
+ ///
+ /// Id скважины
+ ///
+ public int IdWell { get; set; }
}
\ No newline at end of file
diff --git a/AsbCloudDb/Model/ChangeLogAbstract.cs b/AsbCloudDb/Model/ChangeLogAbstract.cs
index f4065ea6..d9bc74e0 100644
--- a/AsbCloudDb/Model/ChangeLogAbstract.cs
+++ b/AsbCloudDb/Model/ChangeLogAbstract.cs
@@ -28,7 +28,7 @@ public abstract class ChangeLogAbstract
///
/// Очищено при импорте
///
- public const int IdClearedOnImport = 3;
+ public const int IdCleared = 3;
///
/// Ид записи
diff --git a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj
index c607d523..02400268 100644
--- a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj
+++ b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj
@@ -55,6 +55,7 @@
+
diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs
index 0e88a972..913fdfe4 100644
--- a/AsbCloudInfrastructure/DependencyInjection.cs
+++ b/AsbCloudInfrastructure/DependencyInjection.cs
@@ -45,6 +45,7 @@ using AsbCloudDb.Model.DailyReports.Blocks.TimeBalance;
using AsbCloudDb.Model.WellSections;
using AsbCloudInfrastructure.Services.ProcessMaps;
using AsbCloudApp.Data.ProcessMapPlan;
+using AsbCloudApp.Requests;
namespace AsbCloudInfrastructure
{
@@ -195,10 +196,10 @@ namespace AsbCloudInfrastructure
services.AddTransient();
services.AddTransient();
services.AddTransient();
- services.AddTransient();
+ services.AddScoped();
services.AddTransient();
services.AddTransient();
- services.AddTransient();
+ services.AddScoped();
services.AddTransient();
services.AddTransient();
services.AddTransient();
@@ -222,7 +223,9 @@ namespace AsbCloudInfrastructure
services.AddTransient();
services.AddTransient();
- services.AddTransient, ProcessMapPlanBaseRepository>();
+ services.AddTransient<
+ IChangeLogRepository,
+ ProcessMapPlanBaseRepository>();
services.AddTransient();
diff --git a/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs b/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs
new file mode 100644
index 00000000..64208a8e
--- /dev/null
+++ b/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs
@@ -0,0 +1,288 @@
+using AsbCloudApp.Exceptions;
+using AsbCloudApp.Repositories;
+using AsbCloudApp.Requests;
+using AsbCloudDb.Model;
+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 abstract class ChangeLogRepositoryAbstract : IChangeLogRepository
+ where TDto : AsbCloudApp.Data.ChangeLogAbstract
+ where TEntity : ChangeLogAbstract
+ where TRequest : ChangeLogBaseRequest
+{
+ protected readonly IAsbCloudDbContext context;
+
+ public ChangeLogRepositoryAbstract(IAsbCloudDbContext context)
+ {
+ this.context = context;
+ }
+
+ 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.IdPrevious = null;
+ entity.Obsolete = null;
+ dbSet.Add(entity);
+ }
+
+ result += await SaveChangesWithExceptionHandling(token);
+ }
+ return result;
+ }
+
+ 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;
+
+ var updateTime = DateTimeOffset.UtcNow;
+
+ var ids = dtos.Select(d => d.Id);
+
+ using var transaction = context.Database.BeginTransaction();
+ var result = 0;
+ var dbSet = context
+ .Set();
+
+ var entitiesToDelete = await dbSet
+ .Where(e => e.Obsolete == null)
+ .Where(e => ids.Contains(e.Id))
+ .ToArrayAsync(token);
+
+ var entitiesNotFound = dtos.Where(d => !entitiesToDelete.Any(e => e.Id == d.Id));
+ if (entitiesNotFound.Any())
+ {
+ var notFoundIds = entitiesNotFound.Select(e => e.Id);
+ var stringnotFoundIds = string.Join(", ", notFoundIds);
+ throw new ArgumentInvalidException(nameof(dtos), $"записи с id:[{stringnotFoundIds}] не найдены, или не актуальны.");
+ }
+
+ foreach (var entity in entitiesToDelete)
+ {
+ 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;
+ }
+
+ public async Task UpdateOrInsertRange(int idUser, IEnumerable dtos, CancellationToken token)
+ {
+ var itemsToInsert = dtos.Where(e => e.Id == 0);
+ var itemsToUpdate = dtos.Where(e => e.Id != 0);
+
+ var result = 0;
+ if (itemsToInsert.Any())
+ result += await InsertRange(idUser, itemsToInsert, token);
+
+ if (itemsToUpdate.Any())
+ result += await UpdateRange(idUser, itemsToUpdate, token);
+
+ return result;
+ }
+
+ public async Task Clear(int idUser, TRequest request, CancellationToken token)
+ {
+ var updateTime = DateTimeOffset.UtcNow;
+ var query = BuildQuery(request);
+ query = query.Where(e => e.Obsolete == null);
+
+ var entitiesToDelete = await query.ToArrayAsync(token);
+
+ foreach (var entity in entitiesToDelete)
+ {
+ entity.IdState = ChangeLogAbstract.IdCleared;
+ entity.Obsolete = updateTime;
+ entity.IdEditor = idUser;
+ }
+
+ var result = await SaveChangesWithExceptionHandling(token);
+ return result;
+ }
+
+ public async Task ClearAndInsertRange(int idUser, TRequest request, IEnumerable dtos, CancellationToken token)
+ {
+ var result = 0;
+ var transaction = await context.Database.BeginTransactionAsync(token);
+ result += await Clear(idUser, request, token);
+ result += await InsertRange(idUser, dtos, token);
+ await transaction.CommitAsync(token);
+ return result;
+ }
+
+ public async Task DeleteRange(int idUser, IEnumerable ids, CancellationToken token)
+ {
+ var updateTime = DateTimeOffset.UtcNow;
+ var query = context.Set()
+ .Where(e => ids.Contains(e.Id))
+ .Where(e => e.Obsolete == null);
+
+ var entitiesToDelete = await query.ToArrayAsync(token);
+
+ foreach (var entity in entitiesToDelete)
+ {
+ entity.IdState = ChangeLogAbstract.IdStateDeleted;
+ entity.Obsolete = updateTime;
+ entity.IdEditor = idUser;
+ }
+
+ var result = await SaveChangesWithExceptionHandling(token);
+ return result;
+ }
+
+ public async Task> GetDatesChange(TRequest request, CancellationToken token)
+ {
+ var query = BuildQuery(request);
+
+ var datesCreateQuery = query
+ .Select(e => e.Creation)
+ .Distinct();
+
+ var datesCreate = await datesCreateQuery.ToArrayAsync(token);
+
+ var datesUpdateQuery = query
+ .Where(e => e.Obsolete != null)
+ .Select(e => e.Obsolete!.Value)
+ .Distinct();
+
+ var datesUpdate = await datesUpdateQuery.ToArrayAsync(token);
+
+ TimeSpan offset = GetTimezoneOffset(request);
+
+ 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()
+ .OrderBy(d => d);
+
+ return datesOnly;
+ }
+
+ public async Task> GetChangeLog(TRequest request, DateOnly? date, CancellationToken token)
+ {
+ var query = BuildQuery(request);
+ TimeSpan offset = GetTimezoneOffset(request);
+
+ 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
+ .OrderBy(e => e.Creation)
+ .ThenBy(e => e.Obsolete)
+ .ThenBy(e => e.Id)
+ .ToListAsync(token);
+ var dtos = entities.Select(e => Convert(e, offset));
+
+ return dtos;
+ }
+
+ public async Task> Get(TRequest request, CancellationToken token)
+ {
+ var query = BuildQuery(request);
+ var entities = await query
+ .OrderBy(e => e.Creation)
+ .ThenBy(e => e.Obsolete)
+ .ThenBy(e => e.Id)
+ .ToArrayAsync(token);
+
+ TimeSpan offset = GetTimezoneOffset(request);
+ var dtos = entities.Select(e => Convert(e, offset));
+ return dtos;
+ }
+
+ protected abstract TimeSpan GetTimezoneOffset(TRequest request);
+
+ protected abstract IQueryable BuildQuery(TRequest request);
+
+ 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);
+ }
+}
diff --git a/AsbCloudInfrastructure/Repository/ProcessMapPlanBaseRepository.cs b/AsbCloudInfrastructure/Repository/ProcessMapPlanBaseRepository.cs
index 11b4d0fd..0133b527 100644
--- a/AsbCloudInfrastructure/Repository/ProcessMapPlanBaseRepository.cs
+++ b/AsbCloudInfrastructure/Repository/ProcessMapPlanBaseRepository.cs
@@ -1,115 +1,36 @@
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
+public class ProcessMapPlanBaseRepository : ChangeLogRepositoryAbstract
where TDto : ProcessMapPlanBaseDto
where TEntity : ProcessMapPlanBase
{
- private readonly IAsbCloudDbContext context;
private readonly IWellService wellService;
- public ProcessMapPlanBaseRepository(IAsbCloudDbContext context, IWellService wellService)
+ public ProcessMapPlanBaseRepository(IAsbCloudDbContext context, IWellService wellService)
+ : base(context)
{
- this.context = context;
this.wellService = wellService;
}
- public async Task InsertRange(int idUser, IEnumerable dtos, CancellationToken token)
+ protected override IQueryable BuildQuery(ProcessMapPlanBaseRequestWithWell request)
{
- 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);
+ .Include(e => e.Well)
+ .Where(e => e.IdWell == request.IdWell);
- if(request.IdWellSectionType.HasValue)
+ if (request.IdWellSectionType.HasValue)
query = query.Where(e => e.IdWellSectionType == request.IdWellSectionType);
if (request.UpdateFrom.HasValue)
@@ -126,156 +47,13 @@ public class ProcessMapPlanBaseRepository : IProcessMapPlanBaseRe
.Where(e => e.Obsolete == null || e.Obsolete >= moment);
}
- var entities = await query.ToArrayAsync(token);
- var dtos = entities.Select(e => Convert(e, offset));
- return dtos;
+ return query;
}
- public async Task> GetChangeLog(int idWell, DateOnly? date, CancellationToken token)
+ protected override TimeSpan GetTimezoneOffset(ProcessMapPlanBaseRequestWithWell request)
{
- var query = context
- .Set()
- .Include(e => e.Author)
- .Include(e => e.Editor)
- .Where(e => e.IdWell == idWell);
-
- var timezone = wellService.GetTimezone(idWell);
+ var timezone = wellService.GetTimezone(request.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);
+ return offset;
}
}
diff --git a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs
index 208e94ac..ea9a6975 100644
--- a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs
+++ b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs
@@ -383,12 +383,12 @@ public class WellOperationRepository : IWellOperationRepository
new[] { nameof(wellOperationDtos) });
}
- if (previousDateEnd > currentDateStart)
- {
- yield return new ValidationResult(
- "Предыдущая операция не завершена",
- new[] { nameof(wellOperationDtos) });
- }
+ //if (previousDateEnd > currentDateStart)
+ //{
+ // yield return new ValidationResult(
+ // "Предыдущая операция не завершена",
+ // new[] { nameof(wellOperationDtos) });
+ //}
previous = current;
}
diff --git a/AsbCloudWebApi.IntegrationTests/Clients/IProcessMapPlanDrillingClient.cs b/AsbCloudWebApi.IntegrationTests/Clients/IProcessMapPlanDrillingClient.cs
index 45dfd59a..7c8c65e3 100644
--- a/AsbCloudWebApi.IntegrationTests/Clients/IProcessMapPlanDrillingClient.cs
+++ b/AsbCloudWebApi.IntegrationTests/Clients/IProcessMapPlanDrillingClient.cs
@@ -18,6 +18,9 @@ public interface IProcessMapPlanDrillingClient
[Delete(BaseRoute)]
Task> DeleteRange(int idWell, [Body] IEnumerable ids);
+ [Delete($"{BaseRoute}/clear")]
+ Task> Clear(int idWell);
+
[Get(BaseRoute)]
Task>> Get(int idWell, ProcessMapPlanBaseRequest request);
@@ -28,5 +31,5 @@ public interface IProcessMapPlanDrillingClient
Task>> GetDatesChange(int idWell);
[Put(BaseRoute)]
- Task> UpdateRangeAsync(int idWell, IEnumerable dtos);
+ Task> UpdateOrInsertRange(int idWell, IEnumerable dtos);
}
diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/ProcessMapPlanDrillingControllerTest.cs b/AsbCloudWebApi.IntegrationTests/Controllers/ProcessMapPlanDrillingControllerTest.cs
index 7bdf3135..c0ba7093 100644
--- a/AsbCloudWebApi.IntegrationTests/Controllers/ProcessMapPlanDrillingControllerTest.cs
+++ b/AsbCloudWebApi.IntegrationTests/Controllers/ProcessMapPlanDrillingControllerTest.cs
@@ -179,7 +179,7 @@ public class ProcessMapPlanDrillingControllerTest: BaseIntegrationTest
Assert.Equal(2, count);
var oldEntity = dbset.First(p => p.Id == entry.Entity.Id);
- Assert.Equal(ProcessMapPlanBase.IdClearedOnImport, oldEntity.IdState);
+ Assert.Equal(ProcessMapPlanBase.IdCleared, oldEntity.IdState);
Assert.Equal(1, oldEntity.IdEditor);
Assert.NotNull(oldEntity.Obsolete);
Assert.InRange(oldEntity.Obsolete.Value, startTime, doneTime);
@@ -194,44 +194,61 @@ public class ProcessMapPlanDrillingControllerTest: BaseIntegrationTest
}
[Fact]
- public async Task UpdateRange_returns_success()
+ public async Task UpdateOrInsertRange_returns_success()
{
// arrange
var startTime = DateTimeOffset.UtcNow;
-
+
var dbset = dbContext.Set();
-
+
var entry = dbset.Add(entity);
dbContext.SaveChanges();
entry.State = EntityState.Detached;
- var dtoCopy = dto.Adapt();
- dtoCopy.Id = entry.Entity.Id;
- dtoCopy.Comment = "nebuchadnezzar";
- dtoCopy.DeltaPressureLimitMax ++;
- dtoCopy.DeltaPressurePlan ++;
- dtoCopy.FlowPlan ++;
- dtoCopy.FlowLimitMax ++;
- dtoCopy.RopPlan ++;
- dtoCopy.AxialLoadPlan ++;
- dtoCopy.AxialLoadLimitMax ++;
- dtoCopy.DepthStart ++;
- dtoCopy.DepthEnd ++;
- dtoCopy.TopDriveSpeedPlan ++;
- dtoCopy.TopDriveSpeedLimitMax ++;
- dtoCopy.TopDriveTorquePlan ++;
- dtoCopy.TopDriveTorqueLimitMax ++;
+ var dtoUpdate = dto.Adapt();
+ dtoUpdate.Id = entry.Entity.Id;
+ dtoUpdate.Comment = "nebuchadnezzar";
+ dtoUpdate.DeltaPressureLimitMax++;
+ dtoUpdate.DeltaPressurePlan++;
+ dtoUpdate.FlowPlan++;
+ dtoUpdate.FlowLimitMax++;
+ dtoUpdate.RopPlan++;
+ dtoUpdate.AxialLoadPlan++;
+ dtoUpdate.AxialLoadLimitMax++;
+ dtoUpdate.DepthStart++;
+ dtoUpdate.DepthEnd++;
+ dtoUpdate.TopDriveSpeedPlan++;
+ dtoUpdate.TopDriveSpeedLimitMax++;
+ dtoUpdate.TopDriveTorquePlan++;
+ dtoUpdate.TopDriveTorqueLimitMax++;
+
+ var dtoInsert = dtoUpdate.Adapt();
+ dtoInsert.Id = 0;
+ dtoInsert.Comment = "nebuchad";
+ dtoInsert.DeltaPressureLimitMax++;
+ dtoInsert.DeltaPressurePlan++;
+ dtoInsert.FlowPlan++;
+ dtoInsert.FlowLimitMax++;
+ dtoInsert.RopPlan++;
+ dtoInsert.AxialLoadPlan++;
+ dtoInsert.AxialLoadLimitMax++;
+ dtoInsert.DepthStart++;
+ dtoInsert.DepthEnd++;
+ dtoInsert.TopDriveSpeedPlan++;
+ dtoInsert.TopDriveSpeedLimitMax++;
+ dtoInsert.TopDriveTorquePlan++;
+ dtoInsert.TopDriveTorqueLimitMax++;
// act
- var result = await client.UpdateRangeAsync(entity.IdWell, new ProcessMapPlanDrillingDto[] { dtoCopy });
+ var result = await client.UpdateOrInsertRange(entity.IdWell, new ProcessMapPlanDrillingDto[] { dtoUpdate, dtoInsert });
// assert
var doneTime = DateTimeOffset.UtcNow;
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
- Assert.Equal(2, result.Content);
+ Assert.Equal(3, result.Content);
var count = dbset.Count();
- Assert.Equal(2, count);
+ Assert.Equal(3, count);
var oldEntity = dbset.First(p => p.Id == entry.Entity.Id);
Assert.Equal(ProcessMapPlanBase.IdStateReplaced, oldEntity.IdState);
@@ -239,7 +256,7 @@ public class ProcessMapPlanDrillingControllerTest: BaseIntegrationTest
Assert.NotNull(oldEntity.Obsolete);
Assert.InRange(oldEntity.Obsolete.Value, startTime, doneTime);
- var newEntity = dbset.First(p => p.Id != entry.Entity.Id);
+ var newEntity = dbset.First(p => p.Comment == dtoUpdate.Comment);
Assert.Equal(ProcessMapPlanBase.IdStateActual, newEntity.IdState);
Assert.Equal(1, newEntity.IdAuthor);
Assert.Null(newEntity.IdEditor);
@@ -247,7 +264,7 @@ public class ProcessMapPlanDrillingControllerTest: BaseIntegrationTest
Assert.Equal(oldEntity.Id, newEntity.IdPrevious);
Assert.InRange(newEntity.Creation, startTime, doneTime);
- var expected = dtoCopy.Adapt();
+ var expected = dtoUpdate.Adapt();
var excludeProps = new[] {
nameof(ProcessMapPlanDrilling.Id),
nameof(ProcessMapPlanDrilling.Author),
@@ -289,6 +306,39 @@ public class ProcessMapPlanDrillingControllerTest: BaseIntegrationTest
Assert.InRange(actual.Obsolete.Value, startTime, doneTime);
}
+
+ [Fact]
+ public async Task Clear_returns_success()
+ {
+ //arrange
+ var dbset = dbContext.Set();
+
+ var entry = dbset.Add(entity);
+ dbContext.SaveChanges();
+ entry.State = EntityState.Detached;
+
+ var startTime = DateTimeOffset.UtcNow;
+
+ //act
+ var response = await client.Clear(dto.IdWell);
+
+ //assert
+ var doneTime = DateTimeOffset.UtcNow;
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(1, response.Content);
+
+ var actual = dbContext
+ .Set()
+ .FirstOrDefault(p => p.Id == entry.Entity.Id);
+
+ Assert.NotNull(actual);
+ Assert.Equal(ProcessMapPlanBase.IdCleared, actual.IdState);
+ Assert.Equal(1, actual.IdEditor);
+ Assert.NotNull(actual.Obsolete);
+ Assert.InRange(actual.Obsolete.Value, startTime, doneTime);
+ }
+
+
[Fact]
public async Task GetDatesChange_returns_success()
{
diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/SlipsStatControllerTest.cs b/AsbCloudWebApi.IntegrationTests/Controllers/SlipsStatControllerTest.cs
index a31c5f19..908d5b95 100644
--- a/AsbCloudWebApi.IntegrationTests/Controllers/SlipsStatControllerTest.cs
+++ b/AsbCloudWebApi.IntegrationTests/Controllers/SlipsStatControllerTest.cs
@@ -2,6 +2,7 @@ using AsbCloudApp.Data;
using AsbCloudApp.Requests;
using AsbCloudDb.Model;
using AsbCloudWebApi.IntegrationTests.Clients;
+using Microsoft.EntityFrameworkCore;
using Xunit;
namespace AsbCloudWebApi.IntegrationTests.Controllers;
@@ -10,7 +11,7 @@ public class SlipsStatControllerTest : BaseIntegrationTest
{
private static readonly Schedule schedule = new()
{
- Id = 1,
+ Id = 0,
IdDriller = Data.Defaults.Drillers[0].Id,
IdWell = Data.Defaults.Wells[0].Id,
ShiftStart = new TimeOnly(8, 0, 0),
@@ -21,7 +22,7 @@ public class SlipsStatControllerTest : BaseIntegrationTest
private static readonly DetectedOperation detectedOperation = new()
{
- Id = 1,
+ Id = 0,
IdTelemetry = Data.Defaults.Telemetries[0].Id,
IdCategory = WellOperationCategory.IdSlipsTime,
DateStart = new DateTimeOffset(new DateTime(2024, 1, 23, 15, 0, 0, 0, DateTimeKind.Utc)),
@@ -34,7 +35,7 @@ public class SlipsStatControllerTest : BaseIntegrationTest
private static readonly WellOperation factWellOperation = new()
{
- Id = 1,
+ Id = 0,
IdWell = Data.Defaults.Wells[0].Id,
IdWellSectionType = 1,
IdCategory = WellOperationCategory.IdRotor,
@@ -51,9 +52,18 @@ public class SlipsStatControllerTest : BaseIntegrationTest
public SlipsStatControllerTest(WebAppFactoryFixture factory)
: base(factory)
{
- dbContext.Schedule.Add(schedule);
- dbContext.DetectedOperations.Add(detectedOperation);
- dbContext.WellOperations.Add(factWellOperation);
+ var schedules = dbContext.Set();
+ var detectedOperations = dbContext.Set();
+ var wellOperations = dbContext.Set();
+
+ schedules.RemoveRange(schedules);
+ detectedOperations.RemoveRange(detectedOperations);
+ wellOperations.RemoveRange(wellOperations);
+ dbContext.SaveChanges();
+
+ schedules.Add(schedule);
+ detectedOperations.Add(detectedOperation);
+ wellOperations.Add(factWellOperation);
dbContext.SaveChanges();
client = factory.GetAuthorizedHttpClient();
diff --git a/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanBaseController.cs b/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanBaseController.cs
index 1237e628..c3fd0c49 100644
--- a/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanBaseController.cs
+++ b/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanBaseController.cs
@@ -23,10 +23,10 @@ namespace AsbCloudWebApi.Controllers.ProcessMapPlan;
public abstract class ProcessMapPlanBaseController : ControllerBase
where TDto : ProcessMapPlanBaseDto
{
- private readonly IProcessMapPlanBaseRepository repository;
+ private readonly IChangeLogRepository repository;
private readonly IWellService wellService;
- public ProcessMapPlanBaseController(IProcessMapPlanBaseRepository repository, IWellService wellService)
+ public ProcessMapPlanBaseController(IChangeLogRepository repository, IWellService wellService)
{
this.repository = repository;
this.wellService = wellService;
@@ -68,7 +68,9 @@ public abstract class ProcessMapPlanBaseController : ControllerBase
return this.ValidationBadRequest(nameof(dtos), "all dtos should contain same idWell");
var idUser = await AssertUserHasAccessToWell(idWell, token);
- var result = await repository.ClearAndInsertRange(idUser, idWell, dtos, token);
+
+ var request = new ProcessMapPlanBaseRequestWithWell(idWell);
+ var result = await repository.ClearAndInsertRange(idUser, request, dtos, token);
return Ok(result);
}
@@ -85,10 +87,29 @@ public abstract class ProcessMapPlanBaseController : ControllerBase
public async Task DeleteRange([FromRoute]int idWell, IEnumerable ids, CancellationToken token)
{
var idUser = await AssertUserHasAccessToWell(idWell, token);
+
var result = await repository.DeleteRange(idUser, ids, token);
return Ok(result);
}
+ ///
+ /// Очистка
+ ///
+ ///
+ ///
+ ///
+ [HttpDelete("clear")]
+ [ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
+ public async Task Clear([FromRoute] int idWell, CancellationToken token)
+ {
+ var idUser = await AssertUserHasAccessToWell(idWell, token);
+
+ var request = new ProcessMapPlanBaseRequestWithWell(idWell);
+ var result = await repository.Clear(idUser, request, token);
+ return Ok(result);
+ }
+
///
/// Получение
///
@@ -102,7 +123,9 @@ public abstract class ProcessMapPlanBaseController : ControllerBase
public async Task>> Get([FromRoute] int idWell, [FromQuery]ProcessMapPlanBaseRequest request, CancellationToken token)
{
await AssertUserHasAccessToWell(idWell, token);
- var result = await repository.Get(idWell, request, token);
+
+ var serviceRequest = new ProcessMapPlanBaseRequestWithWell(request, idWell);
+ var result = await repository.Get(serviceRequest, token);
return Ok(result);
}
@@ -119,7 +142,9 @@ public abstract class ProcessMapPlanBaseController : ControllerBase
public async Task>> GetChangeLog([FromRoute] int idWell, [FromQuery] DateOnly? date, CancellationToken token)
{
await AssertUserHasAccessToWell(idWell, token);
- var result = await repository.GetChangeLog(idWell, date, token);
+
+ var serviceRequest = new ProcessMapPlanBaseRequestWithWell(idWell);
+ var result = await repository.GetChangeLog(serviceRequest, date, token);
return Ok(result);
}
@@ -135,24 +160,26 @@ public abstract class ProcessMapPlanBaseController : ControllerBase
public async Task>> GetDatesChange([FromRoute] int idWell, CancellationToken token)
{
await AssertUserHasAccessToWell(idWell, token);
- var result = await repository.GetDatesChange(idWell, token);
+
+ var serviceRequest = new ProcessMapPlanBaseRequestWithWell(idWell);
+ var result = await repository.GetDatesChange(serviceRequest, token);
return Ok(result);
}
///
- /// Редактирование
+ /// Редактирование или добавление [для пакетного редактирования]
///
///
///
///
///
- [HttpPut]
+ [HttpPut()]
[ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
- public async Task UpdateRange([FromRoute] int idWell, IEnumerable dtos, CancellationToken token)
+ public async Task UpdateOrInsertRange([FromRoute] int idWell, IEnumerable dtos, CancellationToken token)
{
var first = dtos.FirstOrDefault();
- if(first is null)
+ if (first is null)
return NoContent();
if (idWell == 0 || dtos.Any(d => d.IdWell != idWell))
@@ -160,7 +187,7 @@ public abstract class ProcessMapPlanBaseController : ControllerBase
var idUser = await AssertUserHasAccessToWell(idWell, token);
- var result = await repository.UpdateRange(idUser, dtos, token);
+ var result = await repository.UpdateOrInsertRange(idUser, dtos, token);
return Ok(result);
}
diff --git a/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanDrillingController.cs b/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanDrillingController.cs
index 907f4ec5..b0a20f1e 100644
--- a/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanDrillingController.cs
+++ b/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanDrillingController.cs
@@ -1,12 +1,15 @@
using AsbCloudApp.Data.ProcessMapPlan;
using AsbCloudApp.Repositories;
+using AsbCloudApp.Requests;
using AsbCloudApp.Services;
namespace AsbCloudWebApi.Controllers.ProcessMapPlan;
public class ProcessMapPlanDrillingController : ProcessMapPlanBaseController
{
- public ProcessMapPlanDrillingController(IProcessMapPlanBaseRepository repository, IWellService wellService)
+ public ProcessMapPlanDrillingController(
+ IChangeLogRepository repository,
+ IWellService wellService)
: base(repository, wellService)
{
}
diff --git a/AsbCloudWebApi/Extentions.cs b/AsbCloudWebApi/Extentions.cs
index 5659292c..7299fae5 100644
--- a/AsbCloudWebApi/Extentions.cs
+++ b/AsbCloudWebApi/Extentions.cs
@@ -70,8 +70,10 @@ namespace Microsoft.AspNetCore.Mvc
public static BadRequestObjectResult ValidationBadRequest(this ControllerBase controller, IEnumerable validationResults)
{
var errors = validationResults
- .SelectMany(e => e.MemberNames.Select(name=> new { name, e.ErrorMessage }))
- .ToDictionary(e => e.name, e => new[] { e.ErrorMessage ?? string.Empty });
+ .SelectMany(e => e.MemberNames.Select(name => new { name, e.ErrorMessage }))
+ .GroupBy(e => e.name)
+ .ToDictionary(e => e.Key, e => e.Select(el => el.ErrorMessage ?? string.Empty).ToArray());
+
var problem = new ValidationProblemDetails(errors);
return controller.BadRequest(problem);
}