Merge pull request '#22341382 Конструкция скважины' (#165) from feature/sections into dev

Reviewed-on: http://test.digitaldrilling.ru:8080/DDrilling/AsbCloudServer/pulls/165
This commit is contained in:
Никита Фролов 2023-12-15 13:43:02 +05:00
commit d7464a03c7
20 changed files with 9990 additions and 24 deletions

View File

@ -0,0 +1,69 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace AsbCloudApp.Data.ProcessMaps;
/// <summary>
/// Секция скважины - план
/// </summary>
public class WellSectionPlanDto : ItemInfoDto,
IId,
IWellRelated,
IValidatableObject
{
/// <inheritdoc/>
public int Id { get; set; }
/// <inheritdoc/>
public int IdWell { get; set; }
/// <summary>
/// Тип секции
/// </summary>
[Required(ErrorMessage = "Поле обязательно для заполнение")]
[Range(1, int.MaxValue)]
public int IdSectionType { get; set; }
/// <summary>
/// Начальная глубина бурения, м
/// </summary>
[Required(ErrorMessage = "Поле обязательно для заполнение")]
[Range(0, 10000, ErrorMessage = "Допустимое значение от 0 до 10000")]
public double DepthStart { get; set; }
/// <summary>
/// Конечная глубина бурения, м
/// </summary>
[Required(ErrorMessage = "Поле обязательно для заполнение")]
[Range(0, 10000, ErrorMessage = "Допустимое значение от 0 до 10000")]
public double DepthEnd { get; set; }
/// <summary>
/// Внешний диаметр
/// </summary>
[Range(1, 10000, ErrorMessage = "Допустимое значение от 1 до 10000")]
public double? OuterDiameter { get; set; }
/// <summary>
/// Внутренний диаметр
/// </summary>
[Range(1, 10000, ErrorMessage = "Допустимое значение от 1 до 10000")]
public double? InnerDiameter { get; set; }
/// <inheritdoc />
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!OuterDiameter.HasValue && !InnerDiameter.HasValue)
yield break;
if (!OuterDiameter.HasValue)
yield return new ValidationResult("Поле обязательно для заполнение", new[] { nameof(OuterDiameter) });
if (!InnerDiameter.HasValue)
yield return new ValidationResult("Поле обязательно для заполнение", new[] { nameof(InnerDiameter) });
if (OuterDiameter <= InnerDiameter)
yield return new ValidationResult("Внешний диаметр не должен быть больше или равен внутреннему",
new[] { nameof(OuterDiameter) });
}
}

View File

@ -0,0 +1,27 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace AsbCloudApp.Data;
/// <summary>
/// Результат валидации объекта
/// </summary>
public class ValidationResultDto<T>
where T : class
{
/// <summary>
/// Флаг валидности
/// </summary>
public bool IsValid => !Warnings.Any();
/// <summary>
/// Объект валидации
/// </summary>
public T Item { get; set; } = null!;
/// <summary>
/// Предупреждения
/// </summary>
public IEnumerable<ValidationResult> Warnings { get; set; } = Enumerable.Empty<ValidationResult>();
}

View File

@ -0,0 +1,22 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Data.ProcessMaps;
using AsbCloudApp.Services;
namespace AsbCloudApp.Repositories;
/// <summary>
/// Секция скважины - план
/// </summary>
public interface IWellSectionPlanRepository : IRepositoryWellRelated<WellSectionPlanDto>
{
/// <summary>
/// Получить типы секций
/// </summary>
/// <param name="idWell"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<WellSectionTypeDto>> GetWellSectionTypesAsync(int idWell, CancellationToken cancellationToken);
}

View File

