diff --git a/Marco.Pms.Model/Dtos/Inventory/PurchaseStatusMappingDto.cs b/Marco.Pms.Model/Dtos/Inventory/PurchaseStatusMappingDto.cs new file mode 100644 index 0000000..3fe1168 --- /dev/null +++ b/Marco.Pms.Model/Dtos/Inventory/PurchaseStatusMappingDto.cs @@ -0,0 +1,9 @@ +namespace Marco.Pms.Model.Dtos.Inventory +{ + public class PurchaseStatusMappingDto + { + public Guid PurchaseStatusId { get; set; } + public Guid NextPurchaseStatusId { get; set; } + public bool IsActive { get; set; } + } +} diff --git a/Marco.Pms.Model/ViewModels/Inventory/PurchaseStatusMappingVM.cs b/Marco.Pms.Model/ViewModels/Inventory/PurchaseStatusMappingVM.cs new file mode 100644 index 0000000..d440cf5 --- /dev/null +++ b/Marco.Pms.Model/ViewModels/Inventory/PurchaseStatusMappingVM.cs @@ -0,0 +1,10 @@ +namespace Marco.Pms.Model.ViewModels.Inventory +{ + public class PurchaseStatusMappingVM + { + public Guid Id { get; set; } + public PurchaseOrderStatusVM? PreviousPurchaseStatus { get; set; } + public PurchaseOrderStatusVM? PurchaseStatus { get; set; } + public PurchaseOrderStatusVM? NextPurchaseStatus { get; set; } + } +} diff --git a/Marco.Pms.Services/Controllers/MasterController.cs b/Marco.Pms.Services/Controllers/MasterController.cs index 89d6b62..5e7479b 100644 --- a/Marco.Pms.Services/Controllers/MasterController.cs +++ b/Marco.Pms.Services/Controllers/MasterController.cs @@ -1116,6 +1116,14 @@ namespace Marco.Pms.Services.Controllers return StatusCode(response.StatusCode, response); } + [HttpPost("purchase-order-status/mapping")] + public async Task AddPurchaseOrderStatusLink([FromBody] PurchaseStatusMappingDto model) + { + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _masterService.AddPurchaseOrderStatusLinkAsync(model, loggedInEmployee, tenantId); + return StatusCode(response.StatusCode, response); + } + [HttpPut("purchase-order-status/edit/{id}")] public async Task UpdatePurchaseOrderStatus(Guid id, [FromBody] PurchaseOrderStatusDto model) { diff --git a/Marco.Pms.Services/Service/MasterService.cs b/Marco.Pms.Services/Service/MasterService.cs index 094f80b..0b4d5c4 100644 --- a/Marco.Pms.Services/Service/MasterService.cs +++ b/Marco.Pms.Services/Service/MasterService.cs @@ -3377,7 +3377,6 @@ namespace Marco.Pms.Services.Service } } - public async Task> AddRequisitionStatusLinkAsync(RequisitionStatusMappingDto model, Employee loggedInEmployee, Guid tenantId) { // Start: API entry log @@ -3488,8 +3487,6 @@ namespace Marco.Pms.Services.Service return ApiResponse.ErrorResponse("Internal Server Error", "An error occurred while processing your request.", 500); } } - - public async Task> UpdateRequisitionStatusAsync(Guid id, RequisitionStatusDto model, Employee loggedInEmployee, Guid tenantId) { try @@ -3639,7 +3636,7 @@ namespace Marco.Pms.Services.Service var purchaseOrderStatus = await _context.PurchaseOrderStatus.FirstOrDefaultAsync(pos => pos.Id == id && pos.TenantId == tenantId); if (purchaseOrderStatus == null) { - _logger.LogWarning("Purchase Order Status {PurchaseOrderStatusId} not found in database for tenant {TenantId}", id, tenantId); + _logger.LogWarning("Purchase Order Status {PurchaseStatusId} not found in database for tenant {TenantId}", id, tenantId); return ApiResponse.ErrorResponse("Purchase Order Status not found", "Purchase Order Status not found in database for current tenant", 404); } var purchaseOrderStatusMapping = await _context.PurchaseStatusMappings @@ -3716,6 +3713,116 @@ namespace Marco.Pms.Services.Service } } + public async Task> AddPurchaseOrderStatusLinkAsync(PurchaseStatusMappingDto model, Employee loggedInEmployee, Guid tenantId) + { + // Start: API entry log + _logger.LogInfo("AddPurchaseOrderStatusLinkAsync started by Employee: {EmployeeId} for Tenant: {TenantId}", loggedInEmployee.Id, tenantId); + + // Validate the Purchase Order Status existence + var purchaseOrderStatus = await _context.PurchaseOrderStatus + .FirstOrDefaultAsync(rs => rs.Id == model.PurchaseStatusId && rs.TenantId == tenantId && rs.IsActive); + if (purchaseOrderStatus == null) + { + _logger.LogWarning("Purchase Order Status not found for Id: {Id}, Tenant: {TenantId}", model.PurchaseStatusId, tenantId); + return ApiResponse.ErrorResponse( + "Purchase Order Status not found", "No active purchase Order status exists with the provided Id.", 404); + } + + // Validate the Next Purchase Order Status existence + var nextPurchaseOrderStatus = await _context.PurchaseOrderStatus + .FirstOrDefaultAsync(rs => rs.Id == model.NextPurchaseStatusId && rs.TenantId == tenantId && rs.IsActive); + if (nextPurchaseOrderStatus == null) + { + _logger.LogWarning("Next Purchase Order Status not found for Id: {Id}, Tenant: {TenantId}", model.NextPurchaseStatusId, tenantId); + return ApiResponse.ErrorResponse( + "Next Purchase Order Status not found", "No active next purchase Order status exists with the provided Id.", 404); + } + + try + { + if (model.IsActive) + { + // Check if mapping already exists + var alreadyExists = await _context.PurchaseStatusMappings + .AnyAsync(rsm => rsm.PurchaseStatusId == model.PurchaseStatusId + && rsm.NextPurchaseStatusId == model.NextPurchaseStatusId); + if (alreadyExists) + { + _logger.LogWarning("Duplicate mapping attempted between {PurchaseStatusId} and {NextPurchaseStatusId}", model.PurchaseStatusId, model.NextPurchaseStatusId); + return ApiResponse.ErrorResponse( + "Mapping already exists", "A mapping between these statuses already exists.", 409); + } + + // Prevent mapping if next status already has a parent + var hasParent = await _context.PurchaseStatusMappings + .AnyAsync(rsm => rsm.NextPurchaseStatusId == model.NextPurchaseStatusId); + if (hasParent) + { + _logger.LogWarning("Next Purchase Order Status {NextPurchaseStatusId} already has a parent, cannot re-map.", model.NextPurchaseStatusId); + return ApiResponse.ErrorResponse( + $"Purchase Order \"{nextPurchaseOrderStatus.Name}\" already has a parent and cannot be mapped again.", + $"Purchase Order \"{nextPurchaseOrderStatus.Name}\" already has a parent and cannot be mapped again.", 409); + } + + // Find any previous mapping for ordering/trace + var previousMapping = await _context.PurchaseStatusMappings + .Include(rsm => rsm.PurchaseStatus) + .FirstOrDefaultAsync(rsm => rsm.NextPurchaseStatusId == model.PurchaseStatusId); + + // Create a new mapping + var newMapping = new PurchaseStatusMapping + { + Id = Guid.NewGuid(), + PreviousPurchaseStatusId = previousMapping?.PurchaseStatusId, + PurchaseStatusId = purchaseOrderStatus.Id, + NextPurchaseStatusId = nextPurchaseOrderStatus.Id, + TenantId = tenantId + }; + _context.PurchaseStatusMappings.Add(newMapping); + await _context.SaveChangesAsync(); + + _logger.LogInfo("Created mapping: {Id} from {PurchaseStatusId} to {NextPurchaseStatusId} for Tenant {TenantId}", newMapping.Id, purchaseOrderStatus.Id, nextPurchaseOrderStatus.Id, tenantId); + + var vm = new PurchaseStatusMappingVM + { + Id = newMapping.Id, + PreviousPurchaseStatus = _mapper.Map(previousMapping?.PurchaseStatus), + PurchaseStatus = _mapper.Map(purchaseOrderStatus), + NextPurchaseStatus = _mapper.Map(nextPurchaseOrderStatus) + }; + + return ApiResponse.SuccessResponse(vm, "Purchase Order Status mapping added successfully.", 200); + } + else + { + // Remove mapping + var mapping = await _context.PurchaseStatusMappings + .Include(rsm => rsm.PreviousPurchaseStatus) + .Include(rsm => rsm.PurchaseStatus) + .Include(rsm => rsm.NextPurchaseStatus) + .FirstOrDefaultAsync(rsm => rsm.PurchaseStatusId == model.PurchaseStatusId + && rsm.NextPurchaseStatusId == model.NextPurchaseStatusId); + if (mapping == null) + { + _logger.LogWarning("No mapping found to remove for {PurchaseStatusId} to {NextPurchaseStatusId}", model.PurchaseStatusId, model.NextPurchaseStatusId); + return ApiResponse.ErrorResponse("Mapping not found", "No mapping exists to remove.", 404); + } + + _context.PurchaseStatusMappings.Remove(mapping); + await _context.SaveChangesAsync(); + + _logger.LogInfo("Mapping removed: {Id} from {PurchaseStatusId} to {NextPurchaseStatusId}", mapping.Id, model.PurchaseStatusId, model.NextPurchaseStatusId); + + var vm = _mapper.Map(mapping); + return ApiResponse.SuccessResponse(vm, "Purchase Order Status mapping removed successfully.", 200); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing AddPurchaseOrderStatusLinkAsync for Tenant: {TenantId}, Employee: {EmployeeId}", tenantId, loggedInEmployee.Id); + return ApiResponse.ErrorResponse("Internal Server Error", "An error occurred while processing your request.", 500); + } + } public async Task> UpdatePurchaseOrderStatusAsync(Guid id, PurchaseOrderStatusDto model, Employee loggedInEmployee, Guid tenantId) { try @@ -3743,7 +3850,7 @@ namespace Marco.Pms.Services.Service var existingPurchaseStatus = await _context.PurchaseOrderStatus.FirstOrDefaultAsync(pos => pos.Id == id && pos.TenantId == tenantId); if (existingPurchaseStatus == null) { - _logger.LogWarning("Purchase Order Status {PurchaseOrderStatusId} not found in database for tenant {TenantId}", id, tenantId); + _logger.LogWarning("Purchase Order Status {PurchaseStatusId} not found in database for tenant {TenantId}", id, tenantId); return ApiResponse.ErrorResponse("Purchase Order Status not found", "Purchase Order Status not found in database for current tenant", 404); } @@ -3790,7 +3897,7 @@ namespace Marco.Pms.Services.Service var existingPurchaseStatus = await _context.PurchaseOrderStatus.FirstOrDefaultAsync(pos => pos.Id == id && pos.TenantId == tenantId); if (existingPurchaseStatus == null) { - _logger.LogWarning("Purchase Order Status {PurchaseOrderStatusId} not found in database for tenant {TenantId} while {Message} Purchase Order Status", id, tenantId, message); + _logger.LogWarning("Purchase Order Status {PurchaseStatusId} not found in database for tenant {TenantId} while {Message} Purchase Order Status", id, tenantId, message); return ApiResponse.ErrorResponse("Purchase Order Status not found", "Purchase Order Status not found in database for current tenant", 404); } diff --git a/Marco.Pms.Services/Service/ServiceInterfaces/IMasterService.cs b/Marco.Pms.Services/Service/ServiceInterfaces/IMasterService.cs index b61ef23..3d935f1 100644 --- a/Marco.Pms.Services/Service/ServiceInterfaces/IMasterService.cs +++ b/Marco.Pms.Services/Service/ServiceInterfaces/IMasterService.cs @@ -148,6 +148,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces Task> GetPurchaseOrderStatusListAsync(bool isActive, Employee loggedInEmployee, Guid tenantId); Task> GetPurchaseOrderStatusDetailsAsync(Guid id, Employee loggedInEmployee, Guid tenantId); Task> CreatePurchaseOrderStatusAsync(PurchaseOrderStatusDto model, Employee loggedInEmployee, Guid tenantId); + Task> AddPurchaseOrderStatusLinkAsync(PurchaseStatusMappingDto model, Employee loggedInEmployee, Guid tenantId); Task> UpdatePurchaseOrderStatusAsync(Guid id, PurchaseOrderStatusDto model, Employee loggedInEmployee, Guid tenantId); Task> DeletePurchaseOrderStatusAsync(Guid id, bool active, Employee loggedInEmployee, Guid tenantId); #endregion