Merge branch 'dev' into feature/integration_tests

This commit is contained in:
Степанов Дмитрий 2023-12-19 09:11:49 +05:00
commit b34b7c7206
31 changed files with 10175 additions and 89 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

@ -105,5 +105,14 @@ namespace AsbCloudApp.Repositories
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<SectionByOperationsDto>> GetSectionsAsync(IEnumerable<int> idsWells, CancellationToken token);
/// <summary>
/// Получить диапазон дат выполнения операций
/// </summary>
/// <param name="idWell"></param>
/// <param name="idType"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<DatesRangeDto?> GetDatesRangeAsync(int idWell, int idType, 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;
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

@ -1,5 +1,8 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.SAUB;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@ -67,5 +70,6 @@ namespace AsbCloudApp.Services
/// <param name="token"></param>
/// <returns></returns>
Task<int> MergeAsync(int from, int to, CancellationToken token);
Task<Stream> GetTelemetriesInfoByLastData(DateTimeOffset from, CancellationToken token);
}
}

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,
Description = "Разрешение на удаление контакта",
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,
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 =>
{
b.Property<int>("Id")
@ -8870,6 +8951,25 @@ namespace AsbCloudDb.Migrations
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 =>
{
b.HasOne("AsbCloudDb.Model.Telemetry", "Telemetry")

View File

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

View File

@ -159,8 +159,11 @@
new (){ Id = 525, Name = "ProcessMap.editCompletedWell", Description = "Разрешение на редактирование РТК у завершенной скважины"},
new (){ Id = 526, Name = "WellOperation.editCompletedWell", Description = "Разрешение на редактирование операций у завершенной скважины"},
new() { Id = 527, Name = "Manual.delete", Description = "Разрешение на удаление инструкций"},
new (){ Id = 528, Name="WellContact.delete", Description="Разрешение на удаление контакта"}
new() { Id = 527, Name = "Manual.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.Manuals;
using AsbCloudDb.Model.ProcessMaps;
using AsbCloudDb.Model.WellSections;
using AsbCloudDb.Model.Trajectory;
namespace AsbCloudDb.Model
@ -80,6 +81,7 @@ namespace AsbCloudDb.Model
DbSet<Contact> Contacts { get; }
DbSet<DrillTest> DrillTests { get; }
DbSet<TrajectoryFact> TrajectoriesFact { get; }
DbSet<WellSectionPlan> WellSectionsPlan { get; }
DatabaseFacade Database { get; }
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

@ -56,6 +56,7 @@
<PackageReference Include="ClosedXML" Version="0.96.0" />
<PackageReference Include="itext7" Version="7.2.3" />
<PackageReference Include="Mapster" Version="7.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.23.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.23.1" />

View File

@ -44,6 +44,8 @@ using Microsoft.Extensions.DependencyInjection;
using AsbCloudApp.Data.DailyReport.Blocks.TimeBalance;
using AsbCloudApp.Services.DailyReport;
using AsbCloudDb.Model.DailyReports.Blocks.TimeBalance;
using AsbCloudDb.Model.WellSections;
using AsbCloudInfrastructure.Services.ProcessMaps;
namespace AsbCloudInfrastructure
{
@ -220,6 +222,7 @@ namespace AsbCloudInfrastructure
services.AddTransient<IProcessMapPlanImportService, ProcessMapPlanImportWellDrillingService>();
services.AddTransient<WellInfoService>();
services.AddTransient<IHelpPageService, HelpPageService>();
services.AddTransient<IScheduleReportService, ScheduleReportService>();
services.AddTransient<TrajectoryService>();
@ -315,6 +318,13 @@ namespace AsbCloudInfrastructure
services.AddTransient<IDailyReportRepository, DailyReportRepository>();
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;
}
}

View File

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

View File

@ -184,6 +184,20 @@ public class WellOperationRepository : IWellOperationRepository
return sections;
}
public async Task<DatesRangeDto?> GetDatesRangeAsync(int idWell, int idType, CancellationToken cancellationToken)
{
var query = db.WellOperations.Where(o => o.IdWell == idWell && o.IdType == idType);
if (!await query.AnyAsync(cancellationToken))
return null;
return new DatesRangeDto
{
From = (await query.MinAsync(o => o.DateStart, cancellationToken)).Date,
To = (await query.MaxAsync(o => o.DateStart, cancellationToken)).Date
};
}
/// <inheritdoc/>
public DateTimeOffset? FirstOperationDate(int idWell)
{

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

@ -19,7 +19,6 @@ using AsbCloudApp.Services.ProcessMaps.WellDrilling;
using AsbCloudApp.Services.Subsystems;
using AsbCloudDb.Model;
using Mapster;
using AsbCloudApp.Data.Trajectory;
namespace AsbCloudInfrastructure.Services.DailyReport;
@ -108,7 +107,15 @@ public class DailyReportService : IDailyReportService
IdWell = well.Id
};
var factWellOperations = (await GetFactWellOperationsAsync(idWell, dailyReport.Date, cancellationToken))
var factOperationRequest = new WellOperationRequest
{
IdWell = idWell,
OperationType = WellOperation.IdOperationTypeFact,
GeDate = dateDailyReport,
LtDate = dateDailyReport.AddHours(24)
};
var factWellOperations = (await wellOperationRepository.GetAsync(factOperationRequest, cancellationToken))
.OrderBy(o => o.DateStart)
.ThenBy(o => o.DepthStart);
@ -150,9 +157,6 @@ public class DailyReportService : IDailyReportService
var dailyReports = new List<DailyReportDto>();
var existingDailyReports = await dailyReportRepository.GetAsync(idWell, request, cancellationToken);
var factWellOperations = await GetFactWellOperationsAsync(idWell, null, cancellationToken);
if (request.GeDate.HasValue)
{
var startDate = new DateTime(request.GeDate.Value.Year, request.GeDate.Value.Month,
@ -173,8 +177,22 @@ public class DailyReportService : IDailyReportService
if (datesRange.From.AddDays(result.Skip) <= datesRange.To)
result.Count = (int)(Math.Ceiling((datesRange.To - DateTime.UnixEpoch).TotalDays) -
Math.Floor((datesRange.From - DateTime.UnixEpoch).TotalDays));
Math.Floor((datesRange.From - DateTime.UnixEpoch).TotalDays)) + 1;
var existingDailyReports = await dailyReportRepository.GetAsync(idWell, request, cancellationToken);
var geDateFactWellOperation = datesRange.From.AddDays(result.Skip);
var ltDateFactWellOperation = geDateFactWellOperation.AddDays(result.Take);
var factWellOperationRequest = new WellOperationRequest
{
IdWell = idWell,
OperationType = WellOperation.IdOperationTypeFact,
GeDate = geDateFactWellOperation,
LtDate = ltDateFactWellOperation
};
var factWellOperations = await wellOperationRepository.GetAsync(factWellOperationRequest, cancellationToken);
if (request.SortFields?.Contains("DateStart desc") == true)
{
@ -208,7 +226,9 @@ public class DailyReportService : IDailyReportService
IdWell = idWell
};
AddFactWellOperationBlock(dailyReport, factWellOperations.Where(o => o.DateStart >= date && o.DateStart <= date.AddDays(1)));
var factWellOperationPerDay = factWellOperations.Where(o => o.DateStart.Date >= date && o.DateStart.Date <= date.AddDays(1));
AddFactWellOperationBlock(dailyReport, factWellOperationPerDay);
dailyReports.Add(dailyReport);
}
@ -216,18 +236,16 @@ public class DailyReportService : IDailyReportService
public async Task<DatesRangeDto?> GetDatesRangeAsync(int idWell, CancellationToken cancellationToken)
{
var factOperations = await GetFactWellOperationsAsync(idWell, null, cancellationToken);
var factOperationDatesRange = await wellOperationRepository.GetDatesRangeAsync(idWell, WellOperation.IdOperationTypeFact,
cancellationToken);
if (!factOperations.Any())
if (factOperationDatesRange is null)
return null;
var minDateStart = factOperations.Min(o => o.DateStart).Date;
var maxDateStart = factOperations.Max(o => o.DateStart).Date;
return new DatesRangeDto
{
From = minDateStart.AddDays(1) <= DateTime.UtcNow ? minDateStart : DateTime.UtcNow.Date.AddDays(-1),
To = maxDateStart.AddDays(1) <= DateTime.UtcNow ? maxDateStart : DateTime.UtcNow.Date.AddDays(-1)
From = factOperationDatesRange.From.AddDays(1) <= DateTime.UtcNow ? factOperationDatesRange.From : DateTime.UtcNow.Date.AddDays(-1),
To = factOperationDatesRange.To.AddDays(1) <= DateTime.UtcNow ? factOperationDatesRange.To : DateTime.UtcNow.Date.AddDays(-1)
};
}
@ -358,15 +376,6 @@ public class DailyReportService : IDailyReportService
};
}
private Task<IEnumerable<WellOperationDto>> GetFactWellOperationsAsync(int idWell, DateTime? dateDailyReport, CancellationToken cancellationToken) =>
wellOperationRepository.GetAsync(new WellOperationRequest
{
IdWell = idWell,
OperationType = WellOperation.IdOperationTypeFact,
GeDate = dateDailyReport,
LtDate = dateDailyReport?.AddHours(24)
}, cancellationToken);
private async Task<bool> IsDateDailyReportInRangeAsync(int idWell, DateTime dateDailyReport, CancellationToken cancellationToken)
{
var datesRange = await GetDatesRangeAsync(idWell, cancellationToken);

View File

@ -10,6 +10,7 @@ using System.Threading;
using System.Threading.Tasks;
using AsbCloudInfrastructure.Services.DetectOperations.Detectors;
using AsbCloudApp.Repositories;
using Microsoft.AspNetCore.Http.Extensions;
namespace AsbCloudInfrastructure.Services.DetectOperations;
@ -21,12 +22,6 @@ public class DetectedOperationExportService
new DetectorSlipsTime()
};
private readonly IDictionary<int, string> domains = new Dictionary<int, string>
{
{ 1, "https://cloud.digitaldrilling.ru" },
{ 2, "https://cloud.autodrilling.ru" }
};
private const int headerRowsCount = 1;
private const string cellDepositName = "B1";
@ -54,7 +49,15 @@ public class DetectedOperationExportService
this.wellOperationRepository = wellOperationRepository;
}
public async Task<Stream> ExportAsync(int idWell, int idDomain, CancellationToken cancellationToken)
/// <summary>
/// Экспорт excel файла с операциями по скважине
/// </summary>
/// <param name="idWell">ключ скважины</param>
/// <param name="host">хост</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public async Task<Stream> ExportAsync(int idWell, string host, CancellationToken cancellationToken)
{
var well = await dbContext.Wells
.Include(w => w.Cluster)
@ -69,17 +72,17 @@ public class DetectedOperationExportService
var operations = await DetectOperationsAsync(well.IdTelemetry.Value, DateTime.UnixEpoch, cancellationToken);
return await GenerateExcelFileStreamAsync(well, idDomain, operations, cancellationToken);
return await GenerateExcelFileStreamAsync(well, host, operations, cancellationToken);
}
private async Task<Stream> GenerateExcelFileStreamAsync(Well well, int idDomain, IEnumerable<OperationDetectorResult> operationDetectorResults,
private async Task<Stream> GenerateExcelFileStreamAsync(Well well, string host, IEnumerable<OperationDetectorResult> operationDetectorResults,
CancellationToken cancellationToken)
{
using var excelTemplateStream = await GetExcelTemplateStreamAsync(cancellationToken);
using var workbook = new XLWorkbook(excelTemplateStream, XLEventTracking.Disabled);
await AddToWorkbookAsync(workbook, well, idDomain, operationDetectorResults, cancellationToken);
await AddToWorkbookAsync(workbook, well, host, operationDetectorResults, cancellationToken);
MemoryStream memoryStream = new MemoryStream();
workbook.SaveAs(memoryStream, new SaveOptions { });
@ -87,7 +90,7 @@ public class DetectedOperationExportService
return memoryStream;
}
private async Task AddToWorkbookAsync(XLWorkbook workbook, Well well, int idDomain, IEnumerable<OperationDetectorResult> operationDetectorResults,
private async Task AddToWorkbookAsync(XLWorkbook workbook, Well well, string host, IEnumerable<OperationDetectorResult> operationDetectorResults,
CancellationToken cancellationToken)
{
const string sheetName = "Операции";
@ -98,12 +101,12 @@ public class DetectedOperationExportService
var sheet = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetName)
?? throw new FileFormatException($"Книга excel не содержит листа {sheetName}.");
await AddToSheetAsync(sheet, well, idDomain, operationDetectorResults
await AddToSheetAsync(sheet, well, host, operationDetectorResults
.OrderBy(x => x.Operation.DateStart).ThenBy(x => x.Operation.DepthStart).ToArray(),
cancellationToken);
}
private async Task AddToSheetAsync(IXLWorksheet sheet, Well well, int idDomain, IList<OperationDetectorResult> operationDetectorResults,
private async Task AddToSheetAsync(IXLWorksheet sheet, Well well, string host, IList<OperationDetectorResult> operationDetectorResults,
CancellationToken cancellationToken)
{
var wellOperationCategories = await dbContext.WellOperationCategories.ToListAsync(cancellationToken);
@ -134,8 +137,11 @@ public class DetectedOperationExportService
&& idReasonOfEndObject is int idReasonOfEnd)
row.Cell(columnIdReasonOfEnd).Value = GetIdReasonOfEnd(idReasonOfEnd);
var link =
$"{domains[idDomain]}/well/{well.Id}/telemetry/monitoring?end={Uri.EscapeDataString(dateStart.AddSeconds(1800 * 0.9).ToString("yyyy-MM-ddTHH:mm:ss.fff"))}&range=1800";
var query = new QueryBuilder();
query.Add("end", dateStart.AddSeconds(1800 * 0.9).ToString("yyyy-MM-ddTHH:mm:ss.fff"));
query.Add("range", "1800");
var link = $"{host}/well/{well.Id}/telemetry/monitoring{query}";
row.Cell(columnDateStart).Value = dateStart;
row.Cell(columnDateStart).SetHyperlink(new XLHyperlink(link));

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

@ -9,7 +9,9 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Csv;
using System.Threading;
using System.Threading.Tasks;
@ -153,6 +155,41 @@ namespace AsbCloudInfrastructure.Services.SAUB
return telemetry;
}
public async Task<Stream> GetTelemetriesInfoByLastData(DateTimeOffset from, CancellationToken token)
{
var fromUtc = from.UtcDateTime;
var query = db.TelemetryDataSaub
.Where(i => i.DateTime >= fromUtc)
.GroupBy(i => i.IdTelemetry)
.Select(i => new
{
i.First().Telemetry,
LastDate = i.Max(i => i.DateTime)
});
var data = await query.ToArrayAsync(token);
var infos = data.Select(i => new TelemetryWithSoftwareVersionsDto(
i.Telemetry.Id,
i.Telemetry.RemoteUid,
i.LastDate,
i.Telemetry.Info.Deposit,
i.Telemetry.Info.Cluster,
i.Telemetry.Info.Well,
i.Telemetry.Info.HmiVersion,
i.Telemetry.Info.SaubPlcVersion,
i.Telemetry.Info.SpinPlcVersion)
);
var stream = new MemoryStream();
if (!data.Any())
return stream;
var serializer = new CsvSerializer<TelemetryWithSoftwareVersionsDto>();
serializer.Serialize(infos, stream);
stream.Seek(0, SeekOrigin.Begin);
return stream;
}
public async Task<int> MergeAsync(int from, int to, CancellationToken token)
{
if (from == to)

View File

@ -0,0 +1,13 @@
using System;
namespace AsbCloudInfrastructure.Services.SAUB;
internal record TelemetryWithSoftwareVersionsDto(int Id,
string RemoteUid,
DateTimeOffset LastData,
string Deposit,
string Cluster,
string Well,
string? HmiVersion,
string? SaubPlcVersion,
string? SpinPlcVersion);

View File

@ -212,6 +212,7 @@ public class DailyReportServiceTest
private readonly DailyReportDto fakeDailyReport;
private readonly WellDto fakeWell;
private readonly DatesRangeDto fakeDatesRange;
public DailyReportServiceTest()
{
@ -233,6 +234,12 @@ public class DailyReportServiceTest
Companies = new[] { fakeCustomer, fakeContractor }
};
fakeDatesRange = new DatesRangeDto
{
From = fakeFirstFactWellOperation.DateStart,
To = fakeLastFactWellOperation.DateStart
};
dailyReportService = new DailyReportService(wellServiceMock,
trajectoryFactNnbRepositoryMock,
dailyReportRepositoryMock,
@ -260,6 +267,9 @@ public class DailyReportServiceTest
wellOperationRepositoryMock.GetAsync(Arg.Any<WellOperationRequest>(), Arg.Any<CancellationToken>())
.ReturnsForAnyArgs(new[] { fakeFirstFactWellOperation, fakeLastFactWellOperation });
wellOperationRepositoryMock.GetDatesRangeAsync(Arg.Any<int>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
.ReturnsForAnyArgs(fakeDatesRange);
wellOperationRepositoryMock.GetSectionTypes()
.ReturnsForAnyArgs(new[] { fakeSectionType });
@ -490,20 +500,23 @@ public class DailyReportServiceTest
[Fact]
public async Task GetAsync_ShouldReturn_FictiveDailyReport()
{
//arrange
var expectedCount = (fakeLastFactWellOperation.DateStart - fakeFirstFactWellOperation.DateStart).TotalDays + 1;
//act
var result = await dailyReportService.GetAsync(idWell, new FileReportRequest(), CancellationToken.None);
//assert
Assert.True((fakeLastFactWellOperation.DateStart - fakeFirstFactWellOperation.DateStart).Days == result.Count);
Assert.Equal(expectedCount, result.Count);
}
[Theory]
[MemberData(nameof(FactWellOperationDates))]
public async Task GetDatesRangeAsync_ShouldReturn_DateRangeByFactWellOperations(IEnumerable<DateTime> factWellOperationDates)
[MemberData(nameof(FactWellOperationDatesRange))]
public async Task GetDatesRangeAsync_ShouldReturn_DateRangeByFactWellOperations(DatesRangeDto datesRange)
{
//arrange
wellOperationRepositoryMock.GetAsync(Arg.Any<WellOperationRequest>(), CancellationToken.None)
.ReturnsForAnyArgs(factWellOperationDates.Select(dateStart => new WellOperationDto { DateStart = dateStart }));
wellOperationRepositoryMock.GetDatesRangeAsync(Arg.Any<int>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
.Returns(datesRange);
//act
var result = await dailyReportService.GetDatesRangeAsync(idWell, CancellationToken.None);
@ -514,43 +527,41 @@ public class DailyReportServiceTest
Assert.True(result.To < DateTime.UtcNow.Date);
}
public static IEnumerable<object[]> FactWellOperationDates()
public static IEnumerable<object[]> FactWellOperationDatesRange()
{
yield return new object[]
{
new[]
new DatesRangeDto
{
new DateTime(2023, 11, 1),
new DateTime(2023, 11, 9),
DateTime.UtcNow
From = new DateTime(2023, 11, 1),
To = new DateTime(2023, 11, 9)
}
};
yield return new object[]
{
new[]
new DatesRangeDto
{
new DateTime(2023, 11, 1),
new DateTime(2023, 11, 1)
From = new DateTime(2023, 11, 1),
To = new DateTime(2023, 11, 1)
}
};
yield return new object[]
{
new[]
new DatesRangeDto
{
DateTime.UtcNow,
DateTime.UtcNow
From = DateTime.UtcNow,
To = DateTime.UtcNow
}
};
yield return new object[]
{
new[]
new DatesRangeDto
{
new DateTime(2023, 11, 1),
new DateTime(2023, 11, 9),
new DateTime(2023, 11, 11)
From = new DateTime(2023, 11, 1),
To = new DateTime(2023, 11, 11)
}
};
}

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.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
@ -8,6 +9,7 @@ using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudApp.Services.ProcessMaps;
using AsbCloudWebApi.SignalR;
using AsbCloudWebApi.SignalR.Clients;
using Microsoft.AspNetCore.Authorization;
@ -32,13 +34,15 @@ public abstract class ProcessMapBaseController<T> : ControllerBase
private readonly IUserRepository userRepository;
private readonly ICrudRepository<WellSectionTypeDto> wellSectionRepository;
private readonly IProcessMapPlanRepository<T> repository;
private readonly IProcessMapPlanService<T> service;
protected ProcessMapBaseController(IWellService wellService,
IProcessMapPlanRepository<T> repository,
IUserRepository userRepository,
ICrudRepository<WellSectionTypeDto> wellSectionRepository,
IHubContext<TelemetryHub, ITelemetryHubClient> telemetryHubContext,
ITelemetryService telemetryService)
ITelemetryService telemetryService,
IProcessMapPlanService<T> service)
{
this.wellService = wellService;
this.repository = repository;
@ -46,9 +50,10 @@ public abstract class ProcessMapBaseController<T> : ControllerBase
this.wellSectionRepository = wellSectionRepository;
this.telemetryHubContext = telemetryHubContext;
this.telemetryService = telemetryService;
this.service = service;
}
public abstract string SignalRMethod { get; }
protected abstract string SignalRGroup { get; }
protected int IdUser
{
@ -147,9 +152,15 @@ public abstract class ProcessMapBaseController<T> : ControllerBase
/// <param name="cancellationToken"></param>
/// <returns></returns>
[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);
}
@ -204,7 +215,7 @@ public abstract class ProcessMapBaseController<T> : ControllerBase
var dtos = await repository.GetByIdWellAsync(idWell, cancellationToken);
await telemetryHubContext.Clients
.Group($"{SignalRMethod}_{idWell}")
.Group($"{SignalRGroup}_{idWell}")
.UpdateProcessMap(dtos, cancellationToken);
}

View File

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

View File

@ -2,6 +2,7 @@
using AsbCloudApp.Data.ProcessMaps;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
using AsbCloudApp.Services.ProcessMaps;
using AsbCloudWebApi.SignalR;
using AsbCloudWebApi.SignalR.Clients;
using Microsoft.AspNetCore.SignalR;
@ -18,10 +19,11 @@ public class ProcessMapWellReamController : ProcessMapBaseController<ProcessMapP
IUserRepository userRepository,
ICrudRepository<WellSectionTypeDto> wellSectionRepository,
IHubContext<TelemetryHub, ITelemetryHubClient> telemetryHubContext,
ITelemetryService telemetryService)
: base(wellService, repository, userRepository, wellSectionRepository, telemetryHubContext, telemetryService)
ITelemetryService 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} не найден");
}
}

