Merge branch 'dev' into feature/refactoring_process_map

This commit is contained in:
Степанов Дмитрий 2023-10-16 13:55:51 +05:00
commit 2898b8064c
29 changed files with 35729 additions and 117 deletions

View File

@ -32,15 +32,4 @@ namespace AsbCloudApp.Data
public string? CompanyTypeCaption { get; set; } = null!;
}
/// <summary>
/// DTO компании с пользователями
/// </summary>
public class CompanyWithUsersDto: CompanyDto
{
/// <summary>
/// Пользователи компании
/// </summary>
public IEnumerable<UserContactDto> Users { get; set; } = Enumerable.Empty<UserContactDto>();
}
}

View File

@ -0,0 +1,63 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace AsbCloudApp.Data.User
{
/// <summary>
/// Контакт
/// </summary>
public class ContactDto : IId
{
/// <inheritdoc/>
public int Id { get; set; }
/// <summary>
/// ключ типа компании
/// </summary>
[Required]
public int IdCompanyType { get; set; }
/// <summary>
/// ключ скважины
/// </summary>
[Required]
public int IdWell { get; set; }
/// <summary>
/// ФИО
/// </summary>
[Required]
[StringLength(260, MinimumLength = 0, ErrorMessage = "Допустимая длина ФИО от 1 до 260 символов")]
public string FullName { get; set; } = null!;
/// <summary>
/// Email
/// </summary>
[Required]
[RegularExpression(@"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$", ErrorMessage = "Некорректный email")]
public string Email { get; set; } = null!;
/// <summary>
/// Phone
/// </summary>
[Required]
[RegularExpression(@"^(?:\+7|8)\s?(?:\(\d{3}\)|\d{3})\s?\d{3}-?\d{2}-?\d{2}$", ErrorMessage = "Некорректный номер телефона")]
public string Phone { get; set; } = null!;
/// <summary>
/// Должность
/// </summary>
[Required]
[StringLength(260, MinimumLength = 1, ErrorMessage = "Допустимая длина должности от 1 до 260 символов")]
public string Position { get; set; } = null!;
/// <summary>
/// Компания
/// </summary>
[Required]
[StringLength(260, MinimumLength = 3, ErrorMessage = "Допустимая длина должности от 3 до 260 символов")]
public string Company { get; set; } = null!;
}
}

View File

@ -1,14 +0,0 @@
namespace AsbCloudApp.Data.User
{
/// <summary>
/// Пользователь - контакт
/// </summary>
public class UserContactDto : UserDto
{
/// <summary>
/// является ли пользователь контактом
/// </summary>
public bool IsContact { get; set; }
}
}

View File

