Optimized the Manage infra API in Project Controller
This commit is contained in:
parent
57b7f941e6
commit
eabd31f8cf
@ -95,6 +95,13 @@ namespace Marco.Pms.CacheHelper
|
||||
var result = await _projetCollection.DeleteOneAsync(filter);
|
||||
return result.DeletedCount > 0;
|
||||
}
|
||||
public async Task<bool> RemoveProjectsFromCacheAsync(List<Guid> projectIds)
|
||||
{
|
||||
var stringIds = projectIds.Select(id => id.ToString()).ToList();
|
||||
var filter = Builders<ProjectMongoDB>.Filter.In(p => p.Id, stringIds);
|
||||
var result = await _projetCollection.DeleteManyAsync(filter);
|
||||
return result.DeletedCount > 0;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------- Project InfraStructure -------------------------------------------------------
|
||||
|
||||
|
@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Marco.Pms.Model.Dtos.Project
|
||||
{
|
||||
public class BuildingDot
|
||||
public class BuildingDto
|
||||
{
|
||||
[Key]
|
||||
public Guid? Id { get; set; }
|
@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Marco.Pms.Model.Dtos.Project
|
||||
{
|
||||
public class FloorDot
|
||||
public class FloorDto
|
||||
{
|
||||
public Guid? Id { get; set; }
|
||||
|
@ -1,9 +0,0 @@
|
||||
namespace Marco.Pms.Model.Dtos.Project
|
||||
{
|
||||
public class InfraDot
|
||||
{
|
||||
public BuildingDot? Building { get; set; }
|
||||
public FloorDot? Floor { get; set; }
|
||||
public WorkAreaDot? WorkArea { get; set; }
|
||||
}
|
||||
}
|
9
Marco.Pms.Model/Dtos/Projects/InfraDto.cs
Normal file
9
Marco.Pms.Model/Dtos/Projects/InfraDto.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Marco.Pms.Model.Dtos.Project
|
||||
{
|
||||
public class InfraDto
|
||||
{
|
||||
public BuildingDto? Building { get; set; }
|
||||
public FloorDto? Floor { get; set; }
|
||||
public WorkAreaDto? WorkArea { get; set; }
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Marco.Pms.Model.Dtos.Project
|
||||
{
|
||||
public class WorkAreaDot
|
||||
public class WorkAreaDto
|
||||
{
|
||||
[Key]
|
||||
public Guid? Id { get; set; }
|
@ -5,7 +5,7 @@ namespace Marco.Pms.Model.Mapper
|
||||
{
|
||||
public static class BuildingMapper
|
||||
{
|
||||
public static Building ToBuildingFromBuildingDto(this BuildingDot model, Guid tenantId)
|
||||
public static Building ToBuildingFromBuildingDto(this BuildingDto model, Guid tenantId)
|
||||
{
|
||||
return new Building
|
||||
{
|
||||
@ -20,7 +20,7 @@ namespace Marco.Pms.Model.Mapper
|
||||
|
||||
public static class FloorMapper
|
||||
{
|
||||
public static Floor ToFloorFromFloorDto(this FloorDot model, Guid tenantId)
|
||||
public static Floor ToFloorFromFloorDto(this FloorDto model, Guid tenantId)
|
||||
{
|
||||
return new Floor
|
||||
{
|
||||
@ -34,7 +34,7 @@ namespace Marco.Pms.Model.Mapper
|
||||
|
||||
public static class WorAreaMapper
|
||||
{
|
||||
public static WorkArea ToWorkAreaFromWorkAreaDto(this WorkAreaDot model, Guid tenantId)
|
||||
public static WorkArea ToWorkAreaFromWorkAreaDto(this WorkAreaDto model, Guid tenantId)
|
||||
{
|
||||
return new WorkArea
|
||||
{
|
||||
|
8
Marco.Pms.Model/Utilities/ServiceResponse.cs
Normal file
8
Marco.Pms.Model/Utilities/ServiceResponse.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Marco.Pms.Model.Utilities
|
||||
{
|
||||
public class ServiceResponse
|
||||
{
|
||||
public object? Notification { get; set; }
|
||||
public ApiResponse<object> Response { get; set; } = ApiResponse<object>.ErrorResponse("");
|
||||
}
|
||||
}
|
@ -1,10 +1,8 @@
|
||||
using Marco.Pms.DataAccess.Data;
|
||||
using Marco.Pms.Model.Dtos.Project;
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.Mapper;
|
||||
using Marco.Pms.Model.Projects;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using Marco.Pms.Model.ViewModels.Projects;
|
||||
using Marco.Pms.Services.Helpers;
|
||||
using Marco.Pms.Services.Service;
|
||||
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||
@ -359,6 +357,30 @@ namespace MarcoBMS.Services.Controllers
|
||||
|
||||
#region =================================================================== Project Infrastructre Manage APIs ===================================================================
|
||||
|
||||
[HttpPost("manage-infra")]
|
||||
public async Task<IActionResult> ManageProjectInfra(List<InfraDto> infraDtos)
|
||||
{
|
||||
// --- Step 1: Input Validation ---
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList();
|
||||
_logger.LogWarning("project Alocation called with invalid model state for list of projects. Errors: {Errors}", string.Join(", ", errors));
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request data provided.", errors, 400));
|
||||
}
|
||||
|
||||
// --- Step 2: Prepare data without I/O ---
|
||||
Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var serviceResponse = await _projectServices.ManageProjectInfraAsync(infraDtos, tenantId, loggedInEmployee);
|
||||
var response = serviceResponse.Response;
|
||||
var notification = serviceResponse.Notification;
|
||||
if (notification != null)
|
||||
{
|
||||
await _signalR.SendNotificationAsync(notification);
|
||||
}
|
||||
return StatusCode(response.StatusCode, response);
|
||||
|
||||
}
|
||||
|
||||
[HttpPost("task")]
|
||||
public async Task<IActionResult> CreateProjectTask([FromBody] List<WorkItemDto> workItemDtos)
|
||||
{
|
||||
@ -439,134 +461,6 @@ namespace MarcoBMS.Services.Controllers
|
||||
return Ok(ApiResponse<object>.SuccessResponse(new { }, "Task deleted successfully", 200));
|
||||
}
|
||||
|
||||
[HttpPost("manage-infra")]
|
||||
public async Task<IActionResult> ManageProjectInfra(List<InfraDot> infraDots)
|
||||
{
|
||||
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
|
||||
var responseData = new InfraVM { };
|
||||
string responseMessage = "";
|
||||
string message = "";
|
||||
List<Guid> projectIds = new List<Guid>();
|
||||
if (infraDots != null)
|
||||
{
|
||||
foreach (var item in infraDots)
|
||||
{
|
||||
if (item.Building != null)
|
||||
{
|
||||
|
||||
Building building = item.Building.ToBuildingFromBuildingDto(tenantId);
|
||||
building.TenantId = tenantId;
|
||||
|
||||
if (item.Building.Id == null)
|
||||
{
|
||||
//create
|
||||
_context.Buildings.Add(building);
|
||||
await _context.SaveChangesAsync();
|
||||
responseData.building = building;
|
||||
responseMessage = "Buliding Added Successfully";
|
||||
message = "Building Added";
|
||||
await _cache.AddBuildngInfra(building.ProjectId, building);
|
||||
}
|
||||
else
|
||||
{
|
||||
//update
|
||||
_context.Buildings.Update(building);
|
||||
await _context.SaveChangesAsync();
|
||||
responseData.building = building;
|
||||
responseMessage = "Buliding Updated Successfully";
|
||||
message = "Building Updated";
|
||||
await _cache.UpdateBuildngInfra(building.ProjectId, building);
|
||||
}
|
||||
projectIds.Add(building.ProjectId);
|
||||
}
|
||||
if (item.Floor != null)
|
||||
{
|
||||
Floor floor = item.Floor.ToFloorFromFloorDto(tenantId);
|
||||
floor.TenantId = tenantId;
|
||||
bool isCreated = false;
|
||||
|
||||
if (item.Floor.Id == null)
|
||||
{
|
||||
//create
|
||||
_context.Floor.Add(floor);
|
||||
await _context.SaveChangesAsync();
|
||||
responseData.floor = floor;
|
||||
responseMessage = "Floor Added Successfully";
|
||||
message = "Floor Added";
|
||||
isCreated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//update
|
||||
_context.Floor.Update(floor);
|
||||
await _context.SaveChangesAsync();
|
||||
responseData.floor = floor;
|
||||
responseMessage = "Floor Updated Successfully";
|
||||
message = "Floor Updated";
|
||||
}
|
||||
Building? building = await _context.Buildings.FirstOrDefaultAsync(b => b.Id == floor.BuildingId);
|
||||
var projectId = building?.ProjectId ?? Guid.Empty;
|
||||
projectIds.Add(projectId);
|
||||
message = $"{message} in Building: {building?.Name}";
|
||||
if (isCreated)
|
||||
{
|
||||
await _cache.AddBuildngInfra(projectId, floor: floor);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _cache.UpdateBuildngInfra(projectId, floor: floor);
|
||||
}
|
||||
}
|
||||
if (item.WorkArea != null)
|
||||
{
|
||||
WorkArea workArea = item.WorkArea.ToWorkAreaFromWorkAreaDto(tenantId);
|
||||
workArea.TenantId = tenantId;
|
||||
bool isCreated = false;
|
||||
|
||||
if (item.WorkArea.Id == null)
|
||||
{
|
||||
//create
|
||||
_context.WorkAreas.Add(workArea);
|
||||
await _context.SaveChangesAsync();
|
||||
responseData.workArea = workArea;
|
||||
responseMessage = "Work Area Added Successfully";
|
||||
message = "Work Area Added";
|
||||
isCreated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//update
|
||||
_context.WorkAreas.Update(workArea);
|
||||
await _context.SaveChangesAsync();
|
||||
responseData.workArea = workArea;
|
||||
responseMessage = "Work Area Updated Successfully";
|
||||
message = "Work Area Updated";
|
||||
}
|
||||
Floor? floor = await _context.Floor.Include(f => f.Building).FirstOrDefaultAsync(f => f.Id == workArea.FloorId);
|
||||
var projectId = floor?.Building?.ProjectId ?? Guid.Empty;
|
||||
projectIds.Add(projectId);
|
||||
message = $"{message} in Building: {floor?.Building?.Name}, on Floor: {floor?.FloorName}";
|
||||
if (isCreated)
|
||||
{
|
||||
await _cache.AddBuildngInfra(projectId, workArea: workArea, buildingId: floor?.BuildingId);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _cache.UpdateBuildngInfra(projectId, workArea: workArea, buildingId: floor?.BuildingId);
|
||||
}
|
||||
}
|
||||
}
|
||||
message = $"{message} by {LoggedInEmployee.FirstName} {LoggedInEmployee.LastName}";
|
||||
var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Infra", ProjectIds = projectIds, Message = message };
|
||||
|
||||
await _signalR.SendNotificationAsync(notification);
|
||||
return Ok(ApiResponse<object>.SuccessResponse(responseData, responseMessage, 200));
|
||||
}
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid details.", "Infra Details are not valid.", 400));
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
@ -478,6 +478,18 @@ namespace Marco.Pms.Services.Helpers
|
||||
|
||||
}
|
||||
}
|
||||
public async Task RemoveProjectsAsync(List<Guid> projectIds)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _projectCache.RemoveProjectsFromCacheAsync(projectIds);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occured while deleting project list from to Cache");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------ Project Infrastructure Cache ---------------------------------------
|
||||
|
||||
|
@ -51,6 +51,9 @@ namespace Marco.Pms.Services.MappingProfiles
|
||||
CreateMap<ProjectsAllocationDto, ProjectAllocation>();
|
||||
CreateMap<ProjectAllocation, ProjectAllocationVM>();
|
||||
|
||||
CreateMap<BuildingDto, Building>();
|
||||
CreateMap<FloorDto, Floor>();
|
||||
CreateMap<WorkAreaDto, WorkArea>();
|
||||
CreateMap<WorkItemDto, WorkItem>()
|
||||
.ForMember(
|
||||
dest => dest.Description,
|
||||
|
@ -1033,83 +1033,360 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
#region =================================================================== Project Infrastructre Manage APIs ===================================================================
|
||||
|
||||
public async Task<ApiResponse<object>> CreateProjectTask1(List<WorkItemDto> workItemDtos, Guid tenantId, Employee loggedInEmployee)
|
||||
public async Task<ApiResponse<object>> ManageProjectInfra(List<InfraDto> infraDots, Guid tenantId, Employee loggedInEmployee)
|
||||
{
|
||||
_logger.LogInfo("CreateProjectTask called with {Count} items", workItemDtos?.Count ?? 0);
|
||||
|
||||
// Validate request
|
||||
if (workItemDtos == null || !workItemDtos.Any())
|
||||
{
|
||||
_logger.LogWarning("No work items provided in the request.");
|
||||
return ApiResponse<object>.ErrorResponse("Invalid details.", "Work Item details are not valid.", 400);
|
||||
}
|
||||
|
||||
var workItemsToCreate = new List<WorkItem>();
|
||||
var workItemsToUpdate = new List<WorkItem>();
|
||||
var responseList = new List<WorkItemVM>();
|
||||
var responseData = new InfraVM { };
|
||||
string responseMessage = "";
|
||||
string message = "";
|
||||
List<Guid> workAreaIds = new List<Guid>();
|
||||
var workItemIds = workItemDtos.Where(wi => wi.Id != null && wi.Id != Guid.Empty).Select(wi => wi.Id).ToList();
|
||||
var workItems = await _context.WorkItems.AsNoTracking().Where(wi => workItemIds.Contains(wi.Id)).ToListAsync();
|
||||
|
||||
foreach (var itemDto in workItemDtos)
|
||||
List<Guid> projectIds = new List<Guid>();
|
||||
if (infraDots != null)
|
||||
{
|
||||
var workItem = _mapper.Map<WorkItem>(itemDto);
|
||||
workItem.TenantId = tenantId;
|
||||
var workArea = await _context.WorkAreas.Include(a => a.Floor).FirstOrDefaultAsync(a => a.Id == workItem.WorkAreaId) ?? new WorkArea();
|
||||
|
||||
Building building = await _context.Buildings.FirstOrDefaultAsync(b => b.Id == (workArea.Floor != null ? workArea.Floor.BuildingId : Guid.Empty)) ?? new Building();
|
||||
|
||||
if (itemDto.Id != null && itemDto.Id != Guid.Empty)
|
||||
foreach (var item in infraDots)
|
||||
{
|
||||
// Update existing
|
||||
workItemsToUpdate.Add(workItem);
|
||||
message = $"Task Updated in Building: {building.Name}, on Floor: {workArea.Floor?.FloorName}, in Area: {workArea.AreaName} by {loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||
var existingWorkItem = workItems.FirstOrDefault(wi => wi.Id == workItem.Id);
|
||||
if (existingWorkItem != null)
|
||||
if (item.Building != null)
|
||||
{
|
||||
double plannedWork = workItem.PlannedWork - existingWorkItem.PlannedWork;
|
||||
double completedWork = workItem.CompletedWork - existingWorkItem.CompletedWork;
|
||||
await _cache.UpdatePlannedAndCompleteWorksInBuilding(workArea.Id, plannedWork, completedWork);
|
||||
}
|
||||
|
||||
Building building = _mapper.Map<Building>(item.Building);
|
||||
building.TenantId = tenantId;
|
||||
|
||||
if (item.Building.Id == null)
|
||||
{
|
||||
//create
|
||||
_context.Buildings.Add(building);
|
||||
await _context.SaveChangesAsync();
|
||||
responseData.building = building;
|
||||
responseMessage = "Buliding Added Successfully";
|
||||
message = "Building Added";
|
||||
await _cache.AddBuildngInfra(building.ProjectId, building);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create new
|
||||
workItem.Id = Guid.NewGuid();
|
||||
workItemsToCreate.Add(workItem);
|
||||
message = $"Task Added in Building: {building.Name}, on Floor: {workArea.Floor?.FloorName}, in Area: {workArea.AreaName} by {loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||
await _cache.UpdatePlannedAndCompleteWorksInBuilding(workArea.Id, workItem.PlannedWork, workItem.CompletedWork);
|
||||
}
|
||||
|
||||
responseList.Add(new WorkItemVM
|
||||
{
|
||||
WorkItemId = workItem.Id,
|
||||
WorkItem = workItem
|
||||
});
|
||||
workAreaIds.Add(workItem.WorkAreaId);
|
||||
|
||||
}
|
||||
// Apply DB changes
|
||||
if (workItemsToCreate.Any())
|
||||
{
|
||||
_logger.LogInfo("Adding {Count} new work items", workItemsToCreate.Count);
|
||||
await _context.WorkItems.AddRangeAsync(workItemsToCreate);
|
||||
await _cache.ManageWorkItemDetails(workItemsToCreate);
|
||||
}
|
||||
|
||||
if (workItemsToUpdate.Any())
|
||||
{
|
||||
_logger.LogInfo("Updating {Count} existing work items", workItemsToUpdate.Count);
|
||||
_context.WorkItems.UpdateRange(workItemsToUpdate);
|
||||
await _cache.ManageWorkItemDetails(workItemsToUpdate);
|
||||
}
|
||||
|
||||
//update
|
||||
_context.Buildings.Update(building);
|
||||
await _context.SaveChangesAsync();
|
||||
responseData.building = building;
|
||||
responseMessage = "Buliding Updated Successfully";
|
||||
message = "Building Updated";
|
||||
await _cache.UpdateBuildngInfra(building.ProjectId, building);
|
||||
}
|
||||
projectIds.Add(building.ProjectId);
|
||||
}
|
||||
if (item.Floor != null)
|
||||
{
|
||||
Floor floor = _mapper.Map<Floor>(item.Floor);
|
||||
floor.TenantId = tenantId;
|
||||
bool isCreated = false;
|
||||
|
||||
_logger.LogInfo("CreateProjectTask completed successfully. Created: {Created}, Updated: {Updated}", workItemsToCreate.Count, workItemsToUpdate.Count);
|
||||
if (item.Floor.Id == null)
|
||||
{
|
||||
//create
|
||||
_context.Floor.Add(floor);
|
||||
await _context.SaveChangesAsync();
|
||||
responseData.floor = floor;
|
||||
responseMessage = "Floor Added Successfully";
|
||||
message = "Floor Added";
|
||||
isCreated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//update
|
||||
_context.Floor.Update(floor);
|
||||
await _context.SaveChangesAsync();
|
||||
responseData.floor = floor;
|
||||
responseMessage = "Floor Updated Successfully";
|
||||
message = "Floor Updated";
|
||||
}
|
||||
Building? building = await _context.Buildings.FirstOrDefaultAsync(b => b.Id == floor.BuildingId);
|
||||
var projectId = building?.ProjectId ?? Guid.Empty;
|
||||
projectIds.Add(projectId);
|
||||
message = $"{message} in Building: {building?.Name}";
|
||||
if (isCreated)
|
||||
{
|
||||
await _cache.AddBuildngInfra(projectId, floor: floor);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _cache.UpdateBuildngInfra(projectId, floor: floor);
|
||||
}
|
||||
}
|
||||
if (item.WorkArea != null)
|
||||
{
|
||||
WorkArea workArea = _mapper.Map<WorkArea>(item.WorkArea);
|
||||
workArea.TenantId = tenantId;
|
||||
bool isCreated = false;
|
||||
|
||||
return ApiResponse<object>.SuccessResponse(responseList, message, 200);
|
||||
if (item.WorkArea.Id == null)
|
||||
{
|
||||
//create
|
||||
_context.WorkAreas.Add(workArea);
|
||||
await _context.SaveChangesAsync();
|
||||
responseData.workArea = workArea;
|
||||
responseMessage = "Work Area Added Successfully";
|
||||
message = "Work Area Added";
|
||||
isCreated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//update
|
||||
_context.WorkAreas.Update(workArea);
|
||||
await _context.SaveChangesAsync();
|
||||
responseData.workArea = workArea;
|
||||
responseMessage = "Work Area Updated Successfully";
|
||||
message = "Work Area Updated";
|
||||
}
|
||||
Floor? floor = await _context.Floor.Include(f => f.Building).FirstOrDefaultAsync(f => f.Id == workArea.FloorId);
|
||||
var projectId = floor?.Building?.ProjectId ?? Guid.Empty;
|
||||
projectIds.Add(projectId);
|
||||
message = $"{message} in Building: {floor?.Building?.Name}, on Floor: {floor?.FloorName}";
|
||||
if (isCreated)
|
||||
{
|
||||
await _cache.AddBuildngInfra(projectId, workArea: workArea, buildingId: floor?.BuildingId);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _cache.UpdateBuildngInfra(projectId, workArea: workArea, buildingId: floor?.BuildingId);
|
||||
}
|
||||
}
|
||||
}
|
||||
message = $"{message} by {loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||
var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Infra", ProjectIds = projectIds, Message = message };
|
||||
|
||||
return ApiResponse<object>.SuccessResponse(responseData, responseMessage, 200);
|
||||
}
|
||||
return ApiResponse<object>.ErrorResponse("Invalid details.", "Infra Details are not valid.", 400);
|
||||
|
||||
}
|
||||
|
||||
public async Task<ServiceResponse> ManageProjectInfraAsync(List<InfraDto> infraDtos, Guid tenantId, Employee loggedInEmployee)
|
||||
{
|
||||
// 1. Guard Clause: Handle null or empty input gracefully.
|
||||
if (infraDtos == null || !infraDtos.Any())
|
||||
{
|
||||
return new ServiceResponse
|
||||
{
|
||||
Response = ApiResponse<object>.ErrorResponse("Invalid details.", "No infrastructure details were provided.", 400)
|
||||
};
|
||||
}
|
||||
|
||||
var responseData = new InfraVM();
|
||||
var messages = new List<string>();
|
||||
var projectIds = new HashSet<Guid>(); // Use HashSet for automatic duplicate handling.
|
||||
var cacheUpdateTasks = new List<Task>();
|
||||
|
||||
// --- Pre-fetch parent entities to avoid N+1 query problem ---
|
||||
// 2. Gather all parent IDs needed for validation and context.
|
||||
var requiredBuildingIds = infraDtos
|
||||
.Where(i => i.Floor?.BuildingId != null)
|
||||
.Select(i => i.Floor!.BuildingId)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
var requiredFloorIds = infraDtos
|
||||
.Where(i => i.WorkArea?.FloorId != null)
|
||||
.Select(i => i.WorkArea!.FloorId)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
// 3. Fetch all required parent entities in single batch queries.
|
||||
var buildingsDict = await _context.Buildings
|
||||
.Where(b => requiredBuildingIds.Contains(b.Id))
|
||||
.ToDictionaryAsync(b => b.Id);
|
||||
|
||||
var floorsDict = await _context.Floor
|
||||
.Include(f => f.Building) // Eagerly load Building for later use
|
||||
.Where(f => requiredFloorIds.Contains(f.Id))
|
||||
.ToDictionaryAsync(f => f.Id);
|
||||
// --- End Pre-fetching ---
|
||||
|
||||
// 4. Process all entities and add them to the context's change tracker.
|
||||
foreach (var item in infraDtos)
|
||||
{
|
||||
if (item.Building != null)
|
||||
{
|
||||
ProcessBuilding(item.Building, tenantId, responseData, messages, projectIds, cacheUpdateTasks);
|
||||
}
|
||||
if (item.Floor != null)
|
||||
{
|
||||
ProcessFloor(item.Floor, tenantId, responseData, messages, projectIds, cacheUpdateTasks, buildingsDict);
|
||||
}
|
||||
if (item.WorkArea != null)
|
||||
{
|
||||
ProcessWorkArea(item.WorkArea, tenantId, responseData, messages, projectIds, cacheUpdateTasks, floorsDict);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Save all changes to the database in a single transaction.
|
||||
var changedRecordCount = await _context.SaveChangesAsync();
|
||||
|
||||
// If no changes were actually made, we can exit early.
|
||||
if (changedRecordCount == 0)
|
||||
{
|
||||
return new ServiceResponse
|
||||
{
|
||||
Response = ApiResponse<object>.SuccessResponse(responseData, "No changes detected in the provided infrastructure details.", 200)
|
||||
};
|
||||
}
|
||||
|
||||
// 6. Execute all cache updates concurrently after the DB save is successful.
|
||||
await Task.WhenAll(cacheUpdateTasks);
|
||||
|
||||
// 7. Consolidate messages and create notification payload.
|
||||
string finalResponseMessage = messages.LastOrDefault() ?? "Infrastructure managed successfully.";
|
||||
string logMessage = $"{string.Join(", ", messages)} by {loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||
var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Infra", ProjectIds = projectIds.ToList(), Message = logMessage };
|
||||
|
||||
// TODO: Dispatch the 'notification' object to your notification service.
|
||||
|
||||
return new ServiceResponse
|
||||
{
|
||||
Notification = notification,
|
||||
Response = ApiResponse<object>.SuccessResponse(responseData, finalResponseMessage, 200)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages a batch of infrastructure changes (creates/updates for Buildings, Floors, and WorkAreas).
|
||||
/// This method is optimized to perform all database operations in a single, atomic transaction.
|
||||
/// </summary>
|
||||
public async Task<ApiResponse<object>> ManageProjectInfraAsync1(List<InfraDto> infraDtos, Guid tenantId, Employee loggedInEmployee)
|
||||
{
|
||||
// --- Step 1: Input Validation ---
|
||||
if (infraDtos == null || !infraDtos.Any())
|
||||
{
|
||||
_logger.LogWarning("ManageProjectInfraAsync called with null or empty DTO list.");
|
||||
return ApiResponse<object>.ErrorResponse("Invalid details.", "Infrastructure data cannot be empty.", 400);
|
||||
}
|
||||
|
||||
_logger.LogInfo("Begin ManageProjectInfraAsync for {DtoCount} items, TenantId: {TenantId}, User: {UserId}", infraDtos.Count, tenantId, loggedInEmployee.Id);
|
||||
|
||||
// --- Step 2: Categorize DTOs by Type and Action ---
|
||||
var buildingsToCreateDto = infraDtos.Where(i => i.Building != null && i.Building.Id == null).Select(i => i.Building!).ToList();
|
||||
var buildingsToUpdateDto = infraDtos.Where(i => i.Building != null && i.Building.Id != null).Select(i => i.Building!).ToList();
|
||||
var floorsToCreateDto = infraDtos.Where(i => i.Floor != null && i.Floor.Id == null).Select(i => i.Floor!).ToList();
|
||||
var floorsToUpdateDto = infraDtos.Where(i => i.Floor != null && i.Floor.Id != null).Select(i => i.Floor!).ToList();
|
||||
var workAreasToCreateDto = infraDtos.Where(i => i.WorkArea != null && i.WorkArea.Id == null).Select(i => i.WorkArea!).ToList();
|
||||
var workAreasToUpdateDto = infraDtos.Where(i => i.WorkArea != null && i.WorkArea.Id != null).Select(i => i.WorkArea!).ToList();
|
||||
|
||||
_logger.LogDebug("Categorized DTOs...");
|
||||
|
||||
try
|
||||
{
|
||||
// --- Step 3: Fetch all required existing data in bulk ---
|
||||
|
||||
// Fetch existing entities to be updated
|
||||
var buildingIdsToUpdate = buildingsToUpdateDto.Select(d => d.Id!.Value).ToList();
|
||||
var existingBuildings = await _context.Buildings.Where(b => buildingIdsToUpdate.Contains(b.Id) && b.TenantId == tenantId).ToDictionaryAsync(b => b.Id);
|
||||
|
||||
var floorIdsToUpdate = floorsToUpdateDto.Select(d => d.Id!.Value).ToList();
|
||||
var existingFloors = await _context.Floor.Include(f => f.Building).Where(f => floorIdsToUpdate.Contains(f.Id) && f.TenantId == tenantId).ToDictionaryAsync(f => f.Id);
|
||||
|
||||
var workAreaIdsToUpdate = workAreasToUpdateDto.Select(d => d.Id!.Value).ToList();
|
||||
var existingWorkAreas = await _context.WorkAreas.Include(wa => wa.Floor!.Building).Where(wa => workAreaIdsToUpdate.Contains(wa.Id) && wa.TenantId == tenantId).ToDictionaryAsync(wa => wa.Id);
|
||||
|
||||
// Fetch parent entities for items being created to get their ProjectIds
|
||||
var buildingIdsForNewFloors = floorsToCreateDto.Select(f => f.BuildingId).ToList();
|
||||
var parentBuildingsForNewFloors = await _context.Buildings.Where(b => buildingIdsForNewFloors.Contains(b.Id)).ToDictionaryAsync(b => b.Id);
|
||||
|
||||
var floorIdsForNewWorkAreas = workAreasToCreateDto.Select(wa => wa.FloorId).ToList();
|
||||
var parentFloorsForNewWorkAreas = await _context.Floor.Include(f => f.Building).Where(f => floorIdsForNewWorkAreas.Contains(f.Id)).ToDictionaryAsync(f => f.Id);
|
||||
|
||||
_logger.LogInfo("Fetched existing entities and parents for new items.");
|
||||
|
||||
// --- Step 4: Aggregate all affected ProjectIds for Security Check ---
|
||||
var affectedProjectIds = new HashSet<Guid>();
|
||||
|
||||
// From buildings being created/updated
|
||||
buildingsToCreateDto.ForEach(b => affectedProjectIds.Add(b.ProjectId));
|
||||
foreach (var b in existingBuildings.Values) { affectedProjectIds.Add(b.ProjectId); }
|
||||
|
||||
// From floors being created/updated
|
||||
foreach (var f in floorsToCreateDto) { if (parentBuildingsForNewFloors.TryGetValue(f.BuildingId, out var b)) affectedProjectIds.Add(b.ProjectId); }
|
||||
foreach (var f in existingFloors.Values) { if (f.Building != null) affectedProjectIds.Add(f.Building.ProjectId); }
|
||||
|
||||
// From work areas being created/updated
|
||||
foreach (var wa in workAreasToCreateDto) { if (parentFloorsForNewWorkAreas.TryGetValue(wa.FloorId, out var f) && f.Building != null) affectedProjectIds.Add(f.Building.ProjectId); }
|
||||
foreach (var wa in existingWorkAreas.Values) { if (wa.Floor?.Building != null) affectedProjectIds.Add(wa.Floor.Building.ProjectId); }
|
||||
|
||||
// Security Check against the complete list of affected projects
|
||||
var hasPermission = await _permission.HasPermission(PermissionsMaster.ManageProjectInfra, loggedInEmployee.Id);
|
||||
if (!hasPermission)
|
||||
{
|
||||
_logger.LogWarning("Access DENIED for user {UserId} trying to manage infrastructure for projects.", loggedInEmployee.Id);
|
||||
return ApiResponse<object>.ErrorResponse("Access Denied.", "You do not have permission to manage infrastructure for one or more of the specified projects.", 403);
|
||||
}
|
||||
|
||||
// --- Step 5: Process all logic IN MEMORY, tracking changes ---
|
||||
|
||||
// Process Buildings
|
||||
var createdBuildings = new List<Building>();
|
||||
foreach (var dto in buildingsToCreateDto)
|
||||
{
|
||||
var newBuilding = _mapper.Map<Building>(dto);
|
||||
newBuilding.TenantId = tenantId;
|
||||
createdBuildings.Add(newBuilding);
|
||||
}
|
||||
foreach (var dto in buildingsToUpdateDto) { if (existingBuildings.TryGetValue(dto.Id!.Value, out var b)) _mapper.Map(dto, b); }
|
||||
|
||||
// Process Floors
|
||||
var createdFloors = new List<Floor>();
|
||||
foreach (var dto in floorsToCreateDto)
|
||||
{
|
||||
var newFloor = _mapper.Map<Floor>(dto);
|
||||
newFloor.TenantId = tenantId;
|
||||
createdFloors.Add(newFloor);
|
||||
}
|
||||
foreach (var dto in floorsToUpdateDto) { if (existingFloors.TryGetValue(dto.Id!.Value, out var f)) _mapper.Map(dto, f); }
|
||||
|
||||
// Process WorkAreas
|
||||
var createdWorkAreas = new List<WorkArea>();
|
||||
foreach (var dto in workAreasToCreateDto)
|
||||
{
|
||||
var newWorkArea = _mapper.Map<WorkArea>(dto);
|
||||
newWorkArea.TenantId = tenantId;
|
||||
createdWorkAreas.Add(newWorkArea);
|
||||
}
|
||||
foreach (var dto in workAreasToUpdateDto) { if (existingWorkAreas.TryGetValue(dto.Id!.Value, out var wa)) _mapper.Map(dto, wa); }
|
||||
|
||||
// --- Step 6: Save all database changes in a SINGLE TRANSACTION ---
|
||||
if (createdBuildings.Any()) _context.Buildings.AddRange(createdBuildings);
|
||||
if (createdFloors.Any()) _context.Floor.AddRange(createdFloors);
|
||||
if (createdWorkAreas.Any()) _context.WorkAreas.AddRange(createdWorkAreas);
|
||||
|
||||
if (_context.ChangeTracker.HasChanges())
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
_logger.LogInfo("Database save successful.");
|
||||
}
|
||||
|
||||
// --- Step 7: Update Cache using the aggregated ProjectIds (Non-blocking) ---
|
||||
var finalProjectIds = affectedProjectIds.ToList();
|
||||
if (finalProjectIds.Any())
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInfo("Queuing background cache update for {ProjectCount} projects.", finalProjectIds.Count);
|
||||
// Assuming your cache service has a method to handle this.
|
||||
await _cache.RemoveProjectsAsync(finalProjectIds);
|
||||
_logger.LogInfo("Background cache update task completed for projects: {ProjectIds}", string.Join(", ", finalProjectIds));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "An error occurred during the background cache update task for projects: {ProjectIds}", string.Join(", ", finalProjectIds));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- Step 8: Prepare and return a clear response ---
|
||||
var responseVm = new { /* ... as before ... */ };
|
||||
return ApiResponse<object>.SuccessResponse(responseVm, "Infrastructure changes processed successfully.", 200);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "An unexpected error occurred in ManageProjectInfraAsync.");
|
||||
return ApiResponse<object>.ErrorResponse("Internal Server Error", "An unexpected error occurred.", 500);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -1211,12 +1488,10 @@ namespace Marco.Pms.Services.Service
|
||||
await _context.SaveChangesAsync();
|
||||
_logger.LogInfo("Successfully saved {CreatedCount} new and {UpdatedCount} updated work items.", workItemsToCreate.Count, workItemsToModify.Count);
|
||||
|
||||
// --- Step 5: Update Cache and SignalR AFTER successful DB save (non-blocking) ---
|
||||
// --- Step 5: Update Cache and SignalR AFTER successful DB save ---
|
||||
var allAffectedItems = workItemsToCreate.Concat(workItemsToModify).ToList();
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
|
||||
await UpdateCacheAndNotify(workDeltaForCache, allAffectedItems);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (DbUpdateException ex)
|
||||
@ -1291,133 +1566,6 @@ namespace Marco.Pms.Services.Service
|
||||
// return Ok(ApiResponse<object>.SuccessResponse(new { }, "Task deleted successfully", 200));
|
||||
//}
|
||||
|
||||
//public async Task<IActionResult> ManageProjectInfra(List<InfraDot> infraDots)
|
||||
//{
|
||||
// var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
|
||||
// var responseData = new InfraVM { };
|
||||
// string responseMessage = "";
|
||||
// string message = "";
|
||||
// List<Guid> projectIds = new List<Guid>();
|
||||
// if (infraDots != null)
|
||||
// {
|
||||
// foreach (var item in infraDots)
|
||||
// {
|
||||
// if (item.Building != null)
|
||||
// {
|
||||
|
||||
// Building building = item.Building.ToBuildingFromBuildingDto(tenantId);
|
||||
// building.TenantId = tenantId;
|
||||
|
||||
// if (item.Building.Id == null)
|
||||
// {
|
||||
// //create
|
||||
// _context.Buildings.Add(building);
|
||||
// await _context.SaveChangesAsync();
|
||||
// responseData.building = building;
|
||||
// responseMessage = "Buliding Added Successfully";
|
||||
// message = "Building Added";
|
||||
// await _cache.AddBuildngInfra(building.ProjectId, building);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// //update
|
||||
// _context.Buildings.Update(building);
|
||||
// await _context.SaveChangesAsync();
|
||||
// responseData.building = building;
|
||||
// responseMessage = "Buliding Updated Successfully";
|
||||
// message = "Building Updated";
|
||||
// await _cache.UpdateBuildngInfra(building.ProjectId, building);
|
||||
// }
|
||||
// projectIds.Add(building.ProjectId);
|
||||
// }
|
||||
// if (item.Floor != null)
|
||||
// {
|
||||
// Floor floor = item.Floor.ToFloorFromFloorDto(tenantId);
|
||||
// floor.TenantId = tenantId;
|
||||
// bool isCreated = false;
|
||||
|
||||
// if (item.Floor.Id == null)
|
||||
// {
|
||||
// //create
|
||||
// _context.Floor.Add(floor);
|
||||
// await _context.SaveChangesAsync();
|
||||
// responseData.floor = floor;
|
||||
// responseMessage = "Floor Added Successfully";
|
||||
// message = "Floor Added";
|
||||
// isCreated = true;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// //update
|
||||
// _context.Floor.Update(floor);
|
||||
// await _context.SaveChangesAsync();
|
||||
// responseData.floor = floor;
|
||||
// responseMessage = "Floor Updated Successfully";
|
||||
// message = "Floor Updated";
|
||||
// }
|
||||
// Building? building = await _context.Buildings.FirstOrDefaultAsync(b => b.Id == floor.BuildingId);
|
||||
// var projectId = building?.ProjectId ?? Guid.Empty;
|
||||
// projectIds.Add(projectId);
|
||||
// message = $"{message} in Building: {building?.Name}";
|
||||
// if (isCreated)
|
||||
// {
|
||||
// await _cache.AddBuildngInfra(projectId, floor: floor);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// await _cache.UpdateBuildngInfra(projectId, floor: floor);
|
||||
// }
|
||||
// }
|
||||
// if (item.WorkArea != null)
|
||||
// {
|
||||
// WorkArea workArea = item.WorkArea.ToWorkAreaFromWorkAreaDto(tenantId);
|
||||
// workArea.TenantId = tenantId;
|
||||
// bool isCreated = false;
|
||||
|
||||
// if (item.WorkArea.Id == null)
|
||||
// {
|
||||
// //create
|
||||
// _context.WorkAreas.Add(workArea);
|
||||
// await _context.SaveChangesAsync();
|
||||
// responseData.workArea = workArea;
|
||||
// responseMessage = "Work Area Added Successfully";
|
||||
// message = "Work Area Added";
|
||||
// isCreated = true;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// //update
|
||||
// _context.WorkAreas.Update(workArea);
|
||||
// await _context.SaveChangesAsync();
|
||||
// responseData.workArea = workArea;
|
||||
// responseMessage = "Work Area Updated Successfully";
|
||||
// message = "Work Area Updated";
|
||||
// }
|
||||
// Floor? floor = await _context.Floor.Include(f => f.Building).FirstOrDefaultAsync(f => f.Id == workArea.FloorId);
|
||||
// var projectId = floor?.Building?.ProjectId ?? Guid.Empty;
|
||||
// projectIds.Add(projectId);
|
||||
// message = $"{message} in Building: {floor?.Building?.Name}, on Floor: {floor?.FloorName}";
|
||||
// if (isCreated)
|
||||
// {
|
||||
// await _cache.AddBuildngInfra(projectId, workArea: workArea, buildingId: floor?.BuildingId);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// await _cache.UpdateBuildngInfra(projectId, workArea: workArea, buildingId: floor?.BuildingId);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// message = $"{message} by {loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
|
||||
// var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Infra", ProjectIds = projectIds, Message = message };
|
||||
|
||||
// await _signalR.SendNotificationAsync(notification);
|
||||
// return Ok(ApiResponse<object>.SuccessResponse(responseData, responseMessage, 200));
|
||||
// }
|
||||
// return BadRequest(ApiResponse<object>.ErrorResponse("Invalid details.", "Infra Details are not valid.", 400));
|
||||
|
||||
//}
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Helper Functions ===================================================================
|
||||
@ -1663,6 +1811,82 @@ namespace Marco.Pms.Services.Service
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessBuilding(BuildingDto dto, Guid tenantId, InfraVM responseData, List<string> messages, ISet<Guid> projectIds, List<Task> cacheTasks)
|
||||
{
|
||||
Building building = _mapper.Map<Building>(dto);
|
||||
building.TenantId = tenantId;
|
||||
|
||||
bool isNew = dto.Id == null;
|
||||
if (isNew)
|
||||
{
|
||||
_context.Buildings.Add(building);
|
||||
messages.Add("Building Added");
|
||||
cacheTasks.Add(_cache.AddBuildngInfra(building.ProjectId, building));
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.Buildings.Update(building);
|
||||
messages.Add("Building Updated");
|
||||
cacheTasks.Add(_cache.UpdateBuildngInfra(building.ProjectId, building));
|
||||
}
|
||||
|
||||
responseData.building = building;
|
||||
projectIds.Add(building.ProjectId);
|
||||
}
|
||||
|
||||
private void ProcessFloor(FloorDto dto, Guid tenantId, InfraVM responseData, List<string> messages, ISet<Guid> projectIds, List<Task> cacheTasks, IDictionary<Guid, Building> buildings)
|
||||
{
|
||||
Floor floor = _mapper.Map<Floor>(dto);
|
||||
floor.TenantId = tenantId;
|
||||
|
||||
// Use the pre-fetched dictionary for parent lookup.
|
||||
Building? parentBuilding = buildings.TryGetValue(dto.BuildingId, out var b) ? b : null;
|
||||
|
||||
bool isNew = dto.Id == null;
|
||||
if (isNew)
|
||||
{
|
||||
_context.Floor.Add(floor);
|
||||
messages.Add($"Floor Added in Building: {parentBuilding?.Name ?? "Unknown"}");
|
||||
cacheTasks.Add(_cache.AddBuildngInfra(parentBuilding?.ProjectId ?? Guid.Empty, floor: floor));
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.Floor.Update(floor);
|
||||
messages.Add($"Floor Updated in Building: {parentBuilding?.Name ?? "Unknown"}");
|
||||
cacheTasks.Add(_cache.UpdateBuildngInfra(parentBuilding?.ProjectId ?? Guid.Empty, floor: floor));
|
||||
}
|
||||
|
||||
responseData.floor = floor;
|
||||
if (parentBuilding != null) projectIds.Add(parentBuilding.ProjectId);
|
||||
}
|
||||
|
||||
private void ProcessWorkArea(WorkAreaDto dto, Guid tenantId, InfraVM responseData, List<string> messages, ISet<Guid> projectIds, List<Task> cacheTasks, IDictionary<Guid, Floor> floors)
|
||||
{
|
||||
WorkArea workArea = _mapper.Map<WorkArea>(dto);
|
||||
workArea.TenantId = tenantId;
|
||||
|
||||
// Use the pre-fetched dictionary for parent lookup.
|
||||
Floor? parentFloor = floors.TryGetValue(dto.FloorId, out var f) ? f : null;
|
||||
var parentBuilding = parentFloor?.Building;
|
||||
|
||||
bool isNew = dto.Id == null;
|
||||
if (isNew)
|
||||
{
|
||||
_context.WorkAreas.Add(workArea);
|
||||
messages.Add($"Work Area Added in Building: {parentBuilding?.Name ?? "Unknown"}, on Floor: {parentFloor?.FloorName ?? "Unknown"}");
|
||||
cacheTasks.Add(_cache.AddBuildngInfra(parentBuilding?.ProjectId ?? Guid.Empty, workArea: workArea, buildingId: parentBuilding?.Id));
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.WorkAreas.Update(workArea);
|
||||
messages.Add($"Work Area Updated in Building: {parentBuilding?.Name ?? "Unknown"}, on Floor: {parentFloor?.FloorName ?? "Unknown"}");
|
||||
cacheTasks.Add(_cache.UpdateBuildngInfra(parentBuilding?.ProjectId ?? Guid.Empty, workArea: workArea, buildingId: parentBuilding?.Id));
|
||||
}
|
||||
|
||||
responseData.workArea = workArea;
|
||||
if (parentBuilding != null) projectIds.Add(parentBuilding.ProjectId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||
Task<ApiResponse<List<ProjectAllocationVM>>> AssigneProjectsToEmployeeAsync(List<ProjectsAllocationDto> projectAllocationDtos, Guid employeeId, Guid tenantId, Employee loggedInEmployee);
|
||||
Task<ApiResponse<object>> GetInfraDetailsAsync(Guid projectId, Guid tenantId, Employee loggedInEmployee);
|
||||
Task<ApiResponse<object>> GetWorkItemsAsync(Guid workAreaId, Guid tenantId, Employee loggedInEmployee);
|
||||
Task<ServiceResponse> ManageProjectInfraAsync(List<InfraDto> infraDtos, Guid tenantId, Employee loggedInEmployee);
|
||||
Task<ApiResponse<List<WorkItemVM>>> CreateProjectTaskAsync(List<WorkItemDto> workItemDtos, Guid tenantId, Employee loggedInEmployee);
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user