@ -0,0 +1,22 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Data.ProcessMaps;
namespace AsbCloudApp.Services.ProcessMaps;
/// <summary>
/// РТК
/// </summary>
public interface IProcessMapPlanService<T>
where T : ProcessMapPlanBaseDto
{
/// <summary>
/// Получение РТК план по скважине
/// </summary>
/// <param name="idWell"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<ValidationResultDto<T>>> GetAsync(int idWell, CancellationToken cancellationToken);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,101 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace AsbCloudDb.Migrations
{
public partial class Add_WellSectionPlan : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "t_well_section_plan",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
id_well = table.Column<int>(type: "integer", nullable: false, comment: "Id скважины"),
id_section_type = table.Column<int>(type: "integer", nullable: false, comment: "Тип секции"),
id_user = table.Column<int>(type: "integer", nullable: false, comment: "Id пользователя"),
date_last_update = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true, comment: "Дата последнего обновления"),
depth_start = table.Column<double>(type: "double precision", nullable: false, comment: "Начальная глубина бурения, м"),
depth_end = table.Column<double>(type: "double precision", nullable: false, comment: "Конечная глубина бурения, м"),
outer_diameter = table.Column<double>(type: "double precision", nullable: true, comment: "Внешний диаметр"),
inner_diameter = table.Column<double>(type: "double precision", nullable: true, comment: "Внутренний диаметр")
},
constraints: table =>
{
table.PrimaryKey("PK_t_well_section_plan", x => x.id);
table.ForeignKey(
name: "FK_t_well_section_plan_t_well_id_well",
column: x => x.id_well,
principalTable: "t_well",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_t_well_section_plan_t_well_section_type_id_section_type",
column: x => x.id_section_type,
principalTable: "t_well_section_type",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.InsertData(
table: "t_permission",
columns: new[] { "id", "description", "name" },
values: new object[,]
{
{ 530, "Разрешение на редактирование плановой конструкции скважины", "WellSectionPlan.edit" },
{ 531, "Разрешение на удаление плановой конструкции скважины", "WellSectionPlan.delete" }
});
migrationBuilder.InsertData(
table: "t_relation_user_role_permission",
columns: new[] { "id_permission", "id_user_role" },
values: new object[,]
{
{ 530, 1 },
{ 531, 1 }
});
migrationBuilder.CreateIndex(
name: "IX_t_well_section_plan_id_section_type",
table: "t_well_section_plan",
column: "id_section_type");
migrationBuilder.CreateIndex(
name: "IX_t_well_section_plan_id_well_id_section_type",
table: "t_well_section_plan",
columns: new[] { "id_well", "id_section_type" },
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "t_well_section_plan");
migrationBuilder.DeleteData(
table: "t_relation_user_role_permission",
keyColumns: new[] { "id_permission", "id_user_role" },
keyValues: new object[] { 530, 1 });
migrationBuilder.DeleteData(
table: "t_relation_user_role_permission",
keyColumns: new[] { "id_permission", "id_user_role" },
keyValues: new object[] { 531, 1 });
migrationBuilder.DeleteData(
table: "t_permission",
keyColumn: "id",
keyValue: 530);
migrationBuilder.DeleteData(
table: "t_permission",
keyColumn: "id",
keyValue: 531);
}
}
}

View File

@ -2449,6 +2449,18 @@ namespace AsbCloudDb.Migrations
Id = 528, Id = 528,
Description = "Разрешение на удаление контакта", Description = "Разрешение на удаление контакта",
Name = "WellContact.delete" Name = "WellContact.delete"
},
new
{
Id = 530,
Description = "Разрешение на редактирование плановой конструкции скважины",
Name = "WellSectionPlan.edit"
},
new
{
Id = 531,
Description = "Разрешение на удаление плановой конструкции скважины",
Name = "WellSectionPlan.delete"
}); });
}); });
@ -4045,6 +4057,16 @@ namespace AsbCloudDb.Migrations
{ {
IdUserRole = 1, IdUserRole = 1,
IdPermission = 528 IdPermission = 528
},
new
{
IdUserRole = 1,
IdPermission = 530
},
new
{
IdUserRole = 1,
IdPermission = 531
}); });
}); });
@ -7072,6 +7094,65 @@ namespace AsbCloudDb.Migrations
}); });
}); });
modelBuilder.Entity("AsbCloudDb.Model.WellSections.WellSectionPlan", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<double>("DepthEnd")
.HasColumnType("double precision")
.HasColumnName("depth_end")
.HasComment("Конечная глубина бурения, м");
b.Property<double>("DepthStart")
.HasColumnType("double precision")
.HasColumnName("depth_start")
.HasComment("Начальная глубина бурения, м");
b.Property<int>("IdSectionType")
.HasColumnType("integer")
.HasColumnName("id_section_type")
.HasComment("Тип секции");
b.Property<int>("IdUser")
.HasColumnType("integer")
.HasColumnName("id_user")
.HasComment("Id пользователя");
b.Property<int>("IdWell")
.HasColumnType("integer")
.HasColumnName("id_well")
.HasComment("Id скважины");
b.Property<double?>("InnerDiameter")
.HasColumnType("double precision")
.HasColumnName("inner_diameter")
.HasComment("Внутренний диаметр");
b.Property<DateTimeOffset?>("LastUpdateDate")
.HasColumnType("timestamp with time zone")
.HasColumnName("date_last_update")
.HasComment("Дата последнего обновления");
b.Property<double?>("OuterDiameter")
.HasColumnType("double precision")
.HasColumnName("outer_diameter")
.HasComment("Внешний диаметр");
b.HasKey("Id");
b.HasIndex("IdSectionType");
b.HasIndex("IdWell", "IdSectionType")
.IsUnique();
b.ToTable("t_well_section_plan");
});
modelBuilder.Entity("AsbCloudDb.Model.WellSectionType", b => modelBuilder.Entity("AsbCloudDb.Model.WellSectionType", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -8870,6 +8951,25 @@ namespace AsbCloudDb.Migrations
b.Navigation("Parent"); b.Navigation("Parent");
}); });
modelBuilder.Entity("AsbCloudDb.Model.WellSections.WellSectionPlan", b =>
{
b.HasOne("AsbCloudDb.Model.WellSectionType", "SectionType")
.WithMany()
.HasForeignKey("IdSectionType")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("AsbCloudDb.Model.Well", "Well")
.WithMany()
.HasForeignKey("IdWell")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("SectionType");
b.Navigation("Well");
});
modelBuilder.Entity("AsbCloudDb.Model.WITS.Record1", b => modelBuilder.Entity("AsbCloudDb.Model.WITS.Record1", b =>
{ {
b.HasOne("AsbCloudDb.Model.Telemetry", "Telemetry") b.HasOne("AsbCloudDb.Model.Telemetry", "Telemetry")

View File

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using AsbCloudDb.Model.DailyReports; using AsbCloudDb.Model.DailyReports;
using AsbCloudDb.Model.Manuals; using AsbCloudDb.Model.Manuals;
using AsbCloudDb.Model.ProcessMaps; using AsbCloudDb.Model.ProcessMaps;
using AsbCloudDb.Model.WellSections;
using AsbCloudDb.Model.Trajectory; using AsbCloudDb.Model.Trajectory;
namespace AsbCloudDb.Model namespace AsbCloudDb.Model
@ -63,6 +64,8 @@ namespace AsbCloudDb.Model
public virtual DbSet<TelemetryWirelineRunOut> TelemetryWirelineRunOut => Set<TelemetryWirelineRunOut>(); public virtual DbSet<TelemetryWirelineRunOut> TelemetryWirelineRunOut => Set<TelemetryWirelineRunOut>();
public virtual DbSet<TrajectoryFact> TrajectoriesFact => Set<TrajectoryFact>(); public virtual DbSet<TrajectoryFact> TrajectoriesFact => Set<TrajectoryFact>();
public virtual DbSet<WellSectionPlan> WellSectionsPlan => Set<WellSectionPlan>();
// GTR WITS // GTR WITS
public DbSet<WitsItemFloat> WitsItemFloat => Set<WitsItemFloat>(); public DbSet<WitsItemFloat> WitsItemFloat => Set<WitsItemFloat>();
public DbSet<WitsItemInt> WitsItemInt => Set<WitsItemInt>(); public DbSet<WitsItemInt> WitsItemInt => Set<WitsItemInt>();
@ -435,6 +438,10 @@ namespace AsbCloudDb.Model
.HasJsonConversion(); .HasJsonConversion();
}); });
modelBuilder.Entity<WellSectionPlan>()
.HasIndex(w => new { w.IdWell, w.IdSectionType })
.IsUnique();
DefaultData.DefaultContextData.Fill(modelBuilder); DefaultData.DefaultContextData.Fill(modelBuilder);
} }

