forked from ddrilling/AsbCloudServer
6c2feefff9
1. Добавлен сервис для суточных отчётов 2. Добавлены юнит тесты для сервиса с суточными отчётами
378 lines
13 KiB
C#
378 lines
13 KiB
C#
using AsbCloudApp.Data.DailyReport;
|
||
using AsbCloudApp.Exceptions;
|
||
using AsbCloudApp.Repositories;
|
||
using AsbCloudApp.Services;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using AsbCloudApp.Data;
|
||
using AsbCloudApp.Data.DailyReport.Blocks;
|
||
using AsbCloudApp.Data.DailyReport.Blocks.Sign;
|
||
using AsbCloudApp.Data.DailyReport.Blocks.Subsystems;
|
||
using AsbCloudApp.Data.DailyReport.Blocks.TimeBalance;
|
||
using AsbCloudApp.Data.DailyReport.Blocks.WellOperation;
|
||
using AsbCloudApp.Requests;
|
||
using AsbCloudApp.Services.DailyReport;
|
||
using AsbCloudApp.Services.ProcessMaps.WellDrilling;
|
||
using AsbCloudApp.Services.Subsystems;
|
||
using AsbCloudDb.Model;
|
||
using Mapster;
|
||
|
||
namespace AsbCloudInfrastructure.Services.DailyReport;
|
||
|
||
public class DailyReportService : IDailyReportService
|
||
{
|
||
private readonly IWellService wellService;
|
||
private readonly ITrajectoryFactRepository trajectoryFactRepository;
|
||
private readonly IDailyReportRepository dailyReportRepository;
|
||
private readonly IScheduleRepository scheduleRepository;
|
||
private readonly IWellOperationRepository wellOperationRepository;
|
||
private readonly ISubsystemOperationTimeService subsystemOperationTimeService;
|
||
private readonly IProcessMapReportWellDrillingService processMapReportWellDrillingService;
|
||
private readonly IDetectedOperationService detectedOperationService;
|
||
|
||
public DailyReportService(IWellService wellService,
|
||
ITrajectoryFactRepository trajectoryFactRepository,
|
||
IDailyReportRepository dailyReportRepository,
|
||
IScheduleRepository scheduleRepository,
|
||
IWellOperationRepository wellOperationRepository,
|
||
ISubsystemOperationTimeService subsystemOperationTimeService,
|
||
IProcessMapReportWellDrillingService processMapReportWellDrillingService,
|
||
IDetectedOperationService detectedOperationService)
|
||
{
|
||
this.wellService = wellService;
|
||
this.trajectoryFactRepository = trajectoryFactRepository;
|
||
this.dailyReportRepository = dailyReportRepository;
|
||
this.scheduleRepository = scheduleRepository;
|
||
this.wellOperationRepository = wellOperationRepository;
|
||
this.subsystemOperationTimeService = subsystemOperationTimeService;
|
||
this.processMapReportWellDrillingService = processMapReportWellDrillingService;
|
||
this.detectedOperationService = detectedOperationService;
|
||
}
|
||
|
||
public async Task<int> InsertAsync(int idWell, DateTime dateStart, CancellationToken cancellationToken)
|
||
{
|
||
if (await dailyReportRepository.AnyAsync(idWell, dateStart, cancellationToken))
|
||
throw new ArgumentInvalidException(nameof(dateStart), "Суточный отчёт уже существует");
|
||
|
||
var dailyReport = new DailyReportDto
|
||
{
|
||
IdWell = idWell,
|
||
DateStart = dateStart,
|
||
};
|
||
|
||
return await dailyReportRepository.InsertAsync(dailyReport, cancellationToken);
|
||
}
|
||
|
||
public async Task<int> UpdateBlockAsync<TBlock>(int idDailyReport, int idUser, TBlock editableBlock,
|
||
CancellationToken cancellationToken)
|
||
where TBlock : EditableBlock
|
||
{
|
||
var dailyReport = await dailyReportRepository.GetOrDefaultAsync(idDailyReport, cancellationToken)
|
||
?? throw new ArgumentInvalidException(nameof(idDailyReport), $"Суточный отчёт с Id: {idDailyReport} не найден");
|
||
|
||
editableBlock.IdUser = idUser;
|
||
|
||
dailyReport.DateLastUpdate = DateTime.UtcNow;
|
||
|
||
switch (editableBlock)
|
||
{
|
||
case SubsystemBlockDto subsystemBlock:
|
||
dailyReport.SubsystemBlock = subsystemBlock;
|
||
break;
|
||
case TimeBalanceBlockDto timeBalanceBlock:
|
||
dailyReport.TimeBalanceBlock = timeBalanceBlock;
|
||
break;
|
||
case SignBlockDto signBlock:
|
||
dailyReport.SignBlock = signBlock;
|
||
break;
|
||
}
|
||
|
||
return await dailyReportRepository.UpdateAsync(dailyReport, cancellationToken);
|
||
}
|
||
|
||
public async Task<DailyReportDto> GetAsync(int idWell, DateTime dateStart, CancellationToken cancellationToken)
|
||
{
|
||
var well = await wellService.GetOrDefaultAsync(idWell, cancellationToken);
|
||
|
||
if (well is null)
|
||
throw new ArgumentInvalidException($"Скважина с Id: {idWell} не найдена", nameof(idWell));
|
||
|
||
var dailyReport = await dailyReportRepository.GetOrDefaultAsync(idWell, dateStart, cancellationToken) ?? new DailyReportDto
|
||
{
|
||
DateStart = dateStart.Date,
|
||
IdWell = well.Id
|
||
};
|
||
|
||
var factWellOperations = await GetFactWellOperationsAsync(idWell, dailyReport.DateStart, dailyReport.DateEnd,
|
||
cancellationToken);
|
||
|
||
dailyReport.WellName = well.Caption;
|
||
dailyReport.WellType = well.WellType;
|
||
dailyReport.Cluster = well.Cluster;
|
||
dailyReport.Deposit = well.Deposit;
|
||
dailyReport.Customer = well.Companies.FirstOrDefault(c => c.IdCompanyType == 1)?.Caption;
|
||
dailyReport.Contractor = well.Companies.FirstOrDefault(c => c.IdCompanyType == 2)?.Caption;
|
||
dailyReport.DepthStart = factWellOperations.Min(o => o.DepthStart);
|
||
dailyReport.DepthEnd = factWellOperations.Max(o => o.DepthEnd);
|
||
|
||
await UpdateTimeBalanceBlockAsync(dailyReport, factWellOperations, cancellationToken);
|
||
await UpdateSubsystemBlockAsync(dailyReport, cancellationToken);
|
||
|
||
await AddTrajectoryBlockAsync(dailyReport, cancellationToken);
|
||
await AddScheduleBlockAsync(dailyReport, cancellationToken);
|
||
await AddProcessMapWellDrillingBlockAsync(dailyReport, cancellationToken);
|
||
|
||
AddFactWellOperationBlock(dailyReport, factWellOperations);
|
||
|
||
return dailyReport;
|
||
}
|
||
|
||
public async Task<PaginationContainer<DailyReportDto>> GetAsync(int idWell, FileReportRequest request,
|
||
CancellationToken cancellationToken)
|
||
{
|
||
var result = new PaginationContainer<DailyReportDto>
|
||
{
|
||
Skip = request.Skip ?? 0,
|
||
Take = request.Take ?? 10,
|
||
Items = Enumerable.Empty<DailyReportDto>()
|
||
};
|
||
|
||
var well = await wellService.GetOrDefaultAsync(idWell, cancellationToken)
|
||
?? throw new ArgumentInvalidException(nameof(idWell), "Скважина не найдена");
|
||
|
||
var datesRange = await GetDatesRangeAsync(idWell, cancellationToken);
|
||
|
||
if (datesRange is null)
|
||
return result;
|
||
|
||
var dailyReports = new List<DailyReportDto>();
|
||
|
||
var existingDailyReports = await dailyReportRepository.GetAsync(idWell, request, cancellationToken);
|
||
|
||
if (request.GeDate.HasValue)
|
||
{
|
||
var startDate = new DateTime(request.GeDate.Value.Year, request.GeDate.Value.Month,
|
||
request.GeDate.Value.Day);
|
||
|
||
if (startDate.Date >= datesRange.From.Date)
|
||
datesRange.From = startDate;
|
||
}
|
||
|
||
if (request.LeDate.HasValue)
|
||
{
|
||
var finishDate = new DateTime(request.LeDate.Value.Year, request.LeDate.Value.Month,
|
||
request.LeDate.Value.Day);
|
||
|
||
if (finishDate.Date <= datesRange.To.Date)
|
||
datesRange.To = finishDate;
|
||
}
|
||
|
||
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));
|
||
|
||
|
||
if (request.SortFields?.Contains("DateStart desc") == true)
|
||
{
|
||
for (var day = result.Skip; day - result.Skip < result.Take && datesRange.To.AddDays(-day) >= datesRange.From; day++)
|
||
{
|
||
var dateStart = datesRange.To.AddDays(-day);
|
||
|
||
AddDailyReport(dateStart);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
for (var day = result.Skip; day - result.Skip < result.Take && datesRange.From.AddDays(day) <= datesRange.To; day++)
|
||
{
|
||
var dateStart = datesRange.From.AddDays(day);
|
||
|
||
AddDailyReport(dateStart);
|
||
}
|
||
}
|
||
|
||
result.Items = dailyReports;
|
||
|
||
return result;
|
||
|
||
void AddDailyReport(DateTime dateStart)
|
||
{
|
||
var existingDailyReport = existingDailyReports.FirstOrDefault(d => d.IdWell == idWell &&
|
||
d.DateStart == dateStart);
|
||
|
||
if (existingDailyReport is not null)
|
||
{
|
||
dailyReports.Add(existingDailyReport);
|
||
return;
|
||
}
|
||
|
||
dailyReports.Add(new DailyReportDto
|
||
{
|
||
DateStart = dateStart,
|
||
IdWell = well.Id
|
||
});
|
||
}
|
||
}
|
||
|
||
public async Task<DatesRangeDto?> GetDatesRangeAsync(int idWell, CancellationToken cancellationToken)
|
||
{
|
||
var factOperations = await GetFactWellOperationsAsync(idWell, null, null,
|
||
cancellationToken);
|
||
|
||
if (!factOperations.Any())
|
||
return null;
|
||
|
||
return new DatesRangeDto
|
||
{
|
||
From = factOperations.Min(o => o.DateStart).Date,
|
||
To = factOperations.Max(o => o.DateStart).Date
|
||
};
|
||
}
|
||
|
||
private async Task UpdateTimeBalanceBlockAsync(DailyReportDto dailyReport, IEnumerable<WellOperationDto> factWellOperations,
|
||
CancellationToken cancellationToken)
|
||
{
|
||
const int idWellOperationSlipsTime = 5011;
|
||
|
||
if (dailyReport.TimeBalanceBlock is not null)
|
||
{
|
||
dailyReport.TimeBalanceBlock.CountWellOperationSlipsTime = (await detectedOperationService.GetAsync(
|
||
new DetectedOperationRequest
|
||
{
|
||
IdsCategories = new[] { idWellOperationSlipsTime },
|
||
IdWell = dailyReport.IdWell,
|
||
GtDate = dailyReport.DateStart,
|
||
LtDate = dailyReport.DateEnd
|
||
}, cancellationToken))?.Stats.Sum(s => s.Count);
|
||
|
||
dailyReport.TimeBalanceBlock.WellDepthFact = factWellOperations
|
||
.Where(o => o.IdWellSectionType == dailyReport.TimeBalanceBlock.IdSection)
|
||
.Sum(o => o.DepthEnd = o.DepthStart);
|
||
}
|
||
}
|
||
|
||
private async Task AddTrajectoryBlockAsync(DailyReportDto dailyReport, CancellationToken cancellationToken)
|
||
{
|
||
var trajectory = (await trajectoryFactRepository.GetAsync(new TrajectoryGeoFactRequest
|
||
{
|
||
IdWell = dailyReport.IdWell,
|
||
GeDate = dailyReport.DateStart,
|
||
LtDate = dailyReport.DateEnd
|
||
}, cancellationToken)).LastOrDefault();
|
||
|
||
dailyReport.TrajectoryBlock = new TrajectoryBlockDto
|
||
{
|
||
WellboreDepth = trajectory?.WellboreDepth,
|
||
VerticalDepth = trajectory?.VerticalDepth,
|
||
ZenithAngle = trajectory?.ZenithAngle,
|
||
AzimuthGeo = trajectory?.AzimuthGeo
|
||
};
|
||
}
|
||
|
||
private async Task AddScheduleBlockAsync(DailyReportDto dailyReport, CancellationToken cancellationToken) =>
|
||
dailyReport.ScheduleBlock = (await scheduleRepository.GetAsync(dailyReport.IdWell, dailyReport.DateStart, cancellationToken))
|
||
.Select(s => new ScheduleRecordDto
|
||
{
|
||
ShiftStart = s.ShiftStart,
|
||
ShiftEnd = s.ShiftEnd,
|
||
Name = s.Driller?.Name,
|
||
Surname = s.Driller?.Surname,
|
||
Patronymic = s.Driller?.Patronymic
|
||
});
|
||
|
||
private async Task UpdateSubsystemBlockAsync(DailyReportDto dailyReport, CancellationToken cancellationToken)
|
||
{
|
||
dailyReport.SubsystemBlock ??= new SubsystemBlockDto();
|
||
|
||
dailyReport.SubsystemBlock.Modules = await GetModulesAsync();
|
||
|
||
async Task<IEnumerable<SubsystemRecordDto>> GetModulesAsync()
|
||
{
|
||
var modules = new List<SubsystemRecordDto>();
|
||
|
||
var statSubsystemOperationTimePerDaily = (await subsystemOperationTimeService.GetStatAsync(new SubsystemOperationTimeRequest
|
||
{
|
||
IdWell = dailyReport.IdWell,
|
||
GtDate = dailyReport.DateStart,
|
||
LtDate = dailyReport.DateEnd
|
||
}, cancellationToken)).Select(s =>
|
||
{
|
||
var subsystemRecord = s.Adapt<SubsystemRecordDto>();
|
||
subsystemRecord.IdTimeInterval = 1;
|
||
|
||
return subsystemRecord;
|
||
});
|
||
|
||
var statSubsystemOperationTimePerWell = (await subsystemOperationTimeService.GetStatAsync(new SubsystemOperationTimeRequest
|
||
{
|
||
IdWell = dailyReport.IdWell
|
||
}, cancellationToken)).Select(s =>
|
||
{
|
||
var subsystemRecord = s.Adapt<SubsystemRecordDto>();
|
||
subsystemRecord.IdTimeInterval = 2;
|
||
|
||
return subsystemRecord;
|
||
});
|
||
|
||
modules.AddRange(statSubsystemOperationTimePerDaily);
|
||
modules.AddRange(statSubsystemOperationTimePerWell);
|
||
|
||
if (dailyReport.SubsystemBlock?.Modules != null && dailyReport.SubsystemBlock.Modules.Any())
|
||
modules.AddRange(dailyReport.SubsystemBlock.Modules);
|
||
|
||
return modules;
|
||
}
|
||
}
|
||
|
||
private async Task AddProcessMapWellDrillingBlockAsync(DailyReportDto dailyReport, CancellationToken cancellationToken)
|
||
{
|
||
dailyReport.ProcessMapWellDrillingBlock = (await processMapReportWellDrillingService.GetAsync(dailyReport.IdWell,
|
||
cancellationToken)).Where(p => p.DateStart >= dailyReport.DateStart &&
|
||
p.DateStart <= dailyReport.DateEnd &&
|
||
p.IdMode.HasValue)
|
||
.GroupBy(p => p.IdMode)
|
||
.Select(g => new ProcessMapWellDrillingRecordDto
|
||
{
|
||
IdMode = g.Key!.Value,
|
||
WellBoreDepth = g.Sum(p => p.DeltaDepth),
|
||
Rop = new PlanFactDto<double?>
|
||
{
|
||
Plan = g.Sum(p => p.RopPlan),
|
||
Fact = g.Sum(p => p.RopFact)
|
||
},
|
||
MechDrillingHours = g.Sum(p => p.MechDrillingHours)
|
||
});
|
||
}
|
||
|
||
private void AddFactWellOperationBlock(DailyReportDto dailyReport, IEnumerable<WellOperationDto> factWellOperations)
|
||
{
|
||
const int idWellOperationCategoryDrilling = 4001;
|
||
|
||
dailyReport.FactWellOperationBlock = new WellOperationBlockDto
|
||
{
|
||
WellOperations = factWellOperations.GroupBy(o => o.IdParentCategory)
|
||
.Select(g => new WellOperationRecordDto
|
||
{
|
||
IdWellCategory = g.Key,
|
||
DurationHours = g.Sum(o => o.DurationHours)
|
||
}),
|
||
|
||
DurationHoursDrillingPerSection = factWellOperations
|
||
.Where(o => o.IdParentCategory is idWellOperationCategoryDrilling)
|
||
.Sum(o => o.DurationHours)
|
||
};
|
||
}
|
||
|
||
private Task<IEnumerable<WellOperationDto>> GetFactWellOperationsAsync(int idWell, DateTime? dailyReportDateStart,
|
||
DateTime? dailyReportDateEnd, CancellationToken cancellationToken) =>
|
||
wellOperationRepository.GetAsync(new WellOperationRequest
|
||
{
|
||
IdWell = idWell,
|
||
OperationType = WellOperation.IdOperationTypeFact,
|
||
GeDate = dailyReportDateStart,
|
||
LtDate = dailyReportDateEnd
|
||
}, cancellationToken);
|
||
} |