diff --git a/Marco.Pms.Model/Dtos/Collection/PaymentAdjustmentHeadDto.cs b/Marco.Pms.Model/Dtos/Collection/PaymentAdjustmentHeadDto.cs new file mode 100644 index 0000000..a4d7dc5 --- /dev/null +++ b/Marco.Pms.Model/Dtos/Collection/PaymentAdjustmentHeadDto.cs @@ -0,0 +1,9 @@ +namespace Marco.Pms.Model.Dtos.Collection +{ + public class PaymentAdjustmentHeadDto + { + public Guid? Id { get; set; } + public required string Name { get; set; } + public string? Description { get; set; } + } +} diff --git a/Marco.Pms.Services/Controllers/MasterController.cs b/Marco.Pms.Services/Controllers/MasterController.cs index 08c8ed7..e0b969d 100644 --- a/Marco.Pms.Services/Controllers/MasterController.cs +++ b/Marco.Pms.Services/Controllers/MasterController.cs @@ -1,5 +1,6 @@ using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.Dtos.Activities; +using Marco.Pms.Model.Dtos.Collection; using Marco.Pms.Model.Dtos.DocumentManager; using Marco.Pms.Model.Dtos.Master; using Marco.Pms.Model.Forum; @@ -984,6 +985,29 @@ namespace Marco.Pms.Services.Controllers var response = await _masterService.GetPaymentAdjustmentHeadListAsync(isActive, loggedInEmployee, tenantId); return StatusCode(response.StatusCode, response); } + [HttpPost("payment-adjustment-head")] + public async Task CreatePaymentAdjustmentHead([FromBody] PaymentAdjustmentHeadDto dto) + { + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _masterService.CreatePaymentAdjustmentHeadAsync(dto, loggedInEmployee, tenantId); + return StatusCode(response.StatusCode, response); + } + + [HttpPut("payment-adjustment-head/edit/{id}")] + public async Task UpdatePaymentAdjustmentHead(Guid id, [FromBody] PaymentAdjustmentHeadDto dto) + { + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _masterService.UpdatePaymentAdjustmentHeadAsync(id, dto, loggedInEmployee, tenantId); + return StatusCode(response.StatusCode, response); + } + + [HttpDelete("payment-adjustment-head/delete/{id}")] + public async Task DeletePaymentAdjustmentHead(Guid id, [FromQuery] bool isActive = false) + { + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _masterService.DeletePaymentAdjustmentHeadAsync(id, isActive, loggedInEmployee, tenantId); + return StatusCode(response.StatusCode, response); + } #endregion } } diff --git a/Marco.Pms.Services/MappingProfiles/MappingProfile.cs b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs index 439748a..6763e1f 100644 --- a/Marco.Pms.Services/MappingProfiles/MappingProfile.cs +++ b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs @@ -269,8 +269,6 @@ namespace Marco.Pms.Services.MappingProfiles CreateMap(); CreateMap(); - - CreateMap(); #endregion #region ======================================================= Master ======================================================= @@ -402,19 +400,27 @@ namespace Marco.Pms.Services.MappingProfiles CreateMap(); CreateMap(); #endregion + #region ======================================================= Contact Tag Master ======================================================= CreateMap(); CreateMap(); CreateMap(); #endregion + + #region ======================================================= Payment Adjustment Head Master ======================================================= + CreateMap(); + CreateMap(); + #endregion + #region ======================================================= Expenses Status Master ======================================================= #endregion + #region ======================================================= Expenses Status Master ======================================================= #endregion + #region ======================================================= Expenses Status Master ======================================================= #endregion - #region ======================================================= Expenses Status Master ======================================================= - #endregion + #region ======================================================= Expenses Status Master ======================================================= #endregion diff --git a/Marco.Pms.Services/Service/MasterService.cs b/Marco.Pms.Services/Service/MasterService.cs index 2470b18..f60ee38 100644 --- a/Marco.Pms.Services/Service/MasterService.cs +++ b/Marco.Pms.Services/Service/MasterService.cs @@ -1,9 +1,11 @@ using AutoMapper; using Marco.Pms.DataAccess.Data; using Marco.Pms.Helpers.Utility; +using Marco.Pms.Model.Collection; using Marco.Pms.Model.Directory; using Marco.Pms.Model.DocumentManager; using Marco.Pms.Model.Dtos.Activities; +using Marco.Pms.Model.Dtos.Collection; using Marco.Pms.Model.Dtos.DocumentManager; using Marco.Pms.Model.Dtos.Master; using Marco.Pms.Model.Employees; @@ -2881,6 +2883,7 @@ namespace Marco.Pms.Services.Service #endregion #region =================================================================== Payment Adjustment Head APIs =================================================================== + /// /// Retrieves a list of payment adjustment heads for a specific tenant with optional active status filtering. /// @@ -2920,6 +2923,205 @@ namespace Marco.Pms.Services.Service } } + /// + /// Creates a new payment adjustment head for the specified tenant after permission and uniqueness checks. + /// + /// DTO containing payment adjustment head data + /// The employee performing the action + /// The tenant identifier + /// API response with status and context + public async Task> CreatePaymentAdjustmentHeadAsync(PaymentAdjustmentHeadDto model, Employee loggedInEmployee, Guid tenantId) + { + try + { + // Permission validation + var hasManagePermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); + if (!hasManagePermission) + { + _logger.LogWarning("Access denied for employee {EmployeeId} attempting to create payment adjustment head for tenant {TenantId}.", loggedInEmployee.Id, tenantId); + return ApiResponse.ErrorResponse("Access Denied.", "You do not have permission to manage masters.", 403); + } + + // Uniqueness check for payment adjustment head name + var nameExists = await _context.PaymentAdjustmentHeads + .AnyAsync(pah => pah.Name == model.Name && pah.TenantId == tenantId); + if (nameExists) + { + _logger.LogInfo("Duplicate payment adjustment head name '{Name}' detected for tenant {TenantId}.", model.Name, tenantId); + return ApiResponse.ErrorResponse("A payment adjustment head with this name already exists.", "Name of payment adjustment head already exists.", 409); + } + + // Create and persist new entity + var paymentAdjustmentHead = _mapper.Map(model); + paymentAdjustmentHead.IsActive = true; + paymentAdjustmentHead.TenantId = tenantId; + + _context.PaymentAdjustmentHeads.Add(paymentAdjustmentHead); + await _context.SaveChangesAsync(); + + var response = _mapper.Map(paymentAdjustmentHead); + + _logger.LogInfo("Payment adjustment head '{Name}' created successfully by employee {EmployeeId} for tenant {TenantId}.", paymentAdjustmentHead.Name, loggedInEmployee.Id, tenantId); + + return ApiResponse.SuccessResponse(response, "Payment adjustment head created successfully.", 201); + } + catch (Exception ex) + { + // Log full context with exception for easier debugging + _logger.LogError(ex, "Exception while creating payment adjustment head. Employee: {EmployeeId}, Tenant: {TenantId}, Data: {@Model}", loggedInEmployee.Id, tenantId, model); + return ApiResponse.ErrorResponse("An error occurred.", "Unable to create payment adjustment head at this moment.", 500); + } + } + + /// + /// Updates an existing payment adjustment head for a specified tenant after performing + /// necessary validation, permission, and uniqueness checks. + /// + /// Unique identifier of the payment adjustment head to update + /// DTO containing updated payment adjustment head data + /// The employee performing the action + /// The tenant identifier + /// API response object with update result and status message + public async Task> UpdatePaymentAdjustmentHeadAsync(Guid id, PaymentAdjustmentHeadDto model, Employee loggedInEmployee, Guid tenantId) + { + try + { + // --- Step 1: Validate request payload correctness --- + if (!model.Id.HasValue || model.Id != id) + { + _logger.LogWarning("Invalid ID provided in request. Model ID: {ModelId}, Route ID: {RouteId}, TenantId: {TenantId}", model.Id ?? Guid.Empty, id, tenantId); + return ApiResponse.ErrorResponse("Invalid request.", "Provided invalid ID.", 400); + } + + // --- Step 2: Validate permissions --- + var hasManagePermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); + if (!hasManagePermission) + { + _logger.LogWarning("Access denied for employee {EmployeeId} attempting to update payment adjustment head for tenant {TenantId}.", loggedInEmployee.Id, tenantId); + return ApiResponse.ErrorResponse("Access Denied.", "You do not have permission to manage masters.", 403); + } + + // --- Step 3: Validate uniqueness constraint for name --- + var nameExists = await _context.PaymentAdjustmentHeads + .AnyAsync(pah => pah.Name == model.Name && pah.Id != id && pah.TenantId == tenantId); + + if (nameExists) + { + _logger.LogInfo("Duplicate payment adjustment head name '{Name}' detected during update for tenant {TenantId}.", model.Name, tenantId); + return ApiResponse.ErrorResponse("Conflict detected.", "A payment adjustment head with this name already exists.", 409); + } + + // --- Step 4: Retrieve and validate existing entity --- + var paymentAdjustmentHead = await _context.PaymentAdjustmentHeads + .FirstOrDefaultAsync(pah => pah.Id == id && pah.TenantId == tenantId); + + if (paymentAdjustmentHead == null) + { + _logger.LogWarning("Payment adjustment head with ID {Id} not found for tenant {TenantId}.", id, tenantId); + return ApiResponse.ErrorResponse("Not Found.", "Payment adjustment head not found.", 404); + } + + // Mapping PaymentAdjustmentHead to BsonDocument + var existingEntityBson = _updateLogHelper.EntityToBsonDocument(paymentAdjustmentHead); + + // --- Step 5: Map changes and update entity --- + _mapper.Map(model, paymentAdjustmentHead); + + await _context.SaveChangesAsync(); + + await _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject + { + EntityId = paymentAdjustmentHead.Id.ToString(), + UpdatedById = loggedInEmployee.Id.ToString(), + OldObject = existingEntityBson, + UpdatedAt = DateTime.UtcNow + }, "PaymentAdjustmentHeadModificationLog"); + + _logger.LogInfo("Payment adjustment head '{Name}' updated successfully by employee {EmployeeId} for tenant {TenantId}.", paymentAdjustmentHead.Name, loggedInEmployee.Id, tenantId); + + var response = _mapper.Map(paymentAdjustmentHead); + + // --- Step 6: Return structured success response --- + return ApiResponse.SuccessResponse(response, "Payment adjustment head updated successfully.", 200); + } + catch (Exception ex) + { + // --- Step 7: Handle and log exceptions with full context --- + _logger.LogError(ex, "Exception while updating payment adjustment head. Employee: {EmployeeId}, Tenant: {TenantId}, Model: {@Model}", loggedInEmployee.Id, tenantId, model); + + return ApiResponse.ErrorResponse("An unexpected error occurred.", "Unable to update payment adjustment head at this moment.", 500); + } + } + + /// + /// Activates or deactivates a payment adjustment head (soft delete/restore) for a tenant, + /// including audit logging and permission validation. + /// + /// Unique identifier of the payment adjustment head + /// Flag indicating activation (restore) or deactivation (delete) + /// Employee requesting the operation + /// The tenant identifier + /// API response object with operation status + public async Task> DeletePaymentAdjustmentHeadAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId) + { + // Dynamically select operation word for logs and messages + var operation = isActive ? "restore" : "delete"; + + try + { + // Step 1: Permission check + var hasManagePermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); + if (!hasManagePermission) + { + _logger.LogWarning("Access denied: Employee {EmployeeId} attempted to {Operation} payment adjustment head for tenant {TenantId}.", + loggedInEmployee.Id, operation, tenantId); + return ApiResponse.ErrorResponse("Access Denied.", "You do not have permission to manage masters.", 403); + } + + // Step 2: Entity existence check + var paymentAdjustmentHead = await _context.PaymentAdjustmentHeads + .FirstOrDefaultAsync(pah => pah.Id == id && pah.TenantId == tenantId); + + if (paymentAdjustmentHead == null) + { + _logger.LogWarning("Payment adjustment head with ID {Id} not found for tenant {TenantId} (attempted {Operation}).", + id, tenantId, operation); + return ApiResponse.ErrorResponse("Not Found.", "Payment adjustment head not found.", 404); + } + + // Step 3: Save pre-update state for audit log + var existingEntityBson = _updateLogHelper.EntityToBsonDocument(paymentAdjustmentHead); + + // Step 4: Update IsActive status + paymentAdjustmentHead.IsActive = isActive; + + await _context.SaveChangesAsync(); + + // Step 5: Push update action to audit log + await _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject + { + EntityId = paymentAdjustmentHead.Id.ToString(), + UpdatedById = loggedInEmployee.Id.ToString(), + OldObject = existingEntityBson, + UpdatedAt = DateTime.UtcNow + }, "PaymentAdjustmentHeadModificationLog"); + + _logger.LogInfo( + "Payment adjustment head (ID: {Id}, Name: {Name}) successfully {Operation}d by employee {EmployeeId} for tenant {TenantId}.", + paymentAdjustmentHead.Id, paymentAdjustmentHead.Name, operation, loggedInEmployee.Id, tenantId); + + return ApiResponse.SuccessResponse(new { }, $"Payment adjustment head {operation}d successfully.", 200); + } + catch (Exception ex) + { + _logger.LogError(ex, + "Exception occurred while performing {Operation} on payment adjustment head (ID: {Id}) for tenant {TenantId}.", + operation, id, tenantId); + + return ApiResponse.ErrorResponse("An error occurred.", $"Exception occurred while trying to {operation} payment adjustment head.", 500); + } + } + #endregion #region =================================================================== Helper Function =================================================================== diff --git a/Marco.Pms.Services/Service/ServiceInterfaces/IMasterService.cs b/Marco.Pms.Services/Service/ServiceInterfaces/IMasterService.cs index cadce07..4515b99 100644 --- a/Marco.Pms.Services/Service/ServiceInterfaces/IMasterService.cs +++ b/Marco.Pms.Services/Service/ServiceInterfaces/IMasterService.cs @@ -1,4 +1,5 @@ using Marco.Pms.Model.Dtos.Activities; +using Marco.Pms.Model.Dtos.Collection; using Marco.Pms.Model.Dtos.DocumentManager; using Marco.Pms.Model.Dtos.Master; using Marco.Pms.Model.Employees; @@ -108,6 +109,9 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces #region =================================================================== Payment Adjustment Head APIs =================================================================== Task> GetPaymentAdjustmentHeadListAsync(bool isActive, Employee loggedInEmployee, Guid tenantId); + Task> CreatePaymentAdjustmentHeadAsync(PaymentAdjustmentHeadDto model, Employee loggedInEmployee, Guid tenantId); + Task> UpdatePaymentAdjustmentHeadAsync(Guid id, PaymentAdjustmentHeadDto model, Employee loggedInEmployee, Guid tenantId); + Task> DeletePaymentAdjustmentHeadAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId); #endregion } }