Added the project branch CRUD oprations

This commit is contained in:
ashutosh.nehete 2025-11-19 15:36:45 +05:30
parent 5a402925b1
commit dad135571d
3 changed files with 423 additions and 23 deletions

View File

@ -96,6 +96,66 @@ namespace Marco.Pms.Services.Controllers
} }
#endregion #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 =================================================================== #region =================================================================== Service Project Allocation Functions ===================================================================
[HttpGet("get/allocation/list")] [HttpGet("get/allocation/list")]

View File

@ -15,6 +15,14 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
Task<ApiResponse<object>> DeActivateServiceProjectAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId); Task<ApiResponse<object>> DeActivateServiceProjectAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId);
#endregion #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 =================================================================== #region =================================================================== Service Project Allocation Functions ===================================================================
Task<ApiResponse<object>> GetServiceProjectAllocationListAsync(Guid? projectId, Guid? employeeId, bool isActive, Employee loggedInEmployee, Guid tenantId); 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); Task<ApiResponse<object>> ManageServiceProjectAllocationAsync(List<ServiceProjectAllocationDto> model, Employee loggedInEmployee, Guid tenantId);

View File

@ -30,6 +30,7 @@ namespace Marco.Pms.Services.Service
private readonly ILoggingService _logger; private readonly ILoggingService _logger;
private readonly S3UploadService _s3Service; private readonly S3UploadService _s3Service;
private readonly IMapper _mapper; private readonly IMapper _mapper;
private readonly UtilityMongoDBHelper _updateLogHelper;
private readonly Guid NewStatus = Guid.Parse("32d76a02-8f44-4aa0-9b66-c3716c45a918"); private readonly Guid NewStatus = Guid.Parse("32d76a02-8f44-4aa0-9b66-c3716c45a918");
private readonly Guid AssignedStatus = Guid.Parse("cfa1886d-055f-4ded-84c6-42a2a8a14a66"); private readonly Guid AssignedStatus = Guid.Parse("cfa1886d-055f-4ded-84c6-42a2a8a14a66");
@ -44,7 +45,8 @@ namespace Marco.Pms.Services.Service
ApplicationDbContext context, ApplicationDbContext context,
ILoggingService logger, ILoggingService logger,
S3UploadService s3Service, S3UploadService s3Service,
IMapper mapper) IMapper mapper,
UtilityMongoDBHelper updateLogHelper)
{ {
_serviceScopeFactory = serviceScopeFactory; _serviceScopeFactory = serviceScopeFactory;
_context = context; _context = context;
@ -52,11 +54,11 @@ namespace Marco.Pms.Services.Service
_s3Service = s3Service; _s3Service = s3Service;
_mapper = mapper; _mapper = mapper;
_dbContextFactory = dbContextFactory; _dbContextFactory = dbContextFactory;
_updateLogHelper = updateLogHelper;
} }
#region =================================================================== Service Project Functions =================================================================== #region =================================================================== Service Project Functions ===================================================================
/// <summary> /// <summary>
/// Retrieves a paginated list of active service projects for a tenant, including related services, job counts, and team member information. /// Retrieves a paginated list of active service projects for a tenant, including related services, job counts, and team member information.
/// </summary> /// </summary>
@ -195,7 +197,6 @@ namespace Marco.Pms.Services.Service
} }
} }
/// <summary> /// <summary>
/// Retrieves detailed information for a specific service project, including related client, status, services, and audit information. /// Retrieves detailed information for a specific service project, including related client, status, services, and audit information.
/// </summary> /// </summary>
@ -439,9 +440,7 @@ namespace Marco.Pms.Services.Service
} }
// Create BSON snapshot of existing entity for audit logging (MongoDB) // Create BSON snapshot of existing entity for audit logging (MongoDB)
using var scope = _serviceScopeFactory.CreateScope(); BsonDocument existingEntityBson = _updateLogHelper.EntityToBsonDocument(serviceProject);
var updateLogHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();
BsonDocument existingEntityBson = updateLogHelper.EntityToBsonDocument(serviceProject);
// Map incoming DTO to the tracked entity // Map incoming DTO to the tracked entity
_mapper.Map(model, serviceProject); _mapper.Map(model, serviceProject);
@ -509,7 +508,7 @@ namespace Marco.Pms.Services.Service
}); });
// Push update log asynchronously to MongoDB for audit // Push update log asynchronously to MongoDB for audit
var updateLogTask = updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject var updateLogTask = _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject
{ {
EntityId = id.ToString(), EntityId = id.ToString(),
UpdatedById = loggedInEmployee.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) // Create BSON snapshot of existing entity for audit logging (MongoDB)
using var scope = _serviceScopeFactory.CreateScope(); BsonDocument existingEntityBson = _updateLogHelper.EntityToBsonDocument(serviceProject);
var updateLogHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();
BsonDocument existingEntityBson = updateLogHelper.EntityToBsonDocument(serviceProject);
// Update active status as requested by the client // Update active status as requested by the client
serviceProject.IsActive = isActive; serviceProject.IsActive = isActive;
@ -576,7 +573,7 @@ namespace Marco.Pms.Services.Service
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
// Push update log asynchronously to MongoDB for audit // Push update log asynchronously to MongoDB for audit
await updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject await _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject
{ {
EntityId = id.ToString(), EntityId = id.ToString(),
UpdatedById = loggedInEmployee.Id.ToString(), UpdatedById = loggedInEmployee.Id.ToString(),
@ -599,6 +596,349 @@ namespace Marco.Pms.Services.Service
#endregion #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 =================================================================== #region =================================================================== Service Project Allocation Functions ===================================================================
/// <summary> /// <summary>
@ -1699,9 +2039,7 @@ namespace Marco.Pms.Services.Service
} }
// Create BSON snapshot of existing entity for audit logging (MongoDB) // Create BSON snapshot of existing entity for audit logging (MongoDB)
using var scope = _serviceScopeFactory.CreateScope(); BsonDocument existingEntityBson = _updateLogHelper.EntityToBsonDocument(jobTicket);
var updateLogHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();
BsonDocument existingEntityBson = updateLogHelper.EntityToBsonDocument(jobTicket);
// Map updated properties from DTO, set audit metadata // Map updated properties from DTO, set audit metadata
_mapper.Map(model, jobTicket); _mapper.Map(model, jobTicket);
@ -1825,7 +2163,7 @@ namespace Marco.Pms.Services.Service
await transaction.CommitAsync(); await transaction.CommitAsync();
// Push update log asynchronously to MongoDB for audit // Push update log asynchronously to MongoDB for audit
var updateLogTask = updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject var updateLogTask = _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject
{ {
EntityId = id.ToString(), EntityId = id.ToString(),
UpdatedById = loggedInEmployee.Id.ToString(), UpdatedById = loggedInEmployee.Id.ToString(),
@ -2172,9 +2510,7 @@ namespace Marco.Pms.Services.Service
} }
// Audit: BSON snapshot before update (MongoDB) // Audit: BSON snapshot before update (MongoDB)
using var scope = _serviceScopeFactory.CreateScope(); BsonDocument existingEntityBson = _updateLogHelper.EntityToBsonDocument(jobComment);
var updateLogHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();
BsonDocument existingEntityBson = updateLogHelper.EntityToBsonDocument(jobComment);
// Update comment core fields and audit // Update comment core fields and audit
_mapper.Map(model, jobComment); _mapper.Map(model, jobComment);
@ -2274,7 +2610,7 @@ namespace Marco.Pms.Services.Service
} }
// Push audit log to MongoDB // Push audit log to MongoDB
var updateLogTask = updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject var updateLogTask = _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject
{ {
EntityId = id.ToString(), EntityId = id.ToString(),
UpdatedById = loggedInEmployee.Id.ToString(), UpdatedById = loggedInEmployee.Id.ToString(),
@ -2733,8 +3069,6 @@ namespace Marco.Pms.Services.Service
//private async Task DeleteTalkingPointAttachments(List<Guid> documentIds) //private async Task DeleteTalkingPointAttachments(List<Guid> documentIds)
//{ //{
// using var scope = _serviceScopeFactory.CreateScope();
// var _updateLogHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();
// var attachmentTask = Task.Run(async () => // var attachmentTask = Task.Run(async () =>
// { // {
@ -2778,8 +3112,6 @@ namespace Marco.Pms.Services.Service
private async Task DeleteJobAttachemnts(List<Guid> documentIds) private async Task DeleteJobAttachemnts(List<Guid> documentIds)
{ {
using var scope = _serviceScopeFactory.CreateScope();
var _updateLogHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();
var attachmentTask = Task.Run(async () => var attachmentTask = Task.Run(async () =>
{ {