View File

@ -159,8 +159,11 @@
new (){ Id = 525, Name = "ProcessMap.editCompletedWell", Description = "Разрешение на редактирование РТК у завершенной скважины"}, new (){ Id = 525, Name = "ProcessMap.editCompletedWell", Description = "Разрешение на редактирование РТК у завершенной скважины"},
new (){ Id = 526, Name = "WellOperation.editCompletedWell", Description = "Разрешение на редактирование операций у завершенной скважины"}, new (){ Id = 526, Name = "WellOperation.editCompletedWell", Description = "Разрешение на редактирование операций у завершенной скважины"},
new() { Id = 527, Name = "Manual.delete", Description = "Разрешение на удаление инструкций"}, new() { Id = 527, Name = "Manual.delete", Description = "Разрешение на удаление инструкций" },
new (){ Id = 528, Name="WellContact.delete", Description="Разрешение на удаление контакта"} new() { Id = 528, Name = "WellContact.delete", Description = "Разрешение на удаление контакта" },
new() { Id = 530, Name = "WellSectionPlan.edit", Description = "Разрешение на редактирование плановой конструкции скважины"},
new() { Id = 531, Name = "WellSectionPlan.delete", Description = "Разрешение на удаление плановой конструкции скважины"}
}; };
} }
} }

View File

@ -10,6 +10,7 @@ using System.Threading.Tasks;
using AsbCloudDb.Model.DailyReports; using AsbCloudDb.Model.DailyReports;
using AsbCloudDb.Model.Manuals; using AsbCloudDb.Model.Manuals;
using AsbCloudDb.Model.ProcessMaps; using AsbCloudDb.Model.ProcessMaps;
using AsbCloudDb.Model.WellSections;
using AsbCloudDb.Model.Trajectory; using AsbCloudDb.Model.Trajectory;
namespace AsbCloudDb.Model namespace AsbCloudDb.Model
@ -80,6 +81,7 @@ namespace AsbCloudDb.Model
DbSet<Contact> Contacts { get; } DbSet<Contact> Contacts { get; }
DbSet<DrillTest> DrillTests { get; } DbSet<DrillTest> DrillTests { get; }
DbSet<TrajectoryFact> TrajectoriesFact { get; } DbSet<TrajectoryFact> TrajectoriesFact { get; }
DbSet<WellSectionPlan> WellSectionsPlan { get; }
DatabaseFacade Database { get; } DatabaseFacade Database { get; }
Task<int> RefreshMaterializedViewAsync(string mwName, CancellationToken token); Task<int> RefreshMaterializedViewAsync(string mwName, CancellationToken token);