View File

@ -122,21 +122,21 @@ namespace AsbCloudWebApi.Controllers.SAUB
/// Создает excel файл с операциями по скважине
/// </summary>
/// <param name="idWell">id скважины</param>
/// <param name="idDomain">Идентификатор домена</param>
/// <param name="token"></param>
[HttpGet("export")]
[Permission]
[ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK, "application/octet-stream")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(ValidationProblemDetails), (int)System.Net.HttpStatusCode.BadRequest)]
public async Task<IActionResult> ExportAsync(int idWell, [Range(1, 2)] int idDomain, CancellationToken token)
public async Task<IActionResult> ExportAsync(int idWell, CancellationToken token)
{
var idCompany = User.GetCompanyId();
if (idCompany is null)
return Forbid();
var stream = await detectedOperationExportService.ExportAsync(idWell, idDomain, token);
var host = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host}";
var stream = await detectedOperationExportService.ExportAsync(idWell, host, token);
return File(stream, "application/octet-stream", "operations.xlsx");
}

View File

@ -3,7 +3,9 @@ using AsbCloudApp.Services;
using AsbCloudWebApi.SignalR;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -37,6 +39,19 @@ namespace AsbCloudWebApi.Controllers.SAUB
this.telemetryHubContext = telemetryHubContext;
}
/// <summary>
/// Информация о версиях ПО по телеметриям, данные от которых приходили не позднее From
/// </summary>
/// <returns></returns>
[HttpGet("Active")]
[ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> GetTelemetriesInfoByLastData(CancellationToken token)
{
var from = DateTimeOffset.UtcNow.AddDays(-1);
var stream = await telemetryService.GetTelemetriesInfoByLastData(from, token);
return File(stream, "text/csv", $"Software versions by active telemetries from {from :yy-MM-dd hh-mm}.csv");
}
/// <summary>
/// Принимает общую информацию по скважине
/// </summary>