@ -1,4 +1,5 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.User;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@ -11,30 +12,53 @@ namespace AsbCloudApp.Services
public interface IWellContactService
{
/// <summary>
/// Полуение пользователей по ключу скважины и типу контакта
/// Получение контактов по ключу скважины и типу контакта
/// </summary>
/// <param name="wellId">ключ скважины</param>
/// <param name="idWell">ключ скважины</param>
/// <param name="contactTypeId">тип контакта</param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<CompanyWithUsersDto>> GetAsync(int wellId, int contactTypeId, CancellationToken token);
Task<IEnumerable<ContactDto>> GetAllAsync(int idWell, int contactTypeId, CancellationToken token);
/// <summary>
/// Получение типов контаков
/// Получение контакта по ключу
/// </summary>
/// <param name="idWell">ключ скважины</param>
/// <param name="id">ключ пользователя</param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<CompanyTypeDto>> GetTypesAsync(int idWell, CancellationToken token);
Task<ContactDto?> GetAsync(int idWell, int id, CancellationToken token);
/// <summary>
/// Обновление контактов по ключу скважины, типу контакта и ключам пользователей
/// Получение типов контактов
/// </summary>
/// <param name="idWell">ключ скважины</param>
/// <param name="contactTypeId">ключ типа контакта</param>
/// <param name="userIds">ключи пользователей</param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> UpdateRangeAsync(int idWell, int contactTypeId, IEnumerable<int> userIds, CancellationToken token);
Task<IEnumerable<CompanyTypeDto>> GetTypesAsync(CancellationToken token);
/// <summary>
/// Добавление контакта
/// </summary>
/// <param name="contactDto"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> InsertAsync(ContactDto contactDto, CancellationToken token);
/// <summary>
/// Изменение контакта
/// </summary>
/// <param name="contactDto"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> UpdateAsync(ContactDto contactDto, CancellationToken token);
/// <summary>
/// Удаление контакта
/// </summary>
/// <param name="idWell">ключ скважины</param>
/// <param name="id">ключ скважины</param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> DeleteAsync(int idWell, int id, CancellationToken token);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,49 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace AsbCloudDb.Migrations
{
public partial class Add_Contacts : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "t_contact",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
id_company_type = table.Column<int>(type: "integer", maxLength: 255, nullable: false, comment: "вид деятельности"),
fio = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false, comment: "ФИО"),
email = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false, comment: "email"),
phone = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false, comment: "номер телефона"),
position = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false, comment: "должность"),
company = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false, comment: "компания")
},
constraints: table =>
{
table.PrimaryKey("PK_t_contact", x => x.id);
table.ForeignKey(
name: "FK_t_contact_t_company_type_id_company_type",
column: x => x.id_company_type,
principalTable: "t_company_type",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
},
comment: "Контакты");
migrationBuilder.CreateIndex(
name: "IX_t_contact_id_company_type",
table: "t_contact",
column: "id_company_type");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "t_contact");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AsbCloudDb.Migrations
{
public partial class Add_Permission_Delete_To_Well_Contact : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.InsertData(
table: "t_permission",
columns: new[] { "id", "description", "name" },
values: new object[] { 528, "Разрешение на удаление контакта", "WellContact.delete" });
migrationBuilder.InsertData(
table: "t_relation_user_role_permission",
columns: new[] { "id_permission", "id_user_role" },
values: new object[] { 528, 1 });
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "t_relation_user_role_permission",
keyColumns: new[] { "id_permission", "id_user_role" },
keyValues: new object[] { 528, 1 });
migrationBuilder.DeleteData(
table: "t_permission",
keyColumn: "id",
keyValue: 528);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,49 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AsbCloudDb.Migrations
{
public partial class Add_Well_To_Contacts : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "id_well",
table: "t_contact",
type: "integer",
maxLength: 255,
nullable: false,
defaultValue: 0,
comment: "ключ скважины");
migrationBuilder.CreateIndex(
name: "IX_t_contact_id_well",
table: "t_contact",
column: "id_well");
migrationBuilder.AddForeignKey(
name: "FK_t_contact_t_well_id_well",
table: "t_contact",
column: "id_well",
principalTable: "t_well",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_t_contact_t_well_id_well",
table: "t_contact");
migrationBuilder.DropIndex(
name: "IX_t_contact_id_well",
table: "t_contact");
migrationBuilder.DropColumn(
name: "id_well",
table: "t_contact");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AsbCloudDb.Migrations
{
public partial class Update_WellContacts_Set_FullName : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "fio",
table: "t_contact",
newName: "full_name");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "full_name",
table: "t_contact",
newName: "fio");
}
}
}

View File

