Added the project branch CRUD oprations
This commit is contained in:
parent
5a402925b1
commit
dad135571d
@ -96,6 +96,66 @@ namespace Marco.Pms.Services.Controllers
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Project Branch Functions ===================================================================
|
||||
|
||||
[HttpGet("branch/list/{projectId}")]
|
||||
public async Task<IActionResult> GetProjectBranchListByProject(Guid projectId, [FromQuery] string? searchString, [FromQuery] bool isActive = true,
|
||||
[FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 20)
|
||||
{
|
||||
Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.GetProjectBranchListByProjectAsync(projectId, isActive, searchString, pageNumber, pageSize, loggedInEmployee, tenantId);
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
[HttpGet("branch/details/{id}")]
|
||||
public async Task<IActionResult> GetProjectBranchDetails(Guid id)
|
||||
{
|
||||
Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.GetProjectBranchDetailsAsync(id, loggedInEmployee, tenantId);
|
||||
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
[HttpPost("branch/create")]
|
||||
public async Task<IActionResult> CreateProjectBranch([FromBody] ProjectBranchDto model)
|
||||
{
|
||||
Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.CreateProjectBranchAsync(model, loggedInEmployee, tenantId);
|
||||
if (response.Success)
|
||||
{
|
||||
var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Project_Branch", Response = response.Data };
|
||||
await _signalR.SendNotificationAsync(notification);
|
||||
}
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
[HttpPut("branch/edit/{id}")]
|
||||
public async Task<IActionResult> UpdateProjectBranch(Guid id, ProjectBranchDto model)
|
||||
{
|
||||
Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.UpdateProjectBranchAsync(id, model, loggedInEmployee, tenantId);
|
||||
if (response.Success)
|
||||
{
|
||||
var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Project_Branch", Response = response.Data };
|
||||
await _signalR.SendNotificationAsync(notification);
|
||||
}
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
[HttpDelete("branch/delete/{id}")]
|
||||
public async Task<IActionResult> DeleteProjectBranch(Guid id, [FromQuery] bool isActive = false)
|
||||
{
|
||||
Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.DeleteProjectBranchAsync(id, isActive, loggedInEmployee, tenantId);
|
||||
if (response.Success)
|
||||
{
|
||||
var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Project_Branch", Response = response.Data };
|
||||
await _signalR.SendNotificationAsync(notification);
|
||||
}
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Service Project Allocation Functions ===================================================================
|
||||
|
||||
[HttpGet("get/allocation/list")]
|
||||
|
||||
@ -15,6 +15,14 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||
Task<ApiResponse<object>> DeActivateServiceProjectAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId);
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Project Branch Functions ===================================================================
|
||||
Task<ApiResponse<object>> GetProjectBranchListByProjectAsync(Guid projectId, bool isActive, string? searchString, int pageNumber, int pageSize, Employee loggedInEmployee, Guid tenantId);
|
||||
Task<ApiResponse<object>> GetProjectBranchDetailsAsync(Guid id, Employee loggedInEmployee, Guid tenantId);
|
||||
Task<ApiResponse<object>> CreateProjectBranchAsync(ProjectBranchDto model, Employee loggedInEmployee, Guid tenantId);
|
||||
Task<ApiResponse<object>> UpdateProjectBranchAsync(Guid id, ProjectBranchDto model, Employee loggedInEmployee, Guid tenantId);
|
||||
Task<ApiResponse<object>> DeleteProjectBranchAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId);
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Service Project Allocation Functions ===================================================================
|
||||
Task<ApiResponse<object>> GetServiceProjectAllocationListAsync(Guid? projectId, Guid? employeeId, bool isActive, Employee loggedInEmployee, Guid tenantId);
|
||||
Task<ApiResponse<object>> ManageServiceProjectAllocationAsync(List<ServiceProjectAllocationDto> model, Employee loggedInEmployee, Guid tenantId);
|
||||
|
||||
@ -30,6 +30,7 @@ namespace Marco.Pms.Services.Service
|
||||
private readonly ILoggingService _logger;
|
||||
private readonly S3UploadService _s3Service;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly UtilityMongoDBHelper _updateLogHelper;
|
||||
|
||||
private readonly Guid NewStatus = Guid.Parse("32d76a02-8f44-4aa0-9b66-c3716c45a918");
|
||||
private readonly Guid AssignedStatus = Guid.Parse("cfa1886d-055f-4ded-84c6-42a2a8a14a66");
|
||||
@ -44,7 +45,8 @@ namespace Marco.Pms.Services.Service
|
||||
ApplicationDbContext context,
|
||||
ILoggingService logger,
|
||||
S3UploadService s3Service,
|
||||
IMapper mapper)
|
||||
IMapper mapper,
|
||||
UtilityMongoDBHelper updateLogHelper)
|
||||
{
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
_context = context;
|
||||
@ -52,11 +54,11 @@ namespace Marco.Pms.Services.Service
|
||||
_s3Service = s3Service;
|
||||
_mapper = mapper;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_updateLogHelper = updateLogHelper;
|
||||
}
|
||||
|
||||
#region =================================================================== Service Project Functions ===================================================================
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a paginated list of active service projects for a tenant, including related services, job counts, and team member information.
|
||||
/// </summary>
|
||||
@ -195,7 +197,6 @@ namespace Marco.Pms.Services.Service
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves detailed information for a specific service project, including related client, status, services, and audit information.
|
||||
/// </summary>
|
||||
@ -439,9 +440,7 @@ namespace Marco.Pms.Services.Service
|
||||
}
|
||||
|
||||
// Create BSON snapshot of existing entity for audit logging (MongoDB)
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var updateLogHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();
|
||||
BsonDocument existingEntityBson = updateLogHelper.EntityToBsonDocument(serviceProject);
|
||||
BsonDocument existingEntityBson = _updateLogHelper.EntityToBsonDocument(serviceProject);
|
||||
|
||||
// Map incoming DTO to the tracked entity
|
||||
_mapper.Map(model, serviceProject);
|
||||
@ -509,7 +508,7 @@ namespace Marco.Pms.Services.Service
|
||||
});
|
||||
|
||||
// Push update log asynchronously to MongoDB for audit
|
||||
var updateLogTask = updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject
|
||||
var updateLogTask = _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject
|
||||
{
|
||||
EntityId = id.ToString(),
|
||||
UpdatedById = loggedInEmployee.Id.ToString(),
|
||||
@ -566,9 +565,7 @@ namespace Marco.Pms.Services.Service
|
||||
}
|
||||
|
||||
// Create BSON snapshot of existing entity for audit logging (MongoDB)
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var updateLogHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();
|
||||
BsonDocument existingEntityBson = updateLogHelper.EntityToBsonDocument(serviceProject);
|
||||
BsonDocument existingEntityBson = _updateLogHelper.EntityToBsonDocument(serviceProject);
|
||||
|
||||
// Update active status as requested by the client
|
||||
serviceProject.IsActive = isActive;
|
||||
@ -576,7 +573,7 @@ namespace Marco.Pms.Services.Service
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// Push update log asynchronously to MongoDB for audit
|
||||
await updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject
|
||||
await _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject
|
||||
{
|
||||
EntityId = id.ToString(),
|
||||
UpdatedById = loggedInEmployee.Id.ToString(),
|
||||
@ -599,6 +596,349 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Project Branch Functions ===================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a paginated list of project branches filtered by activity status and optional search criteria.
|
||||
/// Implements enterprise-grade optimizations, detailed logging, and standardized error handling.
|
||||
/// </summary>
|
||||
/// <param name="projectId">Unique identifier for the project.</param>
|
||||
/// <param name="isActive">Filter by active/inactive branches.</param>
|
||||
/// <param name="searchString">Optional search string for filtering by branch name, address, or type.</param>
|
||||
/// <param name="pageNumber">Current page number for pagination.</param>
|
||||
/// <param name="pageSize">Number of records per page.</param>
|
||||
/// <param name="loggedInEmployee">Current logged-in employee details.</param>
|
||||
/// <param name="tenantId">Tenant identifier for multi-tenant architecture.</param>
|
||||
/// <returns>ApiResponse containing paginated branches or error details.</returns>
|
||||
public async Task<ApiResponse<object>> GetProjectBranchListByProjectAsync(Guid projectId, bool isActive, string? searchString, int pageNumber, int pageSize,
|
||||
Employee loggedInEmployee, Guid tenantId)
|
||||
{
|
||||
// Log method invocation with parameters for audit and debugging
|
||||
_logger.LogInfo("Fetching project branches for ProjectId: {ProjectId}, IsActive: {IsActive}, Page: {PageNumber}, Size: {PageSize}", projectId, isActive, pageNumber, pageSize);
|
||||
|
||||
try
|
||||
{
|
||||
// Check if the service project exists
|
||||
var serviceProject = await _context.ServiceProjects
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(sp => sp.Id == projectId && sp.TenantId == tenantId);
|
||||
if (serviceProject == null)
|
||||
{
|
||||
_logger.LogWarning("Service project not found for ProjectId: {ProjectId}", projectId);
|
||||
return ApiResponse<object>.ErrorResponse("Service project not found", "Service project not found", 404);
|
||||
}
|
||||
|
||||
// Build base query with necessary includes and filters
|
||||
var branchQuery = _context.ProjectBranches
|
||||
.Include(pb => pb.Project).ThenInclude(sp => sp!.Status)
|
||||
.Include(pb => pb.CreatedBy).ThenInclude(e => e!.JobRole)
|
||||
.AsNoTracking()
|
||||
.Where(pb => pb.ProjectId == projectId && pb.TenantId == tenantId && pb.IsActive == isActive);
|
||||
|
||||
// Apply search filtering if search string is provided
|
||||
if (!string.IsNullOrWhiteSpace(searchString))
|
||||
{
|
||||
var normalized = searchString.Trim().ToLowerInvariant();
|
||||
branchQuery = branchQuery.Where(pb =>
|
||||
pb.BranchName.ToLower().Contains(normalized) ||
|
||||
pb.Address.ToLower().Contains(normalized) ||
|
||||
pb.BranchType.ToLower().Contains(normalized));
|
||||
}
|
||||
|
||||
// Count total records for pagination metadata
|
||||
var totalEntities = await branchQuery.CountAsync();
|
||||
var totalPages = (int)Math.Ceiling((double)totalEntities / pageSize);
|
||||
|
||||
// Fetch paginated data sorted by name descending
|
||||
var branches = await branchQuery
|
||||
.OrderByDescending(pb => pb.BranchName)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
// Map entities to view models
|
||||
var projectBranchVMs = _mapper.Map<List<ProjectBranchVM>>(branches);
|
||||
|
||||
// Prepare response with pagination metadata
|
||||
var response = new
|
||||
{
|
||||
CurrentPage = pageNumber,
|
||||
TotalPages = totalPages,
|
||||
TotalEntities = totalEntities,
|
||||
Data = projectBranchVMs
|
||||
};
|
||||
|
||||
// Log successful fetch
|
||||
_logger.LogInfo("Fetched {Count} branches for Project: {ProjectName}", projectBranchVMs.Count, serviceProject.Name);
|
||||
return ApiResponse<object>.SuccessResponse(response, $"{projectBranchVMs.Count} branches of project {serviceProject.Name} fetched successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log exception details
|
||||
_logger.LogError(ex, "Error occurred while fetching project branches for ProjectId: {ProjectId}", projectId);
|
||||
// Return standardized problem details response
|
||||
return ApiResponse<object>.ErrorResponse("An unexpected error occurred.", ex.Message, 500);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves detailed information for a single project branch by ID, including related project and employee metadata.
|
||||
/// Provides enterprise-grade optimization, structured error handling, and detailed logging.
|
||||
/// </summary>
|
||||
/// <param name="id">Unique identifier of the project branch.</param>
|
||||
/// <param name="loggedInEmployee">Information about the currently logged-in employee (for auditing/security).</param>
|
||||
/// <param name="tenantId">The current tenant's unique identifier (multi-tenancy support).</param>
|
||||
/// <returns>ApiResponse with the branch details or a standardized error.</returns>
|
||||
public async Task<ApiResponse<object>> GetProjectBranchDetailsAsync(Guid id, Employee loggedInEmployee, Guid tenantId)
|
||||
{
|
||||
_logger.LogInfo("Attempting to fetch details for ProjectBranchId: {ProjectBranchId}, TenantId: {TenantId} by EmployeeId: {EmployeeId}",
|
||||
id, tenantId, loggedInEmployee.Id);
|
||||
|
||||
try
|
||||
{
|
||||
// Query the branch with required related entities; .AsNoTracking improves read speed/performance for lookups.
|
||||
var projectBranch = await _context.ProjectBranches
|
||||
.AsNoTracking()
|
||||
.Include(pb => pb.Project).ThenInclude(sp => sp!.Status)
|
||||
.Include(pb => pb.CreatedBy).ThenInclude(e => e!.JobRole)
|
||||
.Include(pb => pb.UpdatedBy).ThenInclude(e => e!.JobRole)
|
||||
.FirstOrDefaultAsync(pb => pb.Id == id && pb.TenantId == tenantId);
|
||||
|
||||
// Not found: log and return a descriptive error, using the correct HTTP status code.
|
||||
if (projectBranch == null)
|
||||
{
|
||||
_logger.LogWarning("Project branch not found. ProjectBranchId: {ProjectBranchId}, TenantId: {TenantId}", id, tenantId);
|
||||
return ApiResponse<object>.ErrorResponse(
|
||||
"Project branch not found",
|
||||
"No project branch exists with the given ID for this tenant.",
|
||||
404
|
||||
);
|
||||
}
|
||||
|
||||
// Map entity to detail view model to avoid exposing domain internals in API.
|
||||
var branchDetails = _mapper.Map<ProjectBranchDetailsVM>(projectBranch);
|
||||
|
||||
_logger.LogInfo("Project branch details successfully fetched. ProjectBranchId: {ProjectBranchId}", id);
|
||||
|
||||
// Return success with data using a descriptive message.
|
||||
return ApiResponse<object>.SuccessResponse(branchDetails, "Project branch details fetched successfully.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log the complete exception with an error log, capturing all contextual info for troubleshooting.
|
||||
_logger.LogError(ex, "Error while fetching project branch details. ProjectBranchId: {ProjectBranchId}, TenantId: {TenantId}, EmployeeId: {EmployeeId}",
|
||||
id, tenantId, loggedInEmployee.Id);
|
||||
|
||||
// Return a standardized error message; hide internal error details when handing unknown errors.
|
||||
return ApiResponse<object>.ErrorResponse(
|
||||
"An unexpected error occurred while fetching project branch details.",
|
||||
ex.Message,
|
||||
500
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new project branch associated with a specific service project.
|
||||
/// Applies enterprise-grade validation, logging, and exception handling.
|
||||
/// </summary>
|
||||
/// <param name="model">DTO containing project branch creation data.</param>
|
||||
/// <param name="loggedInEmployee">Logged-in employee details for auditing.</param>
|
||||
/// <param name="tenantId">Tenant identifier for multi-tenancy context.</param>
|
||||
/// <returns>ApiResponse containing created project branch details or error info.</returns>
|
||||
public async Task<ApiResponse<object>> CreateProjectBranchAsync(ProjectBranchDto model, Employee loggedInEmployee, Guid tenantId)
|
||||
{
|
||||
_logger.LogInfo("Starting project branch creation. ProjectId: {ProjectId}, TenantId: {TenantId}, CreatedBy: {EmployeeId}",
|
||||
model.ProjectId, tenantId, loggedInEmployee.Id);
|
||||
|
||||
try
|
||||
{
|
||||
// Validate existence of related service project for given tenant
|
||||
var serviceProject = await _context.ServiceProjects
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(sp => sp.Id == model.ProjectId && sp.TenantId == tenantId);
|
||||
|
||||
if (serviceProject == null)
|
||||
{
|
||||
_logger.LogWarning("Service project not found for ProjectId: {ProjectId}, TenantId: {TenantId}", model.ProjectId, tenantId);
|
||||
return ApiResponse<object>.ErrorResponse("Service project not found", "No service project exists with the given ID for this tenant.", 404);
|
||||
}
|
||||
|
||||
// Map DTO to domain entity and initialize audit and status fields
|
||||
var projectBranch = _mapper.Map<ProjectBranch>(model);
|
||||
projectBranch.Id = Guid.NewGuid();
|
||||
projectBranch.IsActive = true;
|
||||
projectBranch.CreatedAt = DateTime.UtcNow;
|
||||
projectBranch.CreatedById = loggedInEmployee.Id;
|
||||
projectBranch.TenantId = tenantId;
|
||||
|
||||
// Add and persist new project branch
|
||||
await using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
_context.ProjectBranches.Add(projectBranch);
|
||||
await _context.SaveChangesAsync();
|
||||
await transaction.CommitAsync();
|
||||
|
||||
// Map to response view models assembling nested related data
|
||||
var response = _mapper.Map<ProjectBranchVM>(projectBranch);
|
||||
response.Project = _mapper.Map<BasicServiceProjectVM>(serviceProject);
|
||||
response.CreatedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);
|
||||
|
||||
_logger.LogInfo("Project branch created successfully. ProjectBranchId: {ProjectBranchId}", projectBranch.Id);
|
||||
return ApiResponse<object>.SuccessResponse(response, "Created project branch successfully", 201);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occurred while creating project branch. ProjectId: {ProjectId}, TenantId: {TenantId}, EmployeeId: {EmployeeId}",
|
||||
model.ProjectId, tenantId, loggedInEmployee.Id);
|
||||
|
||||
return ApiResponse<object>.ErrorResponse("An unexpected error occurred while creating the project branch.", ex.Message, 500);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing project branch with new data. Ensures data consistency, logs changes, and maintains comprehensive audit trail.
|
||||
/// Implements enterprise best practices for validation, logging, transaction management, and error handling.
|
||||
/// </summary>
|
||||
/// <param name="id">ID of the project branch to update.</param>
|
||||
/// <param name="model">DTO containing updated project branch data.</param>
|
||||
/// <param name="loggedInEmployee">Current employee performing the update (for audit and logging).</param>
|
||||
/// <param name="tenantId">Tenant ID for multi-tenant data isolation.</param>
|
||||
/// <returns>ApiResponse indicating success or failure with detailed messages.</returns>
|
||||
public async Task<ApiResponse<object>> UpdateProjectBranchAsync(Guid id, ProjectBranchDto model, Employee loggedInEmployee, Guid tenantId)
|
||||
{
|
||||
// Validate ID consistency between route parameter and payload DTO
|
||||
if (model.Id.HasValue && model.Id != id)
|
||||
{
|
||||
_logger.LogWarning("ID mismatch: Route ID {RouteId} != Payload ID {PayloadId}", id, model.Id);
|
||||
return ApiResponse<object>.ErrorResponse("ID mismatch between route and payload", "The ID provided in the route does not match the payload.", 400);
|
||||
}
|
||||
|
||||
// Fetch current entity state for auditing
|
||||
var projectBranch = await _context.ProjectBranches
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(pb => pb.Id == id && pb.TenantId == tenantId);
|
||||
|
||||
if (projectBranch == null)
|
||||
{
|
||||
_logger.LogWarning("Project branch not found for update. Id: {Id}, TenantId: {TenantId}", id, tenantId);
|
||||
return ApiResponse<object>.ErrorResponse("Project branch not found", "No project branch exists with the provided ID for this tenant.", 404);
|
||||
}
|
||||
|
||||
// Convert existing entity to BSON for detailed audit logging
|
||||
BsonDocument existingEntityBson = _updateLogHelper.EntityToBsonDocument(projectBranch);
|
||||
|
||||
// Map the incoming DTO onto the existing entity
|
||||
_mapper.Map(model, projectBranch);
|
||||
projectBranch.UpdatedAt = DateTime.UtcNow;
|
||||
projectBranch.UpdatedById = loggedInEmployee.Id;
|
||||
|
||||
try
|
||||
{
|
||||
// Execute update within a transaction to ensure atomicity
|
||||
await using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
|
||||
// Mark the entity as modified
|
||||
_context.ProjectBranches.Update(projectBranch);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// Commit transaction
|
||||
await transaction.CommitAsync();
|
||||
|
||||
// Log the update in a dedicated audit log asynchronously
|
||||
var updateLogTask = _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject
|
||||
{
|
||||
EntityId = id.ToString(),
|
||||
UpdatedById = loggedInEmployee.Id.ToString(),
|
||||
OldObject = existingEntityBson,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
}, "ProjectBranchModificationLog");
|
||||
|
||||
// Fetch the latest entity details with related info for response
|
||||
var branchTask = Task.Run(async () =>
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.ProjectBranches
|
||||
.Include(pb => pb.Project).ThenInclude(sp => sp!.Status)
|
||||
.Include(pb => pb.CreatedBy).ThenInclude(e => e!.JobRole)
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(pb => pb.Id == id && pb.TenantId == tenantId);
|
||||
});
|
||||
|
||||
await Task.WhenAll(updateLogTask, branchTask);
|
||||
|
||||
// Map updated entity to view model for API response
|
||||
var response = _mapper.Map<ProjectBranchVM>(branchTask.Result);
|
||||
|
||||
_logger.LogInfo("Successfully updated project branch. Id: {Id}", id);
|
||||
return ApiResponse<object>.SuccessResponse(response, "Project branch updated successfully.", 200);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log detailed error for troubleshooting
|
||||
_logger.LogError(ex, "Error during project branch update. Id: {Id}, TenantId: {TenantId}", id, tenantId);
|
||||
return ApiResponse<object>.ErrorResponse("Failed to update project branch due to an internal error.", ex.Message, 500);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Soft deletes or restores a project branch by toggling its IsActive flag.
|
||||
/// Implements audit logging, transaction safety, and detailed error handling to ensure enterprise readiness.
|
||||
/// </summary>
|
||||
/// <param name="id">The unique identifier of the project branch to be deleted or restored.</param>
|
||||
/// <param name="isActive">Boolean indicating active state; false to soft delete, true to restore.</param>
|
||||
/// <param name="loggedInEmployee">The authenticated employee performing the operation, for auditing purposes.</param>
|
||||
/// <param name="tenantId">Tenant ID to enforce multi-tenant data isolation.</param>
|
||||
/// <returns>ApiResponse indicating the result of the operation, with status and descriptive message.</returns>
|
||||
public async Task<ApiResponse<object>> DeleteProjectBranchAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId)
|
||||
{
|
||||
_logger.LogInfo("Starting soft delete operation for ProjectBranchId: {ProjectBranchId}, TenantId: {TenantId}, By EmployeeId: {EmployeeId}",
|
||||
id, tenantId, loggedInEmployee.Id);
|
||||
|
||||
try
|
||||
{
|
||||
// Fetch the existing project branch record for the tenant
|
||||
var projectBranch = await _context.ProjectBranches
|
||||
.FirstOrDefaultAsync(pb => pb.Id == id && pb.TenantId == tenantId);
|
||||
|
||||
if (projectBranch == null)
|
||||
{
|
||||
_logger.LogWarning("Project branch not found for soft delete. ProjectBranchId: {ProjectBranchId}, TenantId: {TenantId}", id, tenantId);
|
||||
return ApiResponse<object>.ErrorResponse("Project branch not found", "No project branch exists with the given ID for this tenant.", 404);
|
||||
}
|
||||
|
||||
// Capture existing entity state for audit logging
|
||||
BsonDocument existingEntityBson = _updateLogHelper.EntityToBsonDocument(projectBranch);
|
||||
|
||||
// Update the IsActive flag to soft delete or restore
|
||||
projectBranch.IsActive = isActive;
|
||||
|
||||
// Save changes within a transaction to ensure atomicity
|
||||
await using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
await _context.SaveChangesAsync();
|
||||
await transaction.CommitAsync();
|
||||
|
||||
// Log the change asynchronously for audit trail
|
||||
await _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject
|
||||
{
|
||||
EntityId = id.ToString(),
|
||||
UpdatedById = loggedInEmployee.Id.ToString(),
|
||||
OldObject = existingEntityBson,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
}, "ProjectBranchModificationLog");
|
||||
|
||||
_logger.LogInfo("Soft delete operation completed successfully for ProjectBranchId: {ProjectBranchId}", id);
|
||||
|
||||
return ApiResponse<object>.SuccessResponse(new { }, isActive ? "Branch restored successfully" : "Branch deleted successfully", 200);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occurred during soft delete operation for ProjectBranchId: {ProjectBranchId}, TenantId: {TenantId}", id, tenantId);
|
||||
return ApiResponse<object>.ErrorResponse("Failed to delete project branch due to an internal error.", ex.Message, 500);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Service Project Allocation Functions ===================================================================
|
||||
|
||||
/// <summary>
|
||||
@ -1699,9 +2039,7 @@ namespace Marco.Pms.Services.Service
|
||||
}
|
||||
|
||||
// Create BSON snapshot of existing entity for audit logging (MongoDB)
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var updateLogHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();
|
||||
BsonDocument existingEntityBson = updateLogHelper.EntityToBsonDocument(jobTicket);
|
||||
BsonDocument existingEntityBson = _updateLogHelper.EntityToBsonDocument(jobTicket);
|
||||
|
||||
// Map updated properties from DTO, set audit metadata
|
||||
_mapper.Map(model, jobTicket);
|
||||
@ -1825,7 +2163,7 @@ namespace Marco.Pms.Services.Service
|
||||
await transaction.CommitAsync();
|
||||
|
||||
// Push update log asynchronously to MongoDB for audit
|
||||
var updateLogTask = updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject
|
||||
var updateLogTask = _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject
|
||||
{
|
||||
EntityId = id.ToString(),
|
||||
UpdatedById = loggedInEmployee.Id.ToString(),
|
||||
@ -2172,9 +2510,7 @@ namespace Marco.Pms.Services.Service
|
||||
}
|
||||
|
||||
// Audit: BSON snapshot before update (MongoDB)
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var updateLogHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();
|
||||
BsonDocument existingEntityBson = updateLogHelper.EntityToBsonDocument(jobComment);
|
||||
BsonDocument existingEntityBson = _updateLogHelper.EntityToBsonDocument(jobComment);
|
||||
|
||||
// Update comment core fields and audit
|
||||
_mapper.Map(model, jobComment);
|
||||
@ -2274,7 +2610,7 @@ namespace Marco.Pms.Services.Service
|
||||
}
|
||||
|
||||
// Push audit log to MongoDB
|
||||
var updateLogTask = updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject
|
||||
var updateLogTask = _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject
|
||||
{
|
||||
EntityId = id.ToString(),
|
||||
UpdatedById = loggedInEmployee.Id.ToString(),
|
||||
@ -2733,8 +3069,6 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
//private async Task DeleteTalkingPointAttachments(List<Guid> documentIds)
|
||||
//{
|
||||
// using var scope = _serviceScopeFactory.CreateScope();
|
||||
// var _updateLogHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();
|
||||
|
||||
// var attachmentTask = Task.Run(async () =>
|
||||
// {
|
||||
@ -2778,8 +3112,6 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
private async Task DeleteJobAttachemnts(List<Guid> documentIds)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var _updateLogHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();
|
||||
|
||||
var attachmentTask = Task.Run(async () =>
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user