View File

@ -0,0 +1,45 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace AsbCloudDb.Model.WellSections;
[Table("t_well_section_plan")]
public class WellSectionPlan : IId,
IWellRelated
{
[Key]
[Column("id")]
public int Id { get; set; }
[Column("id_well"), Comment("Id скважины")]
public int IdWell { get; set; }
[Column("id_section_type"), Comment("Тип секции")]
public int IdSectionType { get; set; }
[Column("id_user"), Comment("Id пользователя")]
public int IdUser { get; set; }
[Column("date_last_update", TypeName = "timestamp with time zone"), Comment("Дата последнего обновления")]
public DateTimeOffset? LastUpdateDate { get; set; }
[Column("depth_start"), Comment("Начальная глубина бурения, м")]
public double DepthStart { get; set; }
[Column("depth_end"), Comment("Конечная глубина бурения, м")]
public double DepthEnd { get; set; }
[Column("outer_diameter"), Comment("Внешний диаметр")]
public double? OuterDiameter { get; set; }
[Column("inner_diameter"), Comment("Внутренний диаметр")]
public double? InnerDiameter { get; set; }
[ForeignKey(nameof(IdWell))]
public virtual Well Well { get; set; } = null!;
[ForeignKey(nameof(IdSectionType))]
public virtual WellSectionType SectionType { get; set; } = null!;
}

View File

@ -44,6 +44,8 @@ using Microsoft.Extensions.DependencyInjection;
using AsbCloudApp.Data.DailyReport.Blocks.TimeBalance; using AsbCloudApp.Data.DailyReport.Blocks.TimeBalance;
using AsbCloudApp.Services.DailyReport; using AsbCloudApp.Services.DailyReport;
using AsbCloudDb.Model.DailyReports.Blocks.TimeBalance; using AsbCloudDb.Model.DailyReports.Blocks.TimeBalance;
using AsbCloudDb.Model.WellSections;
using AsbCloudInfrastructure.Services.ProcessMaps;
namespace AsbCloudInfrastructure namespace AsbCloudInfrastructure
{ {
@ -315,6 +317,13 @@ namespace AsbCloudInfrastructure
services.AddTransient<IDailyReportRepository, DailyReportRepository>(); services.AddTransient<IDailyReportRepository, DailyReportRepository>();
services.AddTransient<IDailyReportExportService, DailyReportExportService>(); services.AddTransient<IDailyReportExportService, DailyReportExportService>();
services.AddTransient<IRepositoryWellRelated<WellSectionPlanDto>, CrudWellRelatedRepositoryBase<WellSectionPlanDto, WellSectionPlan>>();
services.AddTransient<IProcessMapPlanService<ProcessMapPlanWellDrillingDto>, ProcessMapPlanService<ProcessMapPlanWellDrillingDto>>();
services.AddTransient<IProcessMapPlanService<ProcessMapPlanWellReamDto>, ProcessMapPlanService<ProcessMapPlanWellReamDto>>();
services.AddTransient<IWellSectionPlanRepository, WellSectionPlanRepository>();
return services; return services;
} }
} }

View File

@ -24,7 +24,8 @@ namespace AsbCloudInfrastructure.Repository
{ {
var entities = await GetQuery() var entities = await GetQuery()
.Where(e => e.IdWell == idWell) .Where(e => e.IdWell == idWell)
.ToListAsync(token); .AsNoTracking()
.ToArrayAsync(token);
var dtos = entities.Select(Convert).ToList(); var dtos = entities.Select(Convert).ToList();
return dtos; return dtos;
} }
@ -36,7 +37,8 @@ namespace AsbCloudInfrastructure.Repository
var entities = await GetQuery() var entities = await GetQuery()
.Where(e => idsWells.Contains(e.IdWell)) .Where(e => idsWells.Contains(e.IdWell))
.ToListAsync(token); .AsNoTracking()
.ToArrayAsync(token);
var dtos = entities.Select(Convert).ToList(); var dtos = entities.Select(Convert).ToList();
return dtos; return dtos;
} }

View File

