diff --git a/backend/api/AppInfrastructure/DcdIocConfiguration.cs b/backend/api/AppInfrastructure/DcdIocConfiguration.cs index 32670fd6f..e4ad4c62e 100644 --- a/backend/api/AppInfrastructure/DcdIocConfiguration.cs +++ b/backend/api/AppInfrastructure/DcdIocConfiguration.cs @@ -87,6 +87,8 @@ public static void AddDcdIocConfiguration(this IServiceCollection services) services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); /* Project members */ services.AddScoped(); @@ -181,8 +183,6 @@ public static void AddDcdIocConfiguration(this IServiceCollection services) services.AddScoped(); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/backend/api/Features/TechnicalInput/Dtos/UpdateExplorationWithProfilesDto.cs b/backend/api/Features/Assets/CaseAssets/Explorations/Dtos/UpdateExplorationWithProfilesDto.cs similarity index 90% rename from backend/api/Features/TechnicalInput/Dtos/UpdateExplorationWithProfilesDto.cs rename to backend/api/Features/Assets/CaseAssets/Explorations/Dtos/UpdateExplorationWithProfilesDto.cs index 90f76b6a4..7bb75a3a0 100644 --- a/backend/api/Features/TechnicalInput/Dtos/UpdateExplorationWithProfilesDto.cs +++ b/backend/api/Features/Assets/CaseAssets/Explorations/Dtos/UpdateExplorationWithProfilesDto.cs @@ -1,6 +1,6 @@ using api.Features.CaseProfiles.Dtos.TimeSeries.Update; -namespace api.Features.TechnicalInput.Dtos; +namespace api.Features.Assets.CaseAssets.Explorations.Dtos; public class UpdateGAndGAdminCostOverrideDto : UpdateTimeSeriesCostDto { diff --git a/backend/api/Features/TechnicalInput/Dtos/UpdateWellProjectWithProfilesDto.cs b/backend/api/Features/Assets/CaseAssets/WellProjects/Dtos/UpdateWellProjectWithProfilesDto.cs similarity index 92% rename from backend/api/Features/TechnicalInput/Dtos/UpdateWellProjectWithProfilesDto.cs rename to backend/api/Features/Assets/CaseAssets/WellProjects/Dtos/UpdateWellProjectWithProfilesDto.cs index 663570031..47ccaf457 100644 --- a/backend/api/Features/TechnicalInput/Dtos/UpdateWellProjectWithProfilesDto.cs +++ b/backend/api/Features/Assets/CaseAssets/WellProjects/Dtos/UpdateWellProjectWithProfilesDto.cs @@ -1,7 +1,7 @@ using api.Features.CaseProfiles.Dtos.TimeSeries; using api.Features.CaseProfiles.Dtos.TimeSeries.Update; -namespace api.Features.TechnicalInput.Dtos; +namespace api.Features.Assets.CaseAssets.WellProjects.Dtos; public class UpdateOilProducerCostProfileOverrideDto : UpdateTimeSeriesCostDto, ITimeSeriesOverrideDto { diff --git a/backend/api/Features/CaseProfiles/Dtos/Well/DeleteWellDto.cs b/backend/api/Features/CaseProfiles/Dtos/Well/DeleteWellDto.cs deleted file mode 100644 index 71a4d1f61..000000000 --- a/backend/api/Features/CaseProfiles/Dtos/Well/DeleteWellDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace api.Features.CaseProfiles.Dtos.Well; - -public class DeleteWellDto -{ - [Required] - public Guid Id { get; set; } -} diff --git a/backend/api/Features/CaseProfiles/Services/CaseService.cs b/backend/api/Features/CaseProfiles/Services/CaseService.cs index 8377559bf..2030fd37f 100644 --- a/backend/api/Features/CaseProfiles/Services/CaseService.cs +++ b/backend/api/Features/CaseProfiles/Services/CaseService.cs @@ -52,10 +52,4 @@ public async Task GetCaseWithIncludes(Guid caseId, params Expression> GetAll() - { - return await context.Cases.ToListAsync(); - } } diff --git a/backend/api/Features/CaseProfiles/Services/CostProfileFromDrillingScheduleHelper.cs b/backend/api/Features/CaseProfiles/Services/CostProfileFromDrillingScheduleHelper.cs deleted file mode 100644 index 8c46fd49f..000000000 --- a/backend/api/Features/CaseProfiles/Services/CostProfileFromDrillingScheduleHelper.cs +++ /dev/null @@ -1,293 +0,0 @@ -using api.Context; -using api.Exceptions; -using api.Features.Assets.CaseAssets.Explorations.Dtos; -using api.Features.Assets.CaseAssets.WellProjects.Dtos; -using api.Models; - -using AutoMapper; - -using Microsoft.EntityFrameworkCore; - -namespace api.Features.CaseProfiles.Services; - -public class CostProfileFromDrillingScheduleHelper( - DcdDbContext context, - ICaseService caseService, - IMapper mapper) : ICostProfileFromDrillingScheduleHelper -{ - public async Task UpdateCostProfilesForWells(List wellIds) - { - var explorationWells = GetAllExplorationWells().Where(ew => wellIds.Contains(ew.WellId)); - - var wellProjectWells = GetAllWellProjectWells().Where(wpw => wellIds.Contains(wpw.WellId)); - - var uniqueExplorationIds = explorationWells.Select(ew => ew.ExplorationId).Distinct(); - var uniqueWellProjectIds = wellProjectWells.Select(wpw => wpw.WellProjectId).Distinct(); - - var explorationCases = (await caseService.GetAll()).Where(c => uniqueExplorationIds.Contains(c.ExplorationLink)); - var wellProjectCases = (await caseService.GetAll()).Where(c => uniqueWellProjectIds.Contains(c.WellProjectLink)); - - var explorationCaseIds = explorationCases.Select(c => c.Id).Distinct(); - var wellProjectCaseIds = wellProjectCases.Select(c => c.Id).Distinct(); - - var updatedExplorationDtoList = new List(); - foreach (var caseId in explorationCaseIds) - { - var explorationDto = await UpdateExplorationCostProfilesForCase(caseId); - updatedExplorationDtoList.Add(explorationDto); - } - - var updatedWellProjectDtoList = new List(); - foreach (var caseId in wellProjectCaseIds) - { - var wellProjectDto = await UpdateWellProjectCostProfilesForCase(caseId); - updatedWellProjectDtoList.Add(wellProjectDto); - } - - UpdateExplorations(updatedExplorationDtoList.ToArray()); - - UpdateWellProjects(updatedWellProjectDtoList.ToArray()); - - await context.SaveChangesAsync(); - } - - private WellProjectWithProfilesDto[] UpdateWellProjects(WellProject[] updatedWellProjects) - { - var updatedWellProjectDtoList = new List(); - foreach (var updatedWellProject in updatedWellProjects) - { - var wellProject = context.WellProjects.Update(updatedWellProject); - var wellProjectDto = mapper.Map(wellProject.Entity); - if (wellProjectDto == null) - { - throw new ArgumentNullException(nameof(wellProjectDto)); - } - updatedWellProjectDtoList.Add(wellProjectDto); - } - - return updatedWellProjectDtoList.ToArray(); - } - - private ExplorationWithProfilesDto[] UpdateExplorations(Exploration[] updatedExplorations) - { - var updatedExplorationDtoList = new List(); - foreach (var updatedExploration in updatedExplorations) - { - var exploration = context.Explorations.Update(updatedExploration); - var explorationDto = mapper.Map(exploration.Entity); - if (explorationDto == null) - { - throw new ArgumentNullException(nameof(explorationDto)); - } - updatedExplorationDtoList.Add(explorationDto); - } - - return updatedExplorationDtoList.ToArray(); - } - - private async Task GetExploration(Guid explorationId) - { - var exploration = await context.Explorations.FindAsync(explorationId) - ?? throw new NotFoundInDbException($"Exploration {explorationId} not found in database."); - return exploration; - } - - private async Task GetWellProject(Guid wellProjectId) - { - var wellProject = await context.WellProjects.FindAsync(wellProjectId) - ?? throw new NotFoundInDbException($"WellProject {wellProjectId} not found in database."); - return wellProject; - } - - private async Task UpdateExplorationCostProfilesForCase(Guid caseId) - { - var caseItem = await caseService.GetCase(caseId); - - return await UpdateExplorationCostProfiles(caseItem.ExplorationLink); - } - - public async Task UpdateExplorationCostProfiles(Guid explorationId) - { - var exploration = await GetExploration(explorationId); - - var explorationWells = GetAllExplorationWells().Where(ew => ew.ExplorationId == exploration.Id); - - return UpdateExplorationCostProfilesForCase(exploration, explorationWells); - } - - private Exploration UpdateExplorationCostProfilesForCase(Exploration exploration, IEnumerable explorationWells) - { - var wellIds = explorationWells.Select(ew => ew.WellId); - var wells = GetAllWells().Where(w => wellIds.Contains(w.Id)); - - var explorationCategoryWells = wells.Where(w => w.WellCategory == WellCategory.Exploration_Well).ToList(); - var explorationWellExplorationCategoryWells = explorationWells.Where(ew => explorationCategoryWells.Exists(w => w.Id == ew.WellId)).ToList(); - - var appraisalWells = wells.Where(w => w.WellCategory == WellCategory.Appraisal_Well).ToList(); - var explorationWellAppraisal = explorationWells.Where(ew => appraisalWells.Exists(w => w.Id == ew.WellId)).ToList(); - - var sidetrackWells = wells.Where(w => w.WellCategory == WellCategory.Sidetrack).ToList(); - var explorationWellSidetrack = explorationWells.Where(ew => sidetrackWells.Exists(w => w.Id == ew.WellId)).ToList(); - - var explorationCategoryTimeSeries = GenerateExplorationCostProfileFromDrillingSchedulesAndWellCost(explorationCategoryWells, explorationWellExplorationCategoryWells); - var explorationCategoryCostProfile = new ExplorationWellCostProfile - { - Values = explorationCategoryTimeSeries.Values, - StartYear = explorationCategoryTimeSeries.StartYear, - }; - - var appraisalTimeSeries = GenerateExplorationCostProfileFromDrillingSchedulesAndWellCost(appraisalWells, explorationWellAppraisal); - var appraisalCostProfile = new AppraisalWellCostProfile - { - Values = appraisalTimeSeries.Values, - StartYear = appraisalTimeSeries.StartYear, - }; - - var sidetrackTimeSeries = GenerateExplorationCostProfileFromDrillingSchedulesAndWellCost(sidetrackWells, explorationWellSidetrack); - var sidetrackCostProfile = new SidetrackCostProfile - { - Values = sidetrackTimeSeries.Values, - StartYear = sidetrackTimeSeries.StartYear, - }; - - exploration.ExplorationWellCostProfile = explorationCategoryCostProfile; - exploration.AppraisalWellCostProfile = appraisalCostProfile; - exploration.SidetrackCostProfile = sidetrackCostProfile; - - return exploration; - } - - private static TimeSeries GenerateExplorationCostProfileFromDrillingSchedulesAndWellCost(List wells, List explorationWells) - { - var costProfilesList = new List?>(); - foreach (var explorationWell in explorationWells) - { - if (explorationWell?.DrillingSchedule?.Values?.Length > 0) - { - var well = wells.Single(w => w.Id == explorationWell.WellId); - var values = explorationWell.DrillingSchedule.Values.Select(ds => ds * well.WellCost).ToArray(); - var costProfile = new TimeSeries - { - Values = values, - StartYear = explorationWell.DrillingSchedule.StartYear, - }; - costProfilesList.Add(costProfile); - } - } - - var mergedCostProfile = TimeSeriesCost.MergeCostProfilesList(costProfilesList); - return mergedCostProfile; - } - - private async Task UpdateWellProjectCostProfilesForCase(Guid caseId) - { - var caseItem = await caseService.GetCase(caseId); - - return await UpdateWellProjectCostProfiles(caseItem.WellProjectLink); - } - - public async Task UpdateWellProjectCostProfiles(Guid wellProjectId) - { - var wellProject = await GetWellProject(wellProjectId); - var wellProjectWells = GetAllWellProjectWells().Where(ew => ew.WellProjectId == wellProject.Id); - - return UpdateWellProjectCostProfilesForCase(wellProject, wellProjectWells); - } - - private WellProject UpdateWellProjectCostProfilesForCase(WellProject wellProject, IEnumerable wellProjectWells) - { - var wellIds = wellProjectWells.Select(ew => ew.WellId); - var wells = GetAllWells().Where(w => wellIds.Contains(w.Id)); - - var oilProducerWells = wells.Where(w => w.WellCategory == WellCategory.Oil_Producer).ToList(); - var wellProjectWellOilProducer = wellProjectWells.Where(ew => oilProducerWells.Exists(w => w.Id == ew.WellId)).ToList(); - - var gasProducerWells = wells.Where(w => w.WellCategory == WellCategory.Gas_Producer).ToList(); - var wellProjectWellGasProducer = wellProjectWells.Where(ew => gasProducerWells.Exists(w => w.Id == ew.WellId)).ToList(); - - var waterInjectorWells = wells.Where(w => w.WellCategory == WellCategory.Water_Injector).ToList(); - var wellProjectWellWaterInjector = wellProjectWells.Where(ew => waterInjectorWells.Exists(w => w.Id == ew.WellId)).ToList(); - - var gasInjectorWells = wells.Where(w => w.WellCategory == WellCategory.Gas_Injector).ToList(); - var wellProjectWellGasInjector = wellProjectWells.Where(ew => gasInjectorWells.Exists(w => w.Id == ew.WellId)).ToList(); - - var oilProducerTimeSeries = GenerateWellProjectCostProfileFromDrillingSchedulesAndWellCost(oilProducerWells, wellProjectWellOilProducer); - var oilProducerCostProfile = new OilProducerCostProfile - { - Values = oilProducerTimeSeries.Values, - StartYear = oilProducerTimeSeries.StartYear, - }; - - var gasProducerTimeSeries = GenerateWellProjectCostProfileFromDrillingSchedulesAndWellCost(gasProducerWells, wellProjectWellGasProducer); - var gasProducerCostProfile = new GasProducerCostProfile - { - Values = gasProducerTimeSeries.Values, - StartYear = gasProducerTimeSeries.StartYear, - }; - - var waterInjectorTimeSeries = GenerateWellProjectCostProfileFromDrillingSchedulesAndWellCost(waterInjectorWells, wellProjectWellWaterInjector); - var waterInjectorCostProfile = new WaterInjectorCostProfile - { - Values = waterInjectorTimeSeries.Values, - StartYear = waterInjectorTimeSeries.StartYear, - }; - - var gasInjectorTimeSeries = GenerateWellProjectCostProfileFromDrillingSchedulesAndWellCost(gasInjectorWells, wellProjectWellGasInjector); - var gasInjectorCostProfile = new GasInjectorCostProfile - { - Values = gasInjectorTimeSeries.Values, - StartYear = gasInjectorTimeSeries.StartYear, - }; - - wellProject.OilProducerCostProfile = oilProducerCostProfile; - wellProject.GasProducerCostProfile = gasProducerCostProfile; - wellProject.WaterInjectorCostProfile = waterInjectorCostProfile; - wellProject.GasInjectorCostProfile = gasInjectorCostProfile; - - return wellProject; - } - - private static TimeSeries GenerateWellProjectCostProfileFromDrillingSchedulesAndWellCost(List wells, List wellProjectWells) - { - var costProfilesList = new List?>(); - foreach (var wellProjectWell in wellProjectWells) - { - if (wellProjectWell?.DrillingSchedule?.Values?.Length > 0) - { - var well = wells.Single(w => w.Id == wellProjectWell.WellId); - var values = wellProjectWell.DrillingSchedule.Values.Select(ds => ds * well.WellCost).ToArray(); - var costProfile = new TimeSeries - { - Values = values, - StartYear = wellProjectWell.DrillingSchedule.StartYear, - }; - costProfilesList.Add(costProfile); - } - } - - var mergedCostProfile = TimeSeriesCost.MergeCostProfilesList(costProfilesList); - return mergedCostProfile; - } - - private IQueryable GetAllWells() - { - if (context.Wells != null) - { - return context.Wells; - } - else - { - return Enumerable.Empty().AsQueryable(); - } - } - - private IQueryable GetAllExplorationWells() - { - return context.ExplorationWell.Include(ew => ew.DrillingSchedule); - } - - private IQueryable GetAllWellProjectWells() - { - return context.WellProjectWell.Include(wpw => wpw.DrillingSchedule); - } -} diff --git a/backend/api/Features/CaseProfiles/Services/ICaseService.cs b/backend/api/Features/CaseProfiles/Services/ICaseService.cs index 24e51316f..a19df1f5c 100644 --- a/backend/api/Features/CaseProfiles/Services/ICaseService.cs +++ b/backend/api/Features/CaseProfiles/Services/ICaseService.cs @@ -9,5 +9,4 @@ public interface ICaseService Task GetProject(Guid id); Task GetCase(Guid caseId); Task GetCaseWithIncludes(Guid caseId, params Expression>[] includes); - Task> GetAll(); } diff --git a/backend/api/Features/CaseProfiles/Services/ICostProfileFromDrillingScheduleHelper.cs b/backend/api/Features/CaseProfiles/Services/ICostProfileFromDrillingScheduleHelper.cs deleted file mode 100644 index 0ac133d69..000000000 --- a/backend/api/Features/CaseProfiles/Services/ICostProfileFromDrillingScheduleHelper.cs +++ /dev/null @@ -1,10 +0,0 @@ -using api.Models; - -namespace api.Features.CaseProfiles.Services; - -public interface ICostProfileFromDrillingScheduleHelper -{ - Task UpdateCostProfilesForWells(List wellIds); - Task UpdateExplorationCostProfiles(Guid explorationId); - Task UpdateWellProjectCostProfiles(Guid wellProjectId); -} diff --git a/backend/api/Features/TechnicalInput/Dtos/DeleteWellDto.cs b/backend/api/Features/TechnicalInput/Dtos/DeleteWellDto.cs new file mode 100644 index 000000000..c57442fcd --- /dev/null +++ b/backend/api/Features/TechnicalInput/Dtos/DeleteWellDto.cs @@ -0,0 +1,8 @@ +using System.ComponentModel.DataAnnotations; + +namespace api.Features.TechnicalInput.Dtos; + +public class DeleteWellDto +{ + [Required] public Guid Id { get; set; } +} diff --git a/backend/api/Features/TechnicalInput/Dtos/UpdateTechnicalInputDto.cs b/backend/api/Features/TechnicalInput/Dtos/UpdateTechnicalInputDto.cs index cbe4b9515..56b8e3c55 100644 --- a/backend/api/Features/TechnicalInput/Dtos/UpdateTechnicalInputDto.cs +++ b/backend/api/Features/TechnicalInput/Dtos/UpdateTechnicalInputDto.cs @@ -1,6 +1,7 @@ +using System.ComponentModel.DataAnnotations; + using api.Features.Assets.ProjectAssets.DevelopmentOperationalWellCosts.Dtos; using api.Features.Assets.ProjectAssets.ExplorationOperationalWellCosts.Dtos; -using api.Features.CaseProfiles.Dtos.Well; using api.Features.Projects.Update; using api.Features.Wells.Create; using api.Features.Wells.Update; @@ -9,10 +10,10 @@ namespace api.Features.TechnicalInput.Dtos; public class UpdateTechnicalInputDto { - public UpdateDevelopmentOperationalWellCostsDto DevelopmentOperationalWellCostsDto { get; set; } = null!; - public UpdateExplorationOperationalWellCostsDto ExplorationOperationalWellCostsDto { get; set; } = null!; - public UpdateWellDto[]? UpdateWellDtos { get; set; } - public CreateWellDto[]? CreateWellDtos { get; set; } - public DeleteWellDto[]? DeleteWellDtos { get; set; } - public UpdateProjectDto ProjectDto { get; set; } = null!; + [Required] public required UpdateDevelopmentOperationalWellCostsDto DevelopmentOperationalWellCostsDto { get; set; } + [Required] public required UpdateExplorationOperationalWellCostsDto ExplorationOperationalWellCostsDto { get; set; } + [Required] public required List UpdateWellDtos { get; set; } + [Required] public required List CreateWellDtos { get; set; } + [Required] public required List DeleteWellDtos { get; set; } + [Required] public required UpdateProjectDto ProjectDto { get; set; } } diff --git a/backend/api/Features/TechnicalInput/TechnicalInputService.cs b/backend/api/Features/TechnicalInput/TechnicalInputService.cs index 4b7cae7ae..cc355fbda 100644 --- a/backend/api/Features/TechnicalInput/TechnicalInputService.cs +++ b/backend/api/Features/TechnicalInput/TechnicalInputService.cs @@ -2,54 +2,94 @@ using api.Context.Extensions; using api.Features.Assets.ProjectAssets.DevelopmentOperationalWellCosts.Dtos; using api.Features.Assets.ProjectAssets.ExplorationOperationalWellCosts.Dtos; -using api.Features.CaseProfiles.Dtos.Well; -using api.Features.CaseProfiles.Repositories; -using api.Features.CaseProfiles.Services; using api.Features.Projects.Update; using api.Features.TechnicalInput.Dtos; using api.Features.Wells.Create; using api.Features.Wells.Update; using api.Models; -using AutoMapper; - using Microsoft.EntityFrameworkCore; namespace api.Features.TechnicalInput; -public class TechnicalInputService( - DcdDbContext context, - IProjectWithCasesAndAssetsRepository projectWithCasesAndAssetsRepository, - ICostProfileFromDrillingScheduleHelper costProfileFromDrillingScheduleHelper, - ILogger logger, - IMapper mapper) +public class TechnicalInputService(DcdDbContext context, + UpdateCostProfilesForExplorationWellsService updateCostProfilesForExplorationWellsService, + UpdateCostProfilesForWellProjectsService updateCostProfilesForWellProjectsService) { public async Task UpdateTechnicalInput(Guid projectId, UpdateTechnicalInputDto technicalInputDto) { var projectPk = await context.GetPrimaryKeyForProjectId(projectId); - var project = await projectWithCasesAndAssetsRepository.GetProjectWithCasesAndAssets(projectPk); + var project = await context.Projects + .Include(x => x.DevelopmentOperationalWellCosts) + .Include(x => x.ExplorationOperationalWellCosts) + .Where(x => x.Id == projectPk) + .SingleAsync(); - await UpdateProject(project, technicalInputDto.ProjectDto); + UpdateProject(project, technicalInputDto.ProjectDto); + UpdateExplorationOperationalWellCosts(project.ExplorationOperationalWellCosts!, technicalInputDto.ExplorationOperationalWellCostsDto); + UpdateDevelopmentOperationalWellCosts(project.DevelopmentOperationalWellCosts!, technicalInputDto.DevelopmentOperationalWellCostsDto); - await UpdateExplorationOperationalWellCosts(project, technicalInputDto.ExplorationOperationalWellCostsDto); - await UpdateDevelopmentOperationalWellCosts(project, technicalInputDto.DevelopmentOperationalWellCostsDto); + await context.SaveChangesAsync(); - if (technicalInputDto.DeleteWellDtos?.Length > 0) - { - await DeleteWells(technicalInputDto.DeleteWellDtos); - } - - if (technicalInputDto.UpdateWellDtos?.Length > 0 || technicalInputDto.CreateWellDtos?.Length > 0) - { - await CreateAndUpdateWells(projectPk, technicalInputDto.CreateWellDtos, technicalInputDto.UpdateWellDtos); - } + await DeleteWells(technicalInputDto.DeleteWellDtos); + await CreateWells(technicalInputDto.CreateWellDtos, projectPk); + await UpdateWells(technicalInputDto.UpdateWellDtos); await context.SaveChangesAsync(); } - private async Task DeleteWells(DeleteWellDto[] deleteWellDtos) + private static void UpdateProject(Project project, UpdateProjectDto dto) + { + project.Name = dto.Name; + project.ReferenceCaseId = dto.ReferenceCaseId; + project.Description = dto.Description; + project.Country = dto.Country; + project.Currency = dto.Currency; + project.PhysicalUnit = dto.PhysicalUnit; + project.Classification = dto.Classification; + project.ProjectPhase = dto.ProjectPhase; + project.InternalProjectPhase = dto.InternalProjectPhase; + project.ProjectCategory = dto.ProjectCategory; + project.SharepointSiteUrl = dto.SharepointSiteUrl; + project.CO2RemovedFromGas = dto.CO2RemovedFromGas; + project.CO2EmissionFromFuelGas = dto.CO2EmissionFromFuelGas; + project.FlaredGasPerProducedVolume = dto.FlaredGasPerProducedVolume; + project.CO2EmissionsFromFlaredGas = dto.CO2EmissionsFromFlaredGas; + project.CO2Vented = dto.CO2Vented; + project.DailyEmissionFromDrillingRig = dto.DailyEmissionFromDrillingRig; + project.AverageDevelopmentDrillingDays = dto.AverageDevelopmentDrillingDays; + project.OilPriceUSD = dto.OilPriceUSD; + project.GasPriceNOK = dto.GasPriceNOK; + project.DiscountRate = dto.DiscountRate; + project.ExchangeRateUSDToNOK = dto.ExchangeRateUSDToNOK; + project.ModifyTime = DateTimeOffset.UtcNow; + } + + private static void UpdateExplorationOperationalWellCosts(ExplorationOperationalWellCosts item, UpdateExplorationOperationalWellCostsDto dto) + { + item.ExplorationRigUpgrading = dto.ExplorationRigUpgrading; + item.ExplorationRigMobDemob = dto.ExplorationRigUpgrading; + item.ExplorationProjectDrillingCosts = dto.ExplorationRigUpgrading; + item.AppraisalRigMobDemob = dto.ExplorationRigUpgrading; + item.AppraisalProjectDrillingCosts = dto.ExplorationRigUpgrading; + } + + private static void UpdateDevelopmentOperationalWellCosts(DevelopmentOperationalWellCosts item, UpdateDevelopmentOperationalWellCostsDto dto) { + item.RigUpgrading = dto.RigUpgrading; + item.RigMobDemob = dto.RigMobDemob; + item.AnnualWellInterventionCostPerWell = dto.AnnualWellInterventionCostPerWell; + item.PluggingAndAbandonment = dto.PluggingAndAbandonment; + } + + private async Task DeleteWells(List deleteWellDtos) + { + if (!deleteWellDtos.Any()) + { + return; + } + var affectedAssets = new Dictionary> { { nameof(Exploration), [] }, @@ -58,164 +98,105 @@ private async Task DeleteWells(DeleteWellDto[] deleteWellDtos) foreach (var wellDto in deleteWellDtos) { - var well = await context.Wells.FindAsync(wellDto.Id); - - if (well != null) + var well = await context.Wells.SingleOrDefaultAsync(x => x.Id == wellDto.Id); + if (well == null) { - var explorationWells = context.ExplorationWell.Where(ew => ew.WellId == well.Id); + continue; + } - foreach (var explorationWell in explorationWells) - { - context.ExplorationWell.Remove(explorationWell); - affectedAssets[nameof(Exploration)].Add(explorationWell.ExplorationId); - } + var explorationWells = context.ExplorationWell.Where(ew => ew.WellId == well.Id); - var wellProjectWells = context.WellProjectWell.Where(ew => ew.WellId == well.Id); + foreach (var explorationWell in explorationWells) + { + context.ExplorationWell.Remove(explorationWell); + affectedAssets[nameof(Exploration)].Add(explorationWell.ExplorationId); + } - foreach (var wellProjectWell in wellProjectWells) - { - context.WellProjectWell.Remove(wellProjectWell); - affectedAssets[nameof(WellProject)].Add(wellProjectWell.WellProjectId); - } + var wellProjectWells = context.WellProjectWell.Where(ew => ew.WellId == well.Id); - context.Wells.Remove(well); + foreach (var wellProjectWell in wellProjectWells) + { + context.WellProjectWell.Remove(wellProjectWell); + affectedAssets[nameof(WellProject)].Add(wellProjectWell.WellProjectId); } + + context.Wells.Remove(well); } await context.SaveChangesAsync(); foreach (var explorationId in affectedAssets[nameof(Exploration)]) { - await costProfileFromDrillingScheduleHelper.UpdateExplorationCostProfiles(explorationId); + await updateCostProfilesForExplorationWellsService.UpdateExplorationCostProfiles(explorationId); } foreach (var wellProjectId in affectedAssets[nameof(WellProject)]) { - await costProfileFromDrillingScheduleHelper.UpdateWellProjectCostProfiles(wellProjectId); + await updateCostProfilesForWellProjectsService.UpdateWellProjectCostProfiles(wellProjectId); } } - private async Task CreateAndUpdateWells( - Guid projectId, - CreateWellDto[]? createWellDtos, - UpdateWellDto[]? updateWellDtos) + private async Task CreateWells(List createWellDtos, Guid projectId) { - var updatedWells = new List(); - - if (createWellDtos != null) + if (!createWellDtos.Any()) { - foreach (var wellDto in createWellDtos) - { - var well = mapper.Map(wellDto); - - if (well == null) - { - throw new ArgumentNullException(nameof(well)); - } - - well.ProjectId = projectId; - context.Wells.Add(well); - } + return; } - if (updateWellDtos != null) + foreach (var wellDto in createWellDtos) { - foreach (var wellDto in updateWellDtos) + context.Wells.Add(new Well { - var existing = await GetWell(wellDto.Id); - - if (wellDto.WellCost != existing.WellCost || wellDto.WellCategory != existing.WellCategory) - { - updatedWells.Add(wellDto.Id); - } - - mapper.Map(wellDto, existing); - context.Wells.Update(existing); - } + ProjectId = projectId, + Name = wellDto.Name, + WellCategory = wellDto.WellCategory, + WellInterventionCost = wellDto.WellInterventionCost, + PlugingAndAbandonmentCost = wellDto.PlugingAndAbandonmentCost, + WellCost = wellDto.WellCost, + DrillingDays = wellDto.DrillingDays + }); } - if (createWellDtos?.Any() == true || updateWellDtos?.Any() == true) - { - await context.SaveChangesAsync(); - } - - if (updatedWells.Count != 0) - { - await costProfileFromDrillingScheduleHelper.UpdateCostProfilesForWells(updatedWells); - } - } - - private async Task GetWell(Guid wellId) - { - var well = await context.Wells - .Include(e => e.WellProjectWells) - .Include(e => e.ExplorationWells) - .FirstOrDefaultAsync(w => w.Id == wellId); - - if (well == null) - { - throw new ArgumentException($"Well {wellId} not found."); - } - - return well; - } - private async Task UpdateProject(Project project, UpdateProjectDto updatedDto) - { - mapper.Map(updatedDto, project); - project.ModifyTime = DateTimeOffset.UtcNow; - await context.SaveChangesAsync(); } - private async Task UpdateExplorationOperationalWellCosts(Project project, UpdateExplorationOperationalWellCostsDto updatedDto) + private async Task UpdateWells(List updateWellDtos) { - if (project.ExplorationOperationalWellCosts == null) + if (!updateWellDtos.Any()) { - logger.LogError("Exploration operational well costs not found"); - throw new Exception("Exploration operational well costs not found"); + return; } - var item = await context.ExplorationOperationalWellCosts - .Include(eowc => eowc.Project) - .FirstOrDefaultAsync(o => o.Id == project.ExplorationOperationalWellCosts.Id) - ?? new ExplorationOperationalWellCosts(); - mapper.Map(updatedDto, item); - - var updatedItem = context.ExplorationOperationalWellCosts.Update(item); - await context.SaveChangesAsync(); - - var explorationOperationalWellCostsDto = mapper.Map(updatedItem.Entity); + var updatedWells = new List(); - if (explorationOperationalWellCostsDto == null) + foreach (var wellDto in updateWellDtos) { - logger.LogError("Failed to map exploration operational well costs to dto"); - throw new Exception("Failed to map exploration operational well costs to dto"); - } - } + var existing = await context.Wells + .Include(e => e.WellProjectWells) + .Include(e => e.ExplorationWells) + .SingleAsync(w => w.Id == wellDto.Id); - private async Task UpdateDevelopmentOperationalWellCosts(Project project, UpdateDevelopmentOperationalWellCostsDto updatedDto) - { - if (project.DevelopmentOperationalWellCosts == null) - { - logger.LogError("Development operational well costs not found"); - throw new Exception("Development operational well costs not found"); - } + if (wellDto.WellCost != existing.WellCost || wellDto.WellCategory != existing.WellCategory) + { + updatedWells.Add(wellDto.Id); + } - var item = await context.DevelopmentOperationalWellCosts - .Include(dowc => dowc.Project) - .FirstOrDefaultAsync(o => o.Id == project.DevelopmentOperationalWellCosts.Id) - ?? new DevelopmentOperationalWellCosts(); - mapper.Map(updatedDto, item); + existing.Name = wellDto.Name; + existing.WellInterventionCost = wellDto.WellInterventionCost; + existing.PlugingAndAbandonmentCost = wellDto.PlugingAndAbandonmentCost; + existing.WellCategory = wellDto.WellCategory; + existing.WellCost = wellDto.WellCost; + existing.DrillingDays = wellDto.DrillingDays; + } - var updatedItem = context.DevelopmentOperationalWellCosts.Update(item); await context.SaveChangesAsync(); - var developmentOperationalWellCostsDto = mapper.Map(updatedItem.Entity); - - if (developmentOperationalWellCostsDto == null) + if (updatedWells.Count != 0) { - logger.LogError("Failed to map development operational well costs to dto"); - throw new Exception("Failed to map development operational well costs to dto"); + await updateCostProfilesForExplorationWellsService.HandleExplorationWells(updatedWells); + await updateCostProfilesForWellProjectsService.HandleWellProjects(updatedWells); + + await context.SaveChangesAsync(); } } } diff --git a/backend/api/Features/TechnicalInput/UpdateCostProfilesForExplorationWellsService.cs b/backend/api/Features/TechnicalInput/UpdateCostProfilesForExplorationWellsService.cs new file mode 100644 index 000000000..490870ea6 --- /dev/null +++ b/backend/api/Features/TechnicalInput/UpdateCostProfilesForExplorationWellsService.cs @@ -0,0 +1,109 @@ +using api.Context; +using api.Exceptions; +using api.Models; + +using Microsoft.EntityFrameworkCore; + +namespace api.Features.TechnicalInput; + +public class UpdateCostProfilesForExplorationWellsService(DcdDbContext context) +{ + public async Task HandleExplorationWells(List wellIds) + { + var uniqueExplorationIds = await context.ExplorationWell + .Where(ew => wellIds.Contains(ew.WellId)) + .Select(ew => ew.ExplorationId) + .Distinct() + .ToListAsync(); + + var explorationIdsUsedInCases = await context.Cases + .Where(c => uniqueExplorationIds.Contains(c.ExplorationLink)) + .Select(x => x.ExplorationLink) + .Distinct() + .ToListAsync(); + + foreach (var explorationId in explorationIdsUsedInCases) + { + await UpdateExplorationCostProfiles(explorationId); + } + } + + public async Task UpdateExplorationCostProfiles(Guid explorationId) + { + var exploration = await context.Explorations.SingleOrDefaultAsync(x => x.Id == explorationId) + ?? throw new NotFoundInDbException($"Exploration {explorationId} not found in database."); + + var wellIds = await context.ExplorationWell + .Where(ew => ew.ExplorationId == exploration.Id) + .Select(ew => ew.WellId) + .ToListAsync(); + + var (explorationCategoryWells, explorationWellExplorationCategoryWells) = await LoadWellDataForWellCategory(wellIds, explorationId, WellCategory.Exploration_Well); + var explorationCategoryTimeSeries = GenerateExplorationCostProfileFromDrillingSchedulesAndWellCost(explorationCategoryWells, explorationWellExplorationCategoryWells); + var explorationCategoryCostProfile = new ExplorationWellCostProfile + { + Values = explorationCategoryTimeSeries.Values, + StartYear = explorationCategoryTimeSeries.StartYear, + }; + + var (appraisalWells, explorationWellAppraisal) = await LoadWellDataForWellCategory(wellIds, explorationId, WellCategory.Appraisal_Well); + var appraisalTimeSeries = GenerateExplorationCostProfileFromDrillingSchedulesAndWellCost(appraisalWells, explorationWellAppraisal); + var appraisalCostProfile = new AppraisalWellCostProfile + { + Values = appraisalTimeSeries.Values, + StartYear = appraisalTimeSeries.StartYear, + }; + + var (sidetrackWells, explorationWellSidetrack) = await LoadWellDataForWellCategory(wellIds, explorationId, WellCategory.Sidetrack); + var sidetrackTimeSeries = GenerateExplorationCostProfileFromDrillingSchedulesAndWellCost(sidetrackWells, explorationWellSidetrack); + var sidetrackCostProfile = new SidetrackCostProfile + { + Values = sidetrackTimeSeries.Values, + StartYear = sidetrackTimeSeries.StartYear, + }; + + exploration.ExplorationWellCostProfile = explorationCategoryCostProfile; + exploration.AppraisalWellCostProfile = appraisalCostProfile; + exploration.SidetrackCostProfile = sidetrackCostProfile; + } + + private async Task<(List, List)> LoadWellDataForWellCategory(List wellIds, Guid explorationId, WellCategory wellCategory) + { + var wells = await context.Wells + .Where(w => wellIds.Contains(w.Id)) + .Where(w => w.WellCategory == wellCategory) + .ToListAsync(); + + var wellWellIds = wells.Select(x => x.Id).ToList(); + + var explorationWells = await context.ExplorationWell + .Include(ew => ew.DrillingSchedule) + .Where(ew => ew.ExplorationId == explorationId) + .Where(ew => wellWellIds.Contains(ew.WellId)) + .ToListAsync(); + + return (wells, explorationWells); + } + + private static TimeSeries GenerateExplorationCostProfileFromDrillingSchedulesAndWellCost(List wells, List explorationWells) + { + var costProfilesList = new List?>(); + + foreach (var explorationWell in explorationWells) + { + if (explorationWell.DrillingSchedule?.Values.Length > 0) + { + var well = wells.Single(w => w.Id == explorationWell.WellId); + var values = explorationWell.DrillingSchedule.Values.Select(ds => ds * well.WellCost).ToArray(); + + costProfilesList.Add(new TimeSeries + { + Values = values, + StartYear = explorationWell.DrillingSchedule.StartYear + }); + } + } + + return TimeSeriesCost.MergeCostProfilesList(costProfilesList); + } +} diff --git a/backend/api/Features/TechnicalInput/UpdateCostProfilesForWellProjectsService.cs b/backend/api/Features/TechnicalInput/UpdateCostProfilesForWellProjectsService.cs new file mode 100644 index 000000000..6d1d09e8b --- /dev/null +++ b/backend/api/Features/TechnicalInput/UpdateCostProfilesForWellProjectsService.cs @@ -0,0 +1,118 @@ +using api.Context; +using api.Exceptions; +using api.Models; + +using Microsoft.EntityFrameworkCore; + +namespace api.Features.TechnicalInput; + +public class UpdateCostProfilesForWellProjectsService(DcdDbContext context) +{ + public async Task HandleWellProjects(List wellIds) + { + var uniqueWellProjectIds = await context.WellProjectWell + .Where(wpw => wellIds.Contains(wpw.WellId)) + .Select(wpw => wpw.WellProjectId) + .Distinct() + .ToListAsync(); + + var wellProjectIdsUsedByCases = await context.Cases + .Where(c => uniqueWellProjectIds.Contains(c.WellProjectLink)) + .Select(x => x.WellProjectLink) + .Distinct() + .ToListAsync(); + + foreach (var wellProjectId in wellProjectIdsUsedByCases) + { + await UpdateWellProjectCostProfiles(wellProjectId); + } + } + + public async Task UpdateWellProjectCostProfiles(Guid wellProjectId) + { + var wellProject = await context.WellProjects.SingleOrDefaultAsync(x => x.Id == wellProjectId) + ?? throw new NotFoundInDbException($"WellProject {wellProjectId} not found in database."); + + var wellIds = await context.WellProjectWell + .Where(ew => ew.WellProjectId == wellProjectId) + .Select(ew => ew.WellId) + .ToListAsync(); + + var (oilProducerWells, wellProjectWellOilProducer) = await LoadWellDataForWellCategory(wellIds, wellProjectId, WellCategory.Oil_Producer); + var oilProducerTimeSeries = GenerateWellProjectCostProfileFromDrillingSchedulesAndWellCost(oilProducerWells, wellProjectWellOilProducer); + var oilProducerCostProfile = new OilProducerCostProfile + { + Values = oilProducerTimeSeries.Values, + StartYear = oilProducerTimeSeries.StartYear + }; + + var (gasProducerWells, wellProjectWellGasProducer) = await LoadWellDataForWellCategory(wellIds, wellProjectId, WellCategory.Gas_Producer); + var gasProducerTimeSeries = GenerateWellProjectCostProfileFromDrillingSchedulesAndWellCost(gasProducerWells, wellProjectWellGasProducer); + var gasProducerCostProfile = new GasProducerCostProfile + { + Values = gasProducerTimeSeries.Values, + StartYear = gasProducerTimeSeries.StartYear + }; + + var (waterInjectorWells, wellProjectWellWaterInjector) = await LoadWellDataForWellCategory(wellIds, wellProjectId, WellCategory.Water_Injector); + var waterInjectorTimeSeries = GenerateWellProjectCostProfileFromDrillingSchedulesAndWellCost(waterInjectorWells, wellProjectWellWaterInjector); + var waterInjectorCostProfile = new WaterInjectorCostProfile + { + Values = waterInjectorTimeSeries.Values, + StartYear = waterInjectorTimeSeries.StartYear + }; + + var (gasInjectorWells, wellProjectWellGasInjector) = await LoadWellDataForWellCategory(wellIds, wellProjectId, WellCategory.Gas_Injector); + var gasInjectorTimeSeries = GenerateWellProjectCostProfileFromDrillingSchedulesAndWellCost(gasInjectorWells, wellProjectWellGasInjector); + var gasInjectorCostProfile = new GasInjectorCostProfile + { + Values = gasInjectorTimeSeries.Values, + StartYear = gasInjectorTimeSeries.StartYear + }; + + wellProject.OilProducerCostProfile = oilProducerCostProfile; + wellProject.GasProducerCostProfile = gasProducerCostProfile; + wellProject.WaterInjectorCostProfile = waterInjectorCostProfile; + wellProject.GasInjectorCostProfile = gasInjectorCostProfile; + } + + private async Task<(List, List)> LoadWellDataForWellCategory(List wellIds, Guid wellProjectId, WellCategory wellCategory) + { + var wells = await context.Wells + .Where(w => wellIds.Contains(w.Id)) + .Where(w => w.WellCategory == wellCategory) + .ToListAsync(); + + var wellWellIds = wells.Select(w => w.Id).ToList(); + + var wellProjectWells = await context.WellProjectWell + .Include(wpw => wpw.DrillingSchedule) + .Where(ew => ew.WellProjectId == wellProjectId) + .Where(ew => wellWellIds.Contains(ew.WellId)) + .ToListAsync(); + + return (wells, wellProjectWells); + } + + private static TimeSeries GenerateWellProjectCostProfileFromDrillingSchedulesAndWellCost(List wells, List wellProjectWells) + { + var costProfilesList = new List?>(); + + foreach (var wellProjectWell in wellProjectWells) + { + if (wellProjectWell.DrillingSchedule?.Values.Length > 0) + { + var well = wells.Single(w => w.Id == wellProjectWell.WellId); + var values = wellProjectWell.DrillingSchedule.Values.Select(ds => ds * well.WellCost).ToArray(); + + costProfilesList.Add(new TimeSeries + { + Values = values, + StartYear = wellProjectWell.DrillingSchedule.StartYear + }); + } + } + + return TimeSeriesCost.MergeCostProfilesList(costProfilesList); + } +} diff --git a/backend/api/ModelMapping/AutoMapperProfiles/WellProfile.cs b/backend/api/ModelMapping/AutoMapperProfiles/WellProfile.cs index 14017e174..1ddb99501 100644 --- a/backend/api/ModelMapping/AutoMapperProfiles/WellProfile.cs +++ b/backend/api/ModelMapping/AutoMapperProfiles/WellProfile.cs @@ -1,8 +1,6 @@ using api.Features.CaseProfiles.Dtos; using api.Features.CaseProfiles.Dtos.Well; -using api.Features.Wells.Create; using api.Features.Wells.Get; -using api.Features.Wells.Update; using api.Models; using AutoMapper; @@ -15,9 +13,6 @@ public WellProfile() { CreateMap().ReverseMap(); - CreateMap(); - CreateMap(); - CreateMap().ReverseMap(); CreateMap(); CreateMap(); diff --git a/frontend/src/Components/EditTechnicalInput/EditTechnicalInputModal.tsx b/frontend/src/Components/EditTechnicalInput/EditTechnicalInputModal.tsx index 389cf88ba..ca662f9a9 100644 --- a/frontend/src/Components/EditTechnicalInput/EditTechnicalInputModal.tsx +++ b/frontend/src/Components/EditTechnicalInput/EditTechnicalInputModal.tsx @@ -67,21 +67,23 @@ const EditTechnicalInputModal = () => { const handleSave = async () => { if (!revisionAndProjectData) { return } try { - const dto: Components.Schemas.UpdateTechnicalInputDto = {} setIsSaving(true) - dto.projectDto = { ...revisionAndProjectData.commonProjectAndRevisionData } - - dto.explorationOperationalWellCostsDto = explorationOperationalWellCosts - dto.developmentOperationalWellCostsDto = developmentOperationalWellCosts const wellDtos = [...explorationWells, ...wellProjectWells] - dto.createWellDtos = wellDtos.filter((w) => w.id === EMPTY_GUID || w.id === undefined || w.id === null || w.id === "") - dto.updateWellDtos = wellDtos.filter((w) => w.id !== EMPTY_GUID && w.id !== undefined && w.id !== null && w.id !== "") - dto.deleteWellDtos = deletedWells.map((id) => ({ id })) + const updateTechnicalInputDto = { + projectDto: { ...revisionAndProjectData.commonProjectAndRevisionData }, + + explorationOperationalWellCostsDto: explorationOperationalWellCosts || {}, + developmentOperationalWellCostsDto: developmentOperationalWellCosts || {}, + + createWellDtos: wellDtos.filter((w) => w.id === EMPTY_GUID || w.id === undefined || w.id === null || w.id === ""), + updateWellDtos: wellDtos.filter((w) => w.id !== EMPTY_GUID && w.id !== undefined && w.id !== null && w.id !== ""), + deleteWellDtos: deletedWells.map((id) => ({ id })) + } // refactor to use react-query? - const projectData = await (await GetTechnicalInputService()).update(revisionAndProjectData.projectId, dto) + const projectData = await (await GetTechnicalInputService()).update(revisionAndProjectData.projectId, updateTechnicalInputDto) addProjectEdit(projectData.projectId, projectData.commonProjectAndRevisionData) diff --git a/frontend/src/Services/TechnicalInputService.ts b/frontend/src/Services/TechnicalInputService.ts index 5700c34c8..ac9ac9cdd 100644 --- a/frontend/src/Services/TechnicalInputService.ts +++ b/frontend/src/Services/TechnicalInputService.ts @@ -5,7 +5,7 @@ import { config } from "./config" import { getToken, loginAccessTokenKey } from "../Utils/common" class __TechnicalInputService extends __BaseService { - public async update(projectId: string, body: any): Promise { + public async update(projectId: string, body: Components.Schemas.UpdateTechnicalInputDto): Promise { const res: Components.Schemas.ProjectDataDto = await this.put(`projects/${projectId}/technical-input`, { body }) return res } diff --git a/frontend/src/types.d.ts b/frontend/src/types.d.ts index b9d253999..9df37ccf2 100644 --- a/frontend/src/types.d.ts +++ b/frontend/src/types.d.ts @@ -1558,12 +1558,12 @@ declare namespace Components { override?: boolean; } export interface UpdateTechnicalInputDto { - developmentOperationalWellCostsDto?: UpdateDevelopmentOperationalWellCostsDto; - explorationOperationalWellCostsDto?: UpdateExplorationOperationalWellCostsDto; - updateWellDtos?: UpdateWellDto[] | null; - createWellDtos?: CreateWellDto[] | null; - deleteWellDtos?: DeleteWellDto[] | null; - projectDto?: UpdateProjectDto; + developmentOperationalWellCostsDto: UpdateDevelopmentOperationalWellCostsDto; + explorationOperationalWellCostsDto: UpdateExplorationOperationalWellCostsDto; + updateWellDtos: UpdateWellDto[]; + createWellDtos: CreateWellDto[]; + deleteWellDtos: DeleteWellDto[]; + projectDto: UpdateProjectDto; } export interface UpdateTopsideCostProfileOverrideDto { startYear?: number; // int32