@ -154,6 +154,73 @@ namespace AsbCloudDb.Migrations
});
});
modelBuilder.Entity("AsbCloudDb.Model.Contact", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Company")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("character varying(255)")
.HasColumnName("company")
.HasComment("компания");
b.Property<string>("Email")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("character varying(255)")
.HasColumnName("email")
.HasComment("email");
b.Property<string>("FullName")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("character varying(255)")
.HasColumnName("full_name")
.HasComment("ФИО");
b.Property<int>("IdCompanyType")
.HasMaxLength(255)
.HasColumnType("integer")
.HasColumnName("id_company_type")
.HasComment("вид деятельности");
b.Property<int>("IdWell")
.HasMaxLength(255)
.HasColumnType("integer")
.HasColumnName("id_well")
.HasComment("ключ скважины");
b.Property<string>("Phone")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("phone")
.HasComment("номер телефона");
b.Property<string>("Position")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("character varying(255)")
.HasColumnName("position")
.HasComment("должность");
b.HasKey("Id");
b.HasIndex("IdCompanyType");
b.HasIndex("IdWell");
b.ToTable("t_contact");
b.HasComment("Контакты");
});
modelBuilder.Entity("AsbCloudDb.Model.DailyReport.DailyReport", b =>
{
b.Property<int>("IdWell")
@ -2262,6 +2329,12 @@ namespace AsbCloudDb.Migrations
Id = 527,
Description = "Разрешение на удаление инструкций",
Name = "Manual.delete"
},
new
{
Id = 528,
Description = "Разрешение на удаление контакта",
Name = "WellContact.delete"
});
});
@ -3923,6 +3996,11 @@ namespace AsbCloudDb.Migrations
{
IdUserRole = 1,
IdPermission = 527
},
new
{
IdUserRole = 1,
IdPermission = 528
});
});
@ -7700,6 +7778,25 @@ namespace AsbCloudDb.Migrations
b.Navigation("CompanyType");
});
modelBuilder.Entity("AsbCloudDb.Model.Contact", b =>
{
b.HasOne("AsbCloudDb.Model.CompanyType", "CompanyType")
.WithMany("Contacts")
.HasForeignKey("IdCompanyType")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("AsbCloudDb.Model.Well", "Well")
.WithMany("Contacts")
.HasForeignKey("IdWell")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CompanyType");
b.Navigation("Well");
});
modelBuilder.Entity("AsbCloudDb.Model.DailyReport.DailyReport", b =>
{
b.HasOne("AsbCloudDb.Model.Well", "Well")
@ -8543,6 +8640,8 @@ namespace AsbCloudDb.Migrations
modelBuilder.Entity("AsbCloudDb.Model.CompanyType", b =>
{
b.Navigation("Companies");
b.Navigation("Contacts");
});
modelBuilder.Entity("AsbCloudDb.Model.Deposit", b =>
@ -8624,6 +8723,8 @@ namespace AsbCloudDb.Migrations
modelBuilder.Entity("AsbCloudDb.Model.Well", b =>
{
b.Navigation("Contacts");
b.Navigation("DrillingProgramParts");
b.Navigation("RelationCompaniesWells");

View File

@ -82,7 +82,7 @@ namespace AsbCloudDb.Model
public DbSet<NotificationCategory> NotificationCategories => Set<NotificationCategory>();
public DbSet<Manual> Manuals => Set<Manual>();
public DbSet<ManualDirectory> ManualDirectories => Set<ManualDirectory>();
public DbSet<Contact> Contacts => Set<Contact>();
public AsbCloudDbContext() : base()
{

View File

@ -21,5 +21,8 @@ namespace AsbCloudDb.Model
[InverseProperty(nameof(Company.CompanyType))]
public virtual ICollection<Company> Companies { get; set; } = null!;
[InverseProperty(nameof(Contact.CompanyType))]
public virtual ICollection<Contact> Contacts { get; set; } = null!;
}
}

View File

@ -0,0 +1,51 @@
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace AsbCloudDb.Model
{
[Table("t_contact"), Comment("Контакты")]
public partial class Contact : IId
{
[Key]
[Column("id")]
public int Id { get; set; }
[Column("id_company_type"), Comment("вид деятельности")]
[StringLength(255)]
public int IdCompanyType { get; set; }
[Column("id_well"), Comment("ключ скважины")]
[StringLength(255)]
public int IdWell { get; set; }
[Column("full_name"), Comment("ФИО")]
[StringLength(255)]
public string FullName { get; set; } = string.Empty;
[Column("email"), Comment("email")]
[StringLength(255)]
public string Email { get; set; } = string.Empty;
[Column("phone"), Comment("номер телефона")]
[StringLength(50)]
public string Phone { get; set; } = string.Empty;
[Column("position"), Comment("должность")]
[StringLength(255)]
public string Position { get; set; } = string.Empty;
[Column("company"), Comment("компания")]
[StringLength(255)]
public string Company { get; set; } = string.Empty;
[ForeignKey(nameof(IdCompanyType))]
[InverseProperty(nameof(Model.CompanyType.Contacts))]
public virtual CompanyType CompanyType { get; set; } = null!;
[ForeignKey(nameof(IdWell))]
[InverseProperty(nameof(Model.Well.Contacts))]
public virtual Well Well { get; set; } = null!;
}
}

View File

@ -159,7 +159,8 @@
new (){ Id = 525, Name = "ProcessMap.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="Разрешение на удаление контакта"},
};
}
}

View File

@ -75,6 +75,7 @@ namespace AsbCloudDb.Model
DbSet<NotificationCategory> NotificationCategories { get; }
DbSet<Manual> Manuals { get; }
DbSet<ManualDirectory> ManualDirectories { get; }
DbSet<Contact> Contacts { get; }
DatabaseFacade Database { get; }
Task<int> RefreshMaterializedViewAsync(string mwName, CancellationToken token);

View File

@ -65,5 +65,8 @@ namespace AsbCloudDb.Model
[InverseProperty(nameof(DrillingProgramPart.Well))]
public virtual ICollection<DrillingProgramPart> DrillingProgramParts { get; set; } = null!;
[InverseProperty(nameof(Contact.Well))]
public virtual ICollection<Contact> Contacts { get; set; } = null!;
}
}