@ -0,0 +1,86 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Data.ProcessMaps;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudDb.Model;
using AsbCloudDb.Model.WellSections;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Npgsql;
namespace AsbCloudInfrastructure.Repository;
public class WellSectionPlanRepository : CrudWellRelatedRepositoryBase<WellSectionPlanDto, WellSectionPlan>,
IWellSectionPlanRepository
{
public WellSectionPlanRepository(IAsbCloudDbContext context)
: base(context, query => query.Include(w => w.SectionType))
{
}
public override async Task<int> InsertAsync(WellSectionPlanDto item, CancellationToken token)
{
var id = 0;
try
{
id = await base.InsertAsync(item, token);
}
catch (DbUpdateException ex)
{
if (ex.InnerException is PostgresException pgException)
HandlePostgresException(pgException, item);
else
throw;
}
return id;
}
public override async Task<int> UpdateAsync(WellSectionPlanDto item, CancellationToken token)
{
var id = 0;
try
{
id = await base.UpdateAsync(item, token);
}
catch (DbUpdateException ex)
{
if (ex.InnerException is PostgresException pgException)
HandlePostgresException(pgException, item);
else
throw;
}
return id;
}
public async Task<IEnumerable<WellSectionTypeDto>> GetWellSectionTypesAsync(int idWell, CancellationToken cancellationToken)
{
var query = from wellSectionType in dbContext.WellSectionTypes
join wellSectionPlan in dbContext.WellSectionsPlan on wellSectionType.Id equals wellSectionPlan.IdSectionType
where wellSectionPlan.IdWell == idWell
orderby wellSectionType.Order
select wellSectionType;
IEnumerable<WellSectionType> entities = await query.ToArrayAsync(cancellationToken);
if (!entities.Any())
entities = await dbContext.WellSectionTypes.OrderBy(w => w.Order).ToArrayAsync(cancellationToken);
return entities.Select(w => w.Adapt<WellSectionTypeDto>());
}
private static void HandlePostgresException(PostgresException pgException, WellSectionPlanDto wellSectionPlan)
{
if (pgException.SqlState == PostgresErrorCodes.UniqueViolation)
throw new ArgumentInvalidException($"Секция уже добавлена в конструкцию скважины",
nameof(wellSectionPlan.IdSectionType));
}
}

View File

@ -0,0 +1,65 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Data.ProcessMaps;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
using AsbCloudApp.Services.ProcessMaps;
namespace AsbCloudInfrastructure.Services.ProcessMaps;
public class ProcessMapPlanService<T> : IProcessMapPlanService<T>
where T : ProcessMapPlanBaseDto
{
private readonly ICrudRepository<WellSectionTypeDto> wellSectionTypeRepository;
private readonly IProcessMapPlanRepository<T> processMapPlanRepository;
private readonly IRepositoryWellRelated<WellSectionPlanDto> wellSectionPlanRepository;
public ProcessMapPlanService(ICrudRepository<WellSectionTypeDto> wellSectionTypeRepository,
IProcessMapPlanRepository<T> processMapPlanRepository,
IRepositoryWellRelated<WellSectionPlanDto> wellSectionPlanRepository)
{
this.wellSectionTypeRepository = wellSectionTypeRepository;
this.processMapPlanRepository = processMapPlanRepository;
this.wellSectionPlanRepository = wellSectionPlanRepository;
}
public async Task<IEnumerable<ValidationResultDto<T>>> GetAsync(int idWell, CancellationToken cancellationToken)
{
var wellSectionTypes = await wellSectionTypeRepository.GetAllAsync(cancellationToken);
var processMapsPlan = await processMapPlanRepository.GetByIdWellAsync(idWell, cancellationToken);
var wellSectionsPlan = await wellSectionPlanRepository.GetByIdWellAsync(idWell, cancellationToken);
return processMapsPlan.Select(processMapPlan =>
{
var wellSectionPlan = wellSectionsPlan.FirstOrDefault(s => s.IdSectionType == processMapPlan.IdWellSectionType);
var isValid = wellSectionPlan is not null && wellSectionPlan.DepthStart <= processMapPlan.DepthStart &&
wellSectionPlan.DepthEnd >= processMapPlan.DepthEnd;
var validationResult = new ValidationResultDto<T>
{
Item = processMapPlan
};
if (isValid)
return validationResult;
var wellSectionType = wellSectionTypes.FirstOrDefault(s => s.Id == processMapPlan.IdWellSectionType);
validationResult.Warnings = new ValidationResult[]
{
new($"Конструкция секции: {wellSectionType?.Caption}; " +
$"Интервал бурения от {processMapPlan.DepthStart} до {processMapPlan.DepthEnd} не совпадает с данными указанными на странице " +
$"Конструкция скважины / План", new[] { nameof(processMapPlan.DepthStart), nameof(processMapPlan.DepthEnd) })
};
return validationResult;
});
}
}

View File

