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 AsbCloudDb.Model; using Mapster; namespace AsbCloudInfrastructure.Services.DailyReport; public class DailyReportService : IDailyReportService { private readonly IWellService wellService; private readonly ITrajectoryNnbRepository trajectoryFactNnbRepository; private readonly IDailyReportRepository dailyReportRepository; private readonly IScheduleRepository scheduleRepository; private readonly IWellOperationRepository wellOperationRepository; private readonly ISubsystemService subsystemService; private readonly IProcessMapReportDrillingService processMapReportDrillingService; private readonly IDetectedOperationService detectedOperationService; public DailyReportService(IWellService wellService, ITrajectoryNnbRepository trajectoryFactNnbRepository, IDailyReportRepository dailyReportRepository, IScheduleRepository scheduleRepository, IWellOperationRepository wellOperationRepository, ISubsystemService subsystemService, IProcessMapReportDrillingService processMapReportDrillingService, IDetectedOperationService detectedOperationService) { this.wellService = wellService; this.trajectoryFactNnbRepository = trajectoryFactNnbRepository; this.dailyReportRepository = dailyReportRepository; this.scheduleRepository = scheduleRepository; this.wellOperationRepository = wellOperationRepository; this.subsystemService = subsystemService; this.processMapReportDrillingService = processMapReportDrillingService; this.detectedOperationService = detectedOperationService; } public async Task UpdateOrInsertAsync(int idWell, DateOnly dateDailyReport, int idUser, TBlock editableBlock, CancellationToken cancellationToken) where TBlock : ItemInfoDto { if (!await IsDateDailyReportInRangeAsync(idWell, dateDailyReport, cancellationToken)) throw new ArgumentInvalidException(nameof(dateDailyReport), "Невозможно обновить суточный отчёт"); var dailyReport = await dailyReportRepository.GetOrDefaultAsync(idWell, dateDailyReport, cancellationToken) ?? new DailyReportDto { IdWell = idWell, Date = dateDailyReport }; switch (editableBlock) { case SubsystemBlockDto subsystemBlock: dailyReport.SubsystemBlock = subsystemBlock; break; case TimeBalanceBlockDto timeBalanceBlock: dailyReport.TimeBalanceBlock = timeBalanceBlock; break; case SignBlockDto signBlock: dailyReport.SignBlock = signBlock; break; default: throw new InvalidOperationException("Unexpected type of editableBlock"); } editableBlock.IdUser = idUser; editableBlock.LastUpdateDate = DateTime.UtcNow; dailyReport.DateLastUpdate = DateTime.UtcNow; if (dailyReport.Id == 0) return await dailyReportRepository.InsertAsync(dailyReport, cancellationToken); return await dailyReportRepository.UpdateAsync(dailyReport, cancellationToken); } public async Task GetAsync(int idWell, DateOnly dateDailyReport, CancellationToken cancellationToken) { var well = await wellService.GetOrDefaultAsync(idWell, cancellationToken) ?? throw new ArgumentNullException(nameof(idWell), $"Скважина с Id: {idWell} не найдена"); if (!await IsDateDailyReportInRangeAsync(idWell, dateDailyReport, cancellationToken)) throw new ArgumentInvalidException(nameof(dateDailyReport), "Невозможно получить суточный отчёт"); var dailyReport = await dailyReportRepository.GetOrDefaultAsync(idWell, dateDailyReport, cancellationToken) ?? new DailyReportDto { Date = dateDailyReport, IdWell = well.Id }; var geDate = dailyReport.Date.ToDateTime(TimeOnly.MinValue, DateTimeKind.Unspecified); var ltDate = dailyReport.Date.AddDays(1).ToDateTime(TimeOnly.MinValue, DateTimeKind.Unspecified); var factOperationRequest = new WellOperationRequest { IdWell = idWell, OperationType = WellOperation.IdOperationTypeFact, GeDate = geDate, LeDate = ltDate }; var factWellOperations = (await wellOperationRepository.GetAsync(factOperationRequest, cancellationToken)) .OrderBy(o => o.DateStart) .ThenBy(o => o.DepthStart); dailyReport.WellCaption = 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.FirstOrDefault()?.DepthStart; dailyReport.DepthEnd = factWellOperations.LastOrDefault()?.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> GetAsync(int idWell, FileReportRequest request, CancellationToken cancellationToken) { var result = new PaginationContainer { Skip = request.Skip ?? 0, Take = request.Take ?? 10, Items = Enumerable.Empty() }; var datesRange = await GetDatesRangeAsync(idWell, cancellationToken); if (datesRange is null) return result; var dailyReports = new List(); 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)) + 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, LeDate = ltDateFactWellOperation }; var factWellOperations = await wellOperationRepository.GetAsync(factWellOperationRequest, cancellationToken); 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 dateDailyReport = DateOnly.FromDateTime(datesRange.To.AddDays(-day)); AddDailyReport(dateDailyReport); } } else { for (var day = result.Skip; day - result.Skip < result.Take && datesRange.From.AddDays(day) <= datesRange.To; day++) { var dateDailyReport = DateOnly.FromDateTime(datesRange.From.AddDays(day)); AddDailyReport(dateDailyReport); } } result.Items = dailyReports; return result; void AddDailyReport(DateOnly date) { var dailyReport = existingDailyReports.FirstOrDefault(d => d.IdWell == idWell && d.Date == date) ?? new DailyReportDto { Date = date, IdWell = idWell }; var geDate = date.ToDateTime(TimeOnly.MinValue, DateTimeKind.Unspecified); var leDate = date.AddDays(1).ToDateTime(TimeOnly.MinValue, DateTimeKind.Unspecified); var factWellOperationPerDay = factWellOperations.Where(o => o.DateStart.Date >= geDate && o.DateStart.Date <= leDate); AddFactWellOperationBlock(dailyReport, factWellOperationPerDay); dailyReports.Add(dailyReport); } } public async Task GetDatesRangeAsync(int idWell, CancellationToken cancellationToken) { var timezone = wellService.GetTimezone(idWell); var currentDate = DateTimeOffset.UtcNow.ToRemoteDateTime(timezone.Hours); var factOperationDatesRange = await wellOperationRepository.GetDatesRangeAsync(idWell, WellOperation.IdOperationTypeFact, cancellationToken); if (factOperationDatesRange is null) return null; var from = (factOperationDatesRange.From.AddDays(1) <= DateTime.UtcNow ? factOperationDatesRange.From : currentDate.AddDays(-1)) .Date; var to = (factOperationDatesRange.To.AddDays(1) <= DateTime.UtcNow ? factOperationDatesRange.To : currentDate.AddDays(-1)) .Date; return new DatesRangeDto { From = from, To = to }; } private async Task UpdateTimeBalanceBlockAsync(DailyReportDto dailyReport, IEnumerable factWellOperations, CancellationToken cancellationToken) { const int idWellOperationSlipsTime = 5011; if (dailyReport.TimeBalanceBlock is not null) { dailyReport.TimeBalanceBlock.SectionName = wellOperationRepository.GetSectionTypes() .FirstOrDefault(s => s.Id == dailyReport.TimeBalanceBlock.IdSection)?.Caption; var geDateStart = dailyReport.Date.ToDateTime(TimeOnly.MinValue, DateTimeKind.Unspecified); var leDateEnd = dailyReport.Date.AddDays(1).ToDateTime(TimeOnly.MinValue, DateTimeKind.Unspecified); dailyReport.TimeBalanceBlock.WellOperationSlipsTimeCount = (await detectedOperationService.GetAsync( new DetectedOperationByWellRequest { IdsCategories = new[] { idWellOperationSlipsTime }, IdWell = dailyReport.IdWell, GeDateStart = geDateStart, LeDateEnd = leDateEnd }, cancellationToken))?.Stats.Sum(s => s.Count); dailyReport.TimeBalanceBlock.WellDepth.Fact = factWellOperations .Where(o => o.IdWellSectionType == dailyReport.TimeBalanceBlock.IdSection) .Sum(o => o.DepthEnd - o.DepthStart); } } private async Task AddTrajectoryBlockAsync(DailyReportDto dailyReport, CancellationToken cancellationToken) { var geDate = dailyReport.Date.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc); var leDate = dailyReport.Date.AddDays(1).ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc); var trajectory = (await trajectoryFactNnbRepository.GetByRequestAsync(new TrajectoryRequest { IdWell = dailyReport.IdWell, GeDate = geDate, LeDate = leDate }, cancellationToken)).MaxBy(t => t.WellboreDepth); dailyReport.TrajectoryBlock = new TrajectoryBlockDto { WellboreDepth = trajectory?.WellboreDepth, VerticalDepth = trajectory?.VerticalDepth, ZenithAngle = trajectory?.ZenithAngle, AzimuthGeo = trajectory?.AzimuthGeo }; } private async Task AddScheduleBlockAsync(DailyReportDto dailyReport, CancellationToken cancellationToken) { var workDate = dailyReport.Date.ToDateTime(TimeOnly.MinValue, DateTimeKind.Unspecified); dailyReport.ScheduleBlock = (await scheduleRepository.GetAsync(dailyReport.IdWell, workDate, 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.Subsystems = await GetSubsystemsAsync(); async Task> GetSubsystemsAsync() { var subsystemsStatPerWell = await subsystemService.GetStatAsync(new SubsystemRequest { IdWell = dailyReport.IdWell }, cancellationToken); var geDate = dailyReport.Date.ToDateTime(TimeOnly.MinValue, DateTimeKind.Unspecified); var leDate = dailyReport.Date.AddDays(1).ToDateTime(TimeOnly.MinValue, DateTimeKind.Unspecified); var subsystemsStatPerDay = await subsystemService.GetStatAsync(new SubsystemRequest { IdWell = dailyReport.IdWell, GeDate = geDate, LeDate = leDate }, cancellationToken); var subsystems = subsystemsStatPerWell .Select(subsystemStatPerWell => new SubsystemRecordDto { Name = subsystemStatPerWell.SubsystemName, UsagePerDay = subsystemsStatPerDay.FirstOrDefault(s => s.IdSubsystem == subsystemStatPerWell.IdSubsystem)?.Adapt(), UsagePerWell = subsystemStatPerWell.Adapt() }).ToList(); if (dailyReport.SubsystemBlock?.Subsystems != null && dailyReport.SubsystemBlock.Subsystems.Any()) subsystems.AddRange(dailyReport.SubsystemBlock.Subsystems); return subsystems.OrderBy(s => s.Name); } } private async Task AddProcessMapWellDrillingBlockAsync(DailyReportDto dailyReport, CancellationToken cancellationToken) { var geDate = dailyReport.Date.ToDateTime(TimeOnly.MinValue, DateTimeKind.Unspecified); var leDate = dailyReport.Date.AddDays(1).ToDateTime(TimeOnly.MinValue, DateTimeKind.Unspecified); var request = new DataSaubStatRequest(); dailyReport.ProcessMapWellDrillingBlock = (await processMapReportDrillingService.GetAsync(dailyReport.IdWell, request, cancellationToken)).Where(p => p.DateStart >= geDate && p.DateStart <= leDate) .GroupBy(p => p.DrillingMode) .Select(g => new ProcessMapWellDrillingRecordDto { DrillingMode = g.Key, WellBoreDepth = g.Sum(p => p.DeltaDepth), Rop = new PlanFactDto { Plan = g.Sum(p => p.Rop.Plan), Fact = g.Sum(p => p.Rop.Fact) }, MechDrillingHours = g.Sum(p => p.DrilledTime) }); } private static void AddFactWellOperationBlock(DailyReportDto dailyReport, IEnumerable factWellOperations) { const int idWellOperationCategoryDrilling = 4001; dailyReport.FactWellOperationBlock = new WellOperationBlockDto { WellOperations = factWellOperations.GroupBy(o => o.IdCategory) .Select(g => new WellOperationRecordDto { CategoryName = g.First().CategoryName, DurationHours = g.Sum(o => o.DurationHours) }), SectionDrillingHours = factWellOperations .Where(o => o.IdParentCategory is idWellOperationCategoryDrilling) .Sum(o => o.DurationHours) }; } private async Task IsDateDailyReportInRangeAsync(int idWell, DateOnly dateDailyReport, CancellationToken cancellationToken) { var datesRange = await GetDatesRangeAsync(idWell, cancellationToken); if (datesRange is null) return false; var from = DateOnly.FromDateTime(datesRange.From); var to = DateOnly.FromDateTime(datesRange.To); return dateDailyReport >= from && dateDailyReport <= to; } }