View File

@ -1,5 +1,6 @@
using AsbCloudApp.Data;
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -74,7 +75,8 @@ public abstract class Work : BackgroundWorkDto
}
catch (Exception exception)
{
SetLastError(exception.Message);
var message = FormatExceptionMessage(exception);
SetLastError(message);
if (OnErrorAsync is not null)
{
var task = Task.Run(
@ -86,6 +88,31 @@ public abstract class Work : BackgroundWorkDto
return false;
}
private static string FormatExceptionMessage(Exception exception)
{
var firstException = FirstException(exception);
var message = new StringBuilder();
if (firstException != exception)
{
message.Append("top exception:");
message.AppendLine(exception.Message);
message.Append("inner exception:");
message.AppendLine(firstException.Message);
}
else
message.AppendLine(firstException.Message);
message.AppendLine(exception.StackTrace);
return message.ToString();
}
private static Exception FirstException(Exception exception)
{
if (exception.InnerException is not null)
return FirstException(exception.InnerException);
return exception;
}
/// <summary>
/// делегат фоновой работы
/// </summary>

View File

@ -22,6 +22,7 @@ public class WorkLimitingParameterCalc : Work
protected override async Task Action(string id, IServiceProvider services, Action<string, double?> onProgressCallback, CancellationToken token)
{
using var db = services.GetRequiredService<IAsbCloudDbContext>();
db.Database.SetCommandTimeout(TimeSpan.FromMinutes(5));
var lastDetectedDates = await db.LimitingParameter
.GroupBy(o => o.IdTelemetry)
.Select(g => new

View File

@ -52,7 +52,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
var db = provider.GetRequiredService<IAsbCloudDbContext>();
await instance.InitializeCacheFromDBAsync<TEntity>(db, onProgress, token);
});
work.Timeout = TimeSpan.FromMinutes(15);
worker.WorkStore.RunOnceQueue.Enqueue(work);
}
instance.provider = provider;
@ -159,7 +159,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
isLoading = true;
var defaultTimeout = db.Database.GetCommandTimeout();
db.Database.SetCommandTimeout(TimeSpan.FromSeconds(90));
db.Database.SetCommandTimeout(TimeSpan.FromMinutes(5));
Well[] wells = await db.Set<Well>()
.Include(well => well.Telemetry)

View File

@ -32,7 +32,7 @@ public class WorkSubsystemOperationTimeCalc: Work
protected override async Task Action(string id, IServiceProvider services, Action<string, double?> onProgressCallback, CancellationToken token)
{
using var db = services.GetRequiredService<IAsbCloudDbContext>();
db.Database.SetCommandTimeout(TimeSpan.FromMinutes(5));
var lastDetectedDates = await db.SubsystemOperationTimes
.GroupBy(o => o.IdTelemetry)
.Select(g => new
@ -61,7 +61,7 @@ public class WorkSubsystemOperationTimeCalc: Work
var i = 0d;
foreach (var item in telemetryLastDetectedDates)
{
onProgressCallback($"Start hanling telemetry: {item.IdTelemetry} from {item.LastDate}", i++ / count);
onProgressCallback($"Start handling telemetry: {item.IdTelemetry} from {item.LastDate}", i++ / count);
var newOperationsSaub = await OperationTimeSaubAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token);
if (newOperationsSaub?.Any() == true)
{
@ -278,7 +278,8 @@ public class WorkSubsystemOperationTimeCalc: Work
.Where(d => d.WellDepth != null)
.Where(d => d.WellDepth > 0)
.GroupBy(d => Math.Ceiling(d.WellDepth ?? 0 * 10))
.Select(g => new {
.Select(g => new
{
DateMin = g.Min(d => d.DateTime),
DepthMin = g.Min(d => d.WellDepth) ?? 0,
})

View File

@ -1,5 +1,6 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.User;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using Mapster;
@ -20,71 +21,84 @@ namespace AsbCloudInfrastructure.Services
this.db = db;
}
public async Task<IEnumerable<CompanyWithUsersDto>> GetAsync(int wellId, int contactTypeId, CancellationToken token)
public async Task<IEnumerable<ContactDto>> GetAllAsync(int wellId, int contactTypeId, CancellationToken token)
{
var query = db.Companies
var query = db.Contacts
.Where(c => c.IdCompanyType == contactTypeId)
.Where(c => c.RelationCompaniesWells.Any(rc => rc.IdWell == wellId))
.Select(c => new CompanyWithUsersDto()
{
Caption = c.Caption,
Id = c.Id,
Users = c.Users
.Where(u => u.IdState == 1)
.OrderBy(u => u.Surname)
.Select(u => new UserContactDto()
{
Id = u.Id,
Name = u.Name,
Patronymic = u.Patronymic,
Surname = u.Surname,
Company = u.Company.Adapt<CompanyDto>(),
Email = u.Email,
Phone = u.Phone,
Position = u.Position,
IsContact = u.RelationContactsWells.Any(rel => rel.IdWell == wellId)
})
});
.Where(c => c.IdWell == wellId)
.Select(c => c.Adapt<ContactDto>());
var entities = await query.AsNoTracking()
.ToArrayAsync(token)
.ConfigureAwait(false);
.ToArrayAsync(token);
return entities;
}
public async Task<IEnumerable<CompanyTypeDto>> GetTypesAsync(int idWell, CancellationToken token)
public async Task<ContactDto?> GetAsync(int idWell, int id, CancellationToken token)
{
var dbContact = await GetContact(idWell, id, token);
var result = dbContact?.Adapt<ContactDto>();
return result;
}
public async Task<IEnumerable<CompanyTypeDto>> GetTypesAsync(CancellationToken token)
{
var query = db.CompaniesTypes
.Where(t => t.IsContact)
.Where(t => t.Companies.Any(c => c.Users.Any() && c.RelationCompaniesWells.Any(w => w.IdWell == idWell)))
.OrderBy(t => t.Order);
var entities = await query.AsNoTracking()
.ToArrayAsync(token)
.ConfigureAwait(false);
.ToArrayAsync(token);
var dtos = entities.Adapt<IEnumerable<CompanyTypeDto>>();
return dtos;
}
public async Task<int> UpdateRangeAsync(int idWell, int contactTypeId, IEnumerable<int> userIds, CancellationToken token)
public async Task<int> InsertAsync(ContactDto contactDto, CancellationToken token)
{
var items = db.RelationContactsWells
.Where(x => x.IdWell == idWell && x.User.Company.IdCompanyType == contactTypeId);
var entity = contactDto.Adapt<Contact>();
var wellContacts = userIds.Select(userId => new RelationContactWell()
db.Contacts.Add(entity);
await db.SaveChangesAsync(token).ConfigureAwait(false);
return entity.Id;
}
public async Task<int> UpdateAsync(ContactDto contactDto, CancellationToken token)
{
IdWell = idWell,
IdUser = userId,
});
var dbContact = await GetContact(contactDto.IdWell, contactDto.Id, token);
if (dbContact is null)
throw new ForbidException("Contact doesn't exist");
db.RelationContactsWells.RemoveRange(items);
db.RelationContactsWells.AddRange(wellContacts);
var entity = contactDto.Adapt<Contact>();
db.Contacts.Update(entity);
return await db.SaveChangesAsync(token)
.ConfigureAwait(false);
return await db.SaveChangesAsync(token);
}
public async Task<int> DeleteAsync(int idWell, int id, CancellationToken token)
{
var dbContact = await GetContact(idWell, id, token);
if (dbContact is null)
throw new ForbidException("Contact doesn't exist");
db.Contacts.Remove(dbContact);
return await db.SaveChangesAsync(token);
}
private async Task<Contact?> GetContact(int idWell, int idContact, CancellationToken token)
{
var contact = await db.Contacts
.Where(c => c.IdWell == idWell)
.Where(c => c.Id == idContact)
.AsNoTracking()
.FirstOrDefaultAsync(token);
return contact;
}
}
}

View File

@ -1,7 +1,6 @@
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.DetectOperations;
using AsbCloudInfrastructure.Services.Subsystems;
using AsbCloudInfrastructure.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
@ -12,6 +11,7 @@ using System.Threading;
using AsbCloudInfrastructure.Background;
using AsbCloudApp.Data.SAUB;
using AsbCloudInfrastructure.Services.SAUB;
using AsbCloudInfrastructure.Services.Subsystems;
namespace AsbCloudInfrastructure
{
@ -23,7 +23,7 @@ namespace AsbCloudInfrastructure
var provider = scope.ServiceProvider;
var context = provider.GetRequiredService<IAsbCloudDbContext>();
context.Database.SetCommandTimeout(TimeSpan.FromSeconds(2 * 60));
context.Database.SetCommandTimeout(TimeSpan.FromMinutes(5));
context.Database.Migrate();
// TODO: Сделать инициализацию кеша телеметрии более явной.

View File

@ -33,8 +33,11 @@ namespace AsbCloudWebApi.Controllers
[HttpGet("Current")]
public IActionResult GetCurrent()
{
var result = (BackgroundWorkDto?)backgroundWorker.CurrentWork;
return Ok(result);
var work = backgroundWorker.CurrentWork;
if (work == null)
return NoContent();
return Ok(work);
}
[HttpGet("Failed")]

View File

@ -0,0 +1,72 @@
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace AsbCloudWebApi.Controllers
{
/// <summary>
/// Имитирует разные типы ответа сервера
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class MockController : ControllerBase
{
/// <summary>
/// имитирует http-400
/// </summary>
[HttpGet("400")]
[ProducesResponseType(typeof(ValidationProblemDetails), (int)System.Net.HttpStatusCode.BadRequest)]
public IActionResult Get400([FromQuery, Required]IDictionary<string, string> args)
{
var errors = new Dictionary<string, string[]>();
foreach (var arg in args)
{
var countOfErrors = ((arg.Key + arg.Value).Length % 3) + 1;
var errorsText = Enumerable.Range(0, countOfErrors)
.Select(i => $"{arg.Value} не соответствует критериям проверки № {i}");
errors.Add(arg.Key, errorsText.ToArray());
}
if (errors.Any())
{
var problem = new ValidationProblemDetails(errors);
return BadRequest(problem);
}
else
{
var problem = new ValidationProblemDetails { Detail = "at least one argument must be provided" };
return BadRequest(problem);
}
}
/// <summary>
/// имитирует http-403
/// </summary>
[HttpGet("403")]
public IActionResult Get403()
{
return Forbid();
}
/// <summary>
/// имитирует http-401
/// </summary>
[HttpGet("401")]
public IActionResult Get401()
{
return Unauthorized();
}
/// <summary>
/// имитирует http-500
/// </summary>
[HttpGet("500")]
public IActionResult Get500()
{
throw new System.Exception("Это тестовое исключение");
}
}
}

View File

@ -1,10 +1,10 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.User;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading;
using System.Threading.Tasks;
@ -13,6 +13,7 @@ namespace AsbCloudWebApi.Controllers
/// <summary>
/// контроллер с контактной информацией по скважине
/// </summary>
[Route("api/well/{idWell}/[controller]")]
[ApiController]
[Authorize]
public class WellContactController : ControllerBase
@ -29,66 +30,119 @@ namespace AsbCloudWebApi.Controllers
/// <summary>
/// получение списка типов контактов
/// </summary>
/// <param name="idWell">ключ скважины</param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("api/well/{idWell}/contacts/types")]
[HttpGet("type")]
[ProducesResponseType(typeof(IEnumerable<CompanyTypeDto>), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> GetTypesAsync(int idWell, CancellationToken token)
public async Task<IActionResult> GetTypesAsync(CancellationToken token)
{
var result = await wellContactsRepository.GetTypesAsync(idWell, token).ConfigureAwait(false);
var result = await wellContactsRepository.GetTypesAsync(token);
return Ok(result);
}
/// <summary>
/// Получение пользователей по типу контакта и ключу скважины
/// Получение контактов по типу контакта и ключу скважины
/// </summary>
/// <param name="idWell">ключ скважины</param>
/// <param name="contactTypeId">тип контакта</param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("api/well/{idWell}/contactType/{contactTypeId}")]
[ProducesResponseType(typeof(IEnumerable<CompanyWithUsersDto>), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> GetAsync(int idWell, int contactTypeId, CancellationToken token)
[HttpGet("type/{contactTypeId}")]
[ProducesResponseType(typeof(IEnumerable<ContactDto>), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> GetAllAsync(int idWell, int contactTypeId, CancellationToken token)
{
if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false))
return Forbid();
var result = await wellContactsRepository.GetAsync(idWell, contactTypeId, token).ConfigureAwait(false);
var result = await wellContactsRepository.GetAllAsync(idWell, contactTypeId, token);
return Ok(result);
}
/// <summary>
/// Получение контакта по ключу
/// </summary>
/// <param name="idWell">ключ скважины</param>
/// <param name="id">ключ контакта</param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("{id}")]
[ProducesResponseType(typeof(ContactDto), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> GetAsync(int idWell, int id, CancellationToken token)
{
if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false))
return Forbid();
var result = await wellContactsRepository.GetAsync(idWell, id, token);
return Ok(result);
}
/// <summary>
/// добавление нового контакта
/// </summary>
/// <param name="contactDto">контакт</param>
/// <param name="token"></param>
/// <returns></returns>
[HttpPost]
[Permission]
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> InsertAsync(
[FromBody] ContactDto contactDto,
CancellationToken token)
{
if (!await CanUserAccessToWellAsync(contactDto.IdWell, token).ConfigureAwait(false))
return Forbid();
var result = await wellContactsRepository.InsertAsync(contactDto, token);
return Ok(result);
}
/// <summary>
/// изменение контакта
/// </summary>
/// <param name="contactDto">контакт</param>
/// <param name="token"></param>
/// <returns></returns>
[HttpPut]
[Permission]
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> UpdateAsync(
[FromBody] ContactDto contactDto,
CancellationToken token)
{
if (!await CanUserAccessToWellAsync(contactDto.IdWell, token).ConfigureAwait(false))
return Forbid();
var result = await wellContactsRepository.UpdateAsync(contactDto, token);
return Ok(result);
}
/// <summary>
/// Создание контактов по ключу скважины, типу контакта и ключам пользователей
/// Удаление контакта
/// </summary>
/// <param name="idWell">ключ скважины</param>
/// <param name="contactTypeId">ключ типа контакта</param>
/// <param name="userIds">ключи пользователей</param>
/// <param name="token"></param>
/// <returns></returns>
[HttpPost("api/well/{idWell}/contactType/{contactTypeId}")]
/// <param name="id">id контакта</param>
/// <param name="token">Токен отмены задачи</param>
/// <returns>Количество удаленных из БД строк</returns>
[HttpDelete("{id}")]
[Permission]
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> UpdateRangeAsync(
[Range(1, int.MaxValue, ErrorMessage = "Id скважины не может быть меньше 1")] int idWell,
[Range(1, int.MaxValue, ErrorMessage = "Id типа контакта не может быть меньше 1")] int contactTypeId,
[FromBody] IEnumerable<int> userIds,
CancellationToken token)
public async Task<IActionResult> DeleteAsync(int idWell, int id, CancellationToken token)
{
if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false))
return Forbid();
var result = await wellContactsRepository.UpdateRangeAsync(idWell, contactTypeId, userIds, token).ConfigureAwait(false);
var result = await wellContactsRepository.DeleteAsync(idWell, id, token);
return Ok(result);
}
private async Task<bool> CanUserAccessToWellAsync(int idWell, CancellationToken token)
{
int? idCompany = User.GetCompanyId();
return idCompany is not null && await wellService.IsCompanyInvolvedInWellAsync((int)idCompany,
idWell, token).ConfigureAwait(false);
}
return idCompany is not null && await wellService.IsCompanyInvolvedInWellAsync((int)idCompany, idWell, token);
}
}
}

View File

@ -0,0 +1,67 @@
@baseUrl = http://127.0.0.1:5000
@contentType = application/json
@auth = Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiZGV2IiwiaWRDb21wYW55IjoiMSIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6InJvb3QiLCJuYmYiOjE2OTc0MzcwMzEsImV4cCI6MTcyODk5NDYzMSwiaXNzIjoiYSIsImF1ZCI6ImEifQ.vB7Qb3K9gG77iP8y25zB3RcZIQk9cHkq3I1SkcooYJs
@uid = 20210101_000000000
@id = 1
@idWell = 1
@contactTypeId = 1
### ïîëó÷åíèå ñïèñêà òèïîâ êîíòàêòîâ
GET {{baseUrl}}/api/well/{{idWell}}/WellContact/type
Content-Type: {{contentType}}
accept: */*
Authorization: {{auth}}
### Ïîëó÷åíèå êîíòàêòîâ ïî òèïó êîíòàêòà è êëþ÷ó ñêâàæèíû
GET {{baseUrl}}/api/well/{{idWell}}/WellContact/type/{{contactTypeId}}
Content-Type: {{contentType}}
accept: */*
Authorization: {{auth}}
### Ïîëó÷åíèå êîíòàêòà ïî êëþ÷ó
GET {{baseUrl}}/api/well/{{idWell}}/WellContact/{{id}}
Content-Type: {{contentType}}
accept: */*
Authorization: {{auth}}
### äîáàâëåíèå íîâîãî êîíòàêòà
POST {{baseUrl}}/api/well/{{idWell}}/WellContact
Content-Type: {{contentType}}
accept: */*
Authorization: {{auth}}
{
"id" : 2,
"IdCompanyType" : 1,
"IdWell": 1,
"FullName": "batman",
"Email": "aa@aa.aa",
"Phone": "80000000000",
"Position": "Ïîâàð",
"Company": "Ìèøëåí ëòä"
}
### èçìåíåíèå êîíòàêòà
PUT {{baseUrl}}/api/well/{{idWell}}/WellContact
Content-Type: {{contentType}}
accept: */*
Authorization: {{auth}}
{
"id" : 2,
"IdCompanyType" : 1,
"IdWell": 1,
"FullName": "Áýòìàí Ñóïåðìåíîâè÷",
"Email": "bb@bb.bb",
"Phone": "80000000001",
"Position": "Ïîâàð",
"Company": "Ìèøëåí ëòä"
}
### Óäàëåíèå êîíòàêòà
DELETE {{baseUrl}}/api/well/{{idWell}}/WellContact/{{id}}
Content-Type: {{contentType}}
accept: */*
Authorization: {{auth}}