@ -0,0 +1,94 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Data.ProcessMaps;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
using AsbCloudInfrastructure.Services.ProcessMaps;
using NSubstitute;
using Xunit;
namespace AsbCloudWebApi.Tests.UnitTests.Services.ProcessMaps;
public class ProcessMapPlanServiceTests
{
private const int idWellSectionPlan = 1;
private const int idWell = 3;
private const int idWellSectionType = 54;
private readonly IEnumerable<WellSectionPlanDto> fakeCollectionWellSectionPlan = new WellSectionPlanDto[]
{
new()
{
Id = idWellSectionPlan,
IdWell = idWell,
IdSectionType = idWellSectionType,
DepthStart = 80,
DepthEnd = 150
}
};
private readonly IEnumerable<WellSectionTypeDto> fakeCollectionWellSectionTypes = new WellSectionTypeDto[]
{
new()
{
Id = idWellSectionType,
Caption = "Направление 1",
Order = 1
}
};
private readonly ICrudRepository<WellSectionTypeDto> wellSectionTypeRepositoryMock =
Substitute.For<ICrudRepository<WellSectionTypeDto>>();
private readonly IProcessMapPlanRepository<ProcessMapPlanWellDrillingDto> processMapPlanRepositoryMock =
Substitute.For<IProcessMapPlanRepository<ProcessMapPlanWellDrillingDto>>();
private readonly IRepositoryWellRelated<WellSectionPlanDto> wellSectionPlanRepositoryMock =
Substitute.For<IRepositoryWellRelated<WellSectionPlanDto>>();
private readonly ProcessMapPlanService<ProcessMapPlanWellDrillingDto> processMapPlanService;
public ProcessMapPlanServiceTests()
{
processMapPlanService = new ProcessMapPlanService<ProcessMapPlanWellDrillingDto>(wellSectionTypeRepositoryMock,
processMapPlanRepositoryMock, wellSectionPlanRepositoryMock);
wellSectionPlanRepositoryMock.GetByIdWellAsync(Arg.Any<int>(), Arg.Any<CancellationToken>())
.ReturnsForAnyArgs(fakeCollectionWellSectionPlan);
wellSectionTypeRepositoryMock.GetAllAsync(Arg.Any<CancellationToken>())
.ReturnsForAnyArgs(fakeCollectionWellSectionTypes);
}
[Theory]
[InlineData(80, 150, true)]
[InlineData(90, 140, true)]
[InlineData(0, 110, false)]
[InlineData(110, 200, false)]
public async Task GetAsync_ReturnsValidatedCollectionProcessMapPlanWellDrilling(int depthStart, int depthEnd, bool isValidCollection)
{
//arrange
var fakeCollectionProcessMapPlanWellDrilling = new ProcessMapPlanWellDrillingDto[]
{
new()
{
IdWellSectionType = idWellSectionType,
DepthStart = depthStart,
DepthEnd = depthEnd
}
};
processMapPlanRepositoryMock.GetByIdWellAsync(Arg.Any<int>(), Arg.Any<CancellationToken>())
.ReturnsForAnyArgs(fakeCollectionProcessMapPlanWellDrilling);
//act
var result = (await processMapPlanService.GetAsync(idWell, CancellationToken.None)).ToArray();
//assert
Assert.Equal(isValidCollection, result.All(r => r.IsValid));
Assert.Single(result);
}
}

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudApp.Data; using AsbCloudApp.Data;
@ -8,6 +9,7 @@ using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories; using AsbCloudApp.Repositories;
using AsbCloudApp.Requests; using AsbCloudApp.Requests;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudApp.Services.ProcessMaps;
using AsbCloudWebApi.SignalR; using AsbCloudWebApi.SignalR;
using AsbCloudWebApi.SignalR.Clients; using AsbCloudWebApi.SignalR.Clients;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -32,13 +34,15 @@ public abstract class ProcessMapBaseController<T> : ControllerBase
private readonly IUserRepository userRepository; private readonly IUserRepository userRepository;
private readonly ICrudRepository<WellSectionTypeDto> wellSectionRepository; private readonly ICrudRepository<WellSectionTypeDto> wellSectionRepository;
private readonly IProcessMapPlanRepository<T> repository; private readonly IProcessMapPlanRepository<T> repository;
private readonly IProcessMapPlanService<T> service;
protected ProcessMapBaseController(IWellService wellService, protected ProcessMapBaseController(IWellService wellService,
IProcessMapPlanRepository<T> repository, IProcessMapPlanRepository<T> repository,
IUserRepository userRepository, IUserRepository userRepository,
ICrudRepository<WellSectionTypeDto> wellSectionRepository, ICrudRepository<WellSectionTypeDto> wellSectionRepository,
IHubContext<TelemetryHub, ITelemetryHubClient> telemetryHubContext, IHubContext<TelemetryHub, ITelemetryHubClient> telemetryHubContext,
ITelemetryService telemetryService) ITelemetryService telemetryService,
IProcessMapPlanService<T> service)
{ {
this.wellService = wellService; this.wellService = wellService;
this.repository = repository; this.repository = repository;
@ -46,9 +50,10 @@ public abstract class ProcessMapBaseController<T> : ControllerBase
this.wellSectionRepository = wellSectionRepository; this.wellSectionRepository = wellSectionRepository;
this.telemetryHubContext = telemetryHubContext; this.telemetryHubContext = telemetryHubContext;
this.telemetryService = telemetryService; this.telemetryService = telemetryService;
this.service = service;
} }
public abstract string SignalRMethod { get; } protected abstract string SignalRGroup { get; }
protected int IdUser protected int IdUser
{ {
@ -147,9 +152,15 @@ public abstract class ProcessMapBaseController<T> : ControllerBase
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
[HttpGet] [HttpGet]
public async Task<ActionResult<IEnumerable<T>>> GetAsync(int idWell, CancellationToken cancellationToken) [ProducesResponseType(typeof(ValidationResultDto<>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult<ValidationResultDto<T>>> GetAsync(int idWell, CancellationToken cancellationToken)
{ {
var processMaps = await repository.GetByIdWellAsync(idWell, cancellationToken); var processMaps = await service.GetAsync(idWell, cancellationToken);
if (!processMaps.Any())
return NoContent();
return Ok(processMaps); return Ok(processMaps);
} }
@ -204,7 +215,7 @@ public abstract class ProcessMapBaseController<T> : ControllerBase
var dtos = await repository.GetByIdWellAsync(idWell, cancellationToken); var dtos = await repository.GetByIdWellAsync(idWell, cancellationToken);
await telemetryHubContext.Clients await telemetryHubContext.Clients
.Group($"{SignalRMethod}_{idWell}") .Group($"{SignalRGroup}_{idWell}")
.UpdateProcessMap(dtos, cancellationToken); .UpdateProcessMap(dtos, cancellationToken);
} }

View File

@ -26,7 +26,7 @@ public class ProcessMapWellDrillingController : ProcessMapBaseController<Process
private readonly IProcessMapReportWellDrillingExportService processMapReportWellDrillingExportService; private readonly IProcessMapReportWellDrillingExportService processMapReportWellDrillingExportService;
private readonly IProcessMapPlanImportService processMapPlanImportService; private readonly IProcessMapPlanImportService processMapPlanImportService;
public override string SignalRMethod => "ProcessMapWellDrilling"; protected override string SignalRGroup => "ProcessMapWellDrilling";
public ProcessMapWellDrillingController(IWellService wellService, public ProcessMapWellDrillingController(IWellService wellService,
IProcessMapPlanRepository<ProcessMapPlanWellDrillingDto> repository, IProcessMapPlanRepository<ProcessMapPlanWellDrillingDto> repository,
@ -36,8 +36,9 @@ public class ProcessMapWellDrillingController : ProcessMapBaseController<Process
IProcessMapReportWellDrillingService processMapReportWellDrillingService, IProcessMapReportWellDrillingService processMapReportWellDrillingService,
ICrudRepository<WellSectionTypeDto> wellSectionRepository, ICrudRepository<WellSectionTypeDto> wellSectionRepository,
IHubContext<TelemetryHub, ITelemetryHubClient> telemetryHubContext, IHubContext<TelemetryHub, ITelemetryHubClient> telemetryHubContext,
ITelemetryService telemetryService) ITelemetryService telemetryService,
: base(wellService, repository, userRepository, wellSectionRepository, telemetryHubContext, telemetryService) IProcessMapPlanService<ProcessMapPlanWellDrillingDto> service)
: base(wellService, repository, userRepository, wellSectionRepository, telemetryHubContext, telemetryService, service)
{ {
this.processMapReportWellDrillingExportService = processMapReportWellDrillingExportService; this.processMapReportWellDrillingExportService = processMapReportWellDrillingExportService;
this.processMapPlanImportService = processMapPlanImportService; this.processMapPlanImportService = processMapPlanImportService;

View File

@ -2,6 +2,7 @@
using AsbCloudApp.Data.ProcessMaps; using AsbCloudApp.Data.ProcessMaps;
using AsbCloudApp.Repositories; using AsbCloudApp.Repositories;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudApp.Services.ProcessMaps;
using AsbCloudWebApi.SignalR; using AsbCloudWebApi.SignalR;
using AsbCloudWebApi.SignalR.Clients; using AsbCloudWebApi.SignalR.Clients;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
@ -18,10 +19,11 @@ public class ProcessMapWellReamController : ProcessMapBaseController<ProcessMapP
IUserRepository userRepository, IUserRepository userRepository,
ICrudRepository<WellSectionTypeDto> wellSectionRepository, ICrudRepository<WellSectionTypeDto> wellSectionRepository,
IHubContext<TelemetryHub, ITelemetryHubClient> telemetryHubContext, IHubContext<TelemetryHub, ITelemetryHubClient> telemetryHubContext,
ITelemetryService telemetryService) ITelemetryService telemetryService,
: base(wellService, repository, userRepository, wellSectionRepository, telemetryHubContext, telemetryService) IProcessMapPlanService<ProcessMapPlanWellReamDto> service)
: base(wellService, repository, userRepository, wellSectionRepository, telemetryHubContext, telemetryService, service)
{ {
} }
public override string SignalRMethod => "ProccessMapWellReam"; protected override string SignalRGroup => "ProcessMapWellReam";
} }

View File

@ -0,0 +1,188 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Data.ProcessMaps;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace AsbCloudWebApi.Controllers.ProcessMaps;
/// <summary>
/// Конструкция скважины - план
/// </summary>
[ApiController]
[Route("api/well/{idWell:int}/[controller]")]
[Authorize]
public class WellSectionPlanController : ControllerBase
{
private readonly IWellService wellService;
private readonly IWellSectionPlanRepository wellSectionPlanRepository;
private readonly ICrudRepository<WellSectionTypeDto> wellSectionRepository;
public WellSectionPlanController(IWellService wellService,
IWellSectionPlanRepository wellSectionPlanRepository,
ICrudRepository<WellSectionTypeDto> wellSectionRepository)
{
this.wellService = wellService;
this.wellSectionPlanRepository = wellSectionPlanRepository;
this.wellSectionRepository = wellSectionRepository;
}
//TODO: так же следует вынести в базовый контроллер
private int IdUser
{
get
{
var idUser = User.GetUserId();
if (!idUser.HasValue)
throw new ForbidException("Неизвестный пользователь");
return idUser.Value;
}
}
/// <summary>
/// Добавить секцию
/// </summary>
/// <param name="idWell">Идентификатор скважины</param>
/// <param name="wellSection">Секция скважины - план</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpPost]
[Permission]
[ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> InsertAsync(int idWell, WellSectionPlanDto wellSection, CancellationToken cancellationToken)
{
wellSection.IdWell = idWell;
wellSection.IdUser = IdUser;
await CheckIsExistsWellSectionTypeAsync(wellSection.IdSectionType, cancellationToken);
await AssertUserAccessToWell(idWell, cancellationToken);
var wellSectionId = await wellSectionPlanRepository.InsertAsync(wellSection, cancellationToken);
return Ok(wellSectionId);
}
/// <summary>
/// Обновить секцию
/// </summary>
/// <param name="idWell">Идентификатор скважины</param>
/// <param name="wellSection">Секция скважины - план</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpPut]
[Permission]
[ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> UpdateAsync(int idWell, WellSectionPlanDto wellSection, CancellationToken cancellationToken)
{
wellSection.IdWell = idWell;
wellSection.IdUser = IdUser;
wellSection.LastUpdateDate = DateTimeOffset.UtcNow;
await CheckIsExistsWellSectionTypeAsync(wellSection.IdSectionType, cancellationToken);
await AssertUserAccessToWell(idWell, cancellationToken);
var wellSectionId = await wellSectionPlanRepository.UpdateAsync(wellSection, cancellationToken);
if (wellSectionId == ICrudRepository<WellSectionPlanDto>.ErrorIdNotFound)
return this.ValidationBadRequest(nameof(wellSection.Id), $"Секции скважины с Id: {wellSection.Id} не существует");
return Ok(wellSectionId);
}
/// <summary>
/// Получить типы секций
/// </summary>
/// <param name="idWell">Идентификатор скважины</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpGet("wellSectionTypes")]
[ProducesResponseType(typeof(IEnumerable<WellSectionTypeDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> GetWellSectionTypesAsync(int idWell, CancellationToken cancellationToken)
{
await AssertUserAccessToWell(idWell, cancellationToken);
var wellSectionTypes = await wellSectionPlanRepository.GetWellSectionTypesAsync(idWell, cancellationToken);
if (!wellSectionTypes.Any())
return NoContent();
return Ok(wellSectionTypes);
}
/// <summary>
/// Получить список секций
/// </summary>
/// <param name="idWell">Идентификатор скважины</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<WellSectionPlanDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> GetPlanWellSectionsAsync(int idWell, CancellationToken cancellationToken)
{
await AssertUserAccessToWell(idWell, cancellationToken);
var planWellSections = await wellSectionPlanRepository.GetByIdWellAsync(idWell, cancellationToken);
if (!planWellSections.Any())
return NoContent();
return Ok(planWellSections);
}
/// <summary>
/// Удалить секцию
/// </summary>
/// <param name="idWell">Идентификатор скважины</param>
/// <param name="id">Идентификатор плановой секции</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpDelete]
[Permission]
[ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> DeleteAsync(int idWell, int id, CancellationToken cancellationToken)
{
await AssertUserAccessToWell(idWell, cancellationToken);
var deletedWellSectionPlanCount = await wellSectionPlanRepository.DeleteAsync(id, cancellationToken);
return Ok(deletedWellSectionPlanCount);
}
//TODO: нужно создать базовый контроллер связанный со скважиной и вынести этот метод туда. Данный метод много где дублируется
private async Task AssertUserAccessToWell(int idWell, CancellationToken cancellationToken)
{
var idCompany = User.GetCompanyId();
if (!idCompany.HasValue || !await wellService.IsCompanyInvolvedInWellAsync(idCompany.Value, idWell, cancellationToken))
throw new ForbidException("Нет доступа к скважине");
}
//TODO: тоже нужно вынести в базовый контроллер
private async Task CheckIsExistsWellSectionTypeAsync(int idWellSectionType, CancellationToken cancellationToken)
{
_ = await wellSectionRepository.GetOrDefaultAsync(idWellSectionType, cancellationToken)
?? throw new ArgumentInvalidException(nameof(ProcessMapPlanWellDrillingDto.IdWellSectionType),
$"Тип секции с Id: {idWellSectionType} не найден");
}
}