Added an API to deactivate or activate the purchase invoice
This commit is contained in:
parent
4b981b6c74
commit
a4714d5440
@ -122,6 +122,24 @@ namespace Marco.Pms.Services.Controllers
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
[HttpDelete("delete/{id}")]
|
||||
public async Task<IActionResult> DeletePurchaseInvoice(Guid id, CancellationToken ct, [FromQuery] bool isActive = false)
|
||||
{
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _purchaseInvoiceService.DeletePurchaseInvoiceAsync(id, isActive, loggedInEmployee, tenantId, ct);
|
||||
if (response.Success)
|
||||
{
|
||||
var notification = new
|
||||
{
|
||||
LoggedInUserId = loggedInEmployee.Id,
|
||||
Keyword = "Purchase_Invoice",
|
||||
Response = response.Data
|
||||
};
|
||||
await _signalR.SendNotificationAsync(notification);
|
||||
}
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Delivery Challan Functions ===================================================================
|
||||
@ -186,7 +204,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
var notification = new
|
||||
{
|
||||
LoggedInUserId = loggedInEmployee.Id,
|
||||
Keyword = "Delivery_Challan",
|
||||
Keyword = "Purchase_Invoice_Payment",
|
||||
Response = response.Data
|
||||
};
|
||||
await _signalR.SendNotificationAsync(notification);
|
||||
|
||||
@ -340,7 +340,7 @@ namespace Marco.Pms.Services.Service
|
||||
.FirstOrDefaultAsync(ct);
|
||||
|
||||
// 3. Validation: Handle Not Found immediately
|
||||
if (purchaseInvoice == null || !purchaseInvoice.IsActive)
|
||||
if (purchaseInvoice == null)
|
||||
{
|
||||
_logger.LogWarning("Purchase Invoice not found or inactive. InvoiceId: {InvoiceId}", id);
|
||||
return ApiResponse<PurchaseInvoiceDetailsVM>.ErrorResponse("Purchase invoice not found", "The specified purchase invoice does not exist or has been deleted.", 404);
|
||||
@ -930,7 +930,164 @@ namespace Marco.Pms.Services.Service
|
||||
}
|
||||
}
|
||||
|
||||
//public async Task<ApiResponse> DeletePurchaseInvoiceAsync(Guid id, Guid tenantId, CancellationToken ct = default)
|
||||
public async Task<ApiResponse<object>> DeletePurchaseInvoice(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId, CancellationToken ct)
|
||||
{
|
||||
// Check if the employee has the necessary permissions
|
||||
var deletePermission = await HasPermissionAsync(PermissionsMaster.DeletePurchaseInvoice, loggedInEmployee.Id);
|
||||
if (!deletePermission)
|
||||
{
|
||||
_logger.LogWarning("DeletePurchaseInvoiceAsync failed: EmployeeId {EmployeeId} does not have permission.", loggedInEmployee.Id);
|
||||
return ApiResponse<object>.ErrorResponse("Permission denied", "You do not have permission to delete this invoice.", 403);
|
||||
}
|
||||
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync(ct);
|
||||
|
||||
var purchaseInvoice = await context.PurchaseInvoiceDetails.FirstOrDefaultAsync(x => x.Id == id && x.TenantId == tenantId, ct);
|
||||
if (purchaseInvoice == null)
|
||||
{
|
||||
_logger.LogWarning("DeletePurchaseInvoiceAsync failed: InvoiceId {InvoiceId} not found.", id);
|
||||
return ApiResponse<object>.ErrorResponse("Invoice not found", "The invoice with the specified ID was not found.", 404);
|
||||
}
|
||||
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var updateLogHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();
|
||||
|
||||
var existingEntityBson = updateLogHelper.EntityToBsonDocument(purchaseInvoice);
|
||||
|
||||
purchaseInvoice.IsActive = isActive;
|
||||
|
||||
await context.SaveChangesAsync(ct);
|
||||
|
||||
await updateLogHelper.PushToUpdateLogsAsync(
|
||||
new UpdateLogsObject
|
||||
{
|
||||
EntityId = id.ToString(),
|
||||
UpdatedById = loggedInEmployee.Id.ToString(),
|
||||
OldObject = existingEntityBson,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
},
|
||||
"PurchaseInvoiceModificationLog");
|
||||
|
||||
return ApiResponse<object>.SuccessResponse(new { }, "Invoice deleted successfully.", 200);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Soft-deletes or restores a Purchase Invoice by toggling its active flag,
|
||||
/// with permission checks, audit logging, and structured logging suitable
|
||||
/// for enterprise-grade observability.
|
||||
/// </summary>
|
||||
/// <param name="id">The Purchase Invoice identifier.</param>
|
||||
/// <param name="isActive">
|
||||
/// Indicates the new active state:
|
||||
/// false = mark as deleted/inactive (soft delete),
|
||||
/// true = restore/reactivate.
|
||||
/// </param>
|
||||
/// <param name="loggedInEmployee">The currently logged-in employee performing the operation.</param>
|
||||
/// <param name="tenantId">Tenant identifier to enforce multi-tenant isolation.</param>
|
||||
/// <param name="ct">Cancellation token for cooperative cancellation.</param>
|
||||
/// <returns>
|
||||
/// Standardized <see cref="ApiResponse{T}"/> with operation result or error details.
|
||||
/// </returns>
|
||||
public async Task<ApiResponse<object>> DeletePurchaseInvoiceAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId, CancellationToken ct)
|
||||
{
|
||||
// Guard clause: validate invoice identifier.
|
||||
if (id == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning("DeletePurchaseInvoiceAsync called with empty InvoiceId. TenantId: {TenantId}, EmployeeId: {EmployeeId}", tenantId, loggedInEmployee.Id);
|
||||
return ApiResponse<object>.ErrorResponse("Invoice reference is required.", "DeletePurchaseInvoiceAsync received an empty invoice Id.", 400);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Step 1: Permission check for the current employee.
|
||||
var hasDeletePermission = await HasPermissionAsync(PermissionsMaster.DeletePurchaseInvoice, loggedInEmployee.Id);
|
||||
|
||||
if (!hasDeletePermission)
|
||||
{
|
||||
_logger.LogWarning("DeletePurchaseInvoiceAsync permission denied. InvoiceId: {InvoiceId}, TenantId: {TenantId}, EmployeeId: {EmployeeId}",
|
||||
id, tenantId, loggedInEmployee.Id);
|
||||
|
||||
return ApiResponse<object>.ErrorResponse("You do not have permission to modify this invoice.", "DeletePurchaseInvoiceAsync failed due to missing DeletePurchaseInvoice permission.",
|
||||
403);
|
||||
}
|
||||
|
||||
// Step 2: Create a short-lived DbContext for this operation.
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync(ct);
|
||||
|
||||
// Step 3: Retrieve the invoice scoped to the current tenant.
|
||||
var purchaseInvoice = await context.PurchaseInvoiceDetails
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.TenantId == tenantId, ct);
|
||||
|
||||
if (purchaseInvoice == null)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"DeletePurchaseInvoiceAsync failed: Invoice not found. InvoiceId: {InvoiceId}, TenantId: {TenantId}, EmployeeId: {EmployeeId}",
|
||||
id, tenantId, loggedInEmployee.Id);
|
||||
|
||||
return ApiResponse<object>.ErrorResponse("Invoice not found.", $"Purchase invoice not found for Id: {id}, TenantId: {tenantId}.", 404);
|
||||
}
|
||||
|
||||
// Step 4: Create a scoped helper for MongoDB update logs/audit trail.
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var updateLogHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();
|
||||
|
||||
// Capture the existing state for audit logging before modification.
|
||||
var existingEntityBson = updateLogHelper.EntityToBsonDocument(purchaseInvoice);
|
||||
|
||||
// Step 5: Apply the soft-delete or restore operation.
|
||||
purchaseInvoice.IsActive = isActive;
|
||||
|
||||
// Persist changes with cancellation support.
|
||||
var rowsAffected = await context.SaveChangesAsync(ct);
|
||||
|
||||
if (rowsAffected <= 0)
|
||||
{
|
||||
_logger.LogError(null, "DeletePurchaseInvoiceAsync failed to persist changes. InvoiceId: {InvoiceId}, TenantId: {TenantId}, EmployeeId: {EmployeeId}",
|
||||
id, tenantId, loggedInEmployee.Id);
|
||||
return ApiResponse<object>.ErrorResponse("Failed to update the invoice status.", "DeletePurchaseInvoiceAsync SaveChangesAsync returned 0 rows affected.", 500);
|
||||
}
|
||||
|
||||
// Step 6: Push audit log to MongoDB (non-critical but important for traceability).
|
||||
await updateLogHelper.PushToUpdateLogsAsync(
|
||||
new UpdateLogsObject
|
||||
{
|
||||
EntityId = id.ToString(),
|
||||
UpdatedById = loggedInEmployee.Id.ToString(),
|
||||
OldObject = existingEntityBson,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
},
|
||||
"PurchaseInvoiceModificationLog");
|
||||
|
||||
_logger.LogInfo("DeletePurchaseInvoiceAsync completed successfully. InvoiceId: {InvoiceId}, TenantId: {TenantId}, EmployeeId: {EmployeeId}, NewIsActive: {IsActive}",
|
||||
id, tenantId, loggedInEmployee.Id, isActive);
|
||||
|
||||
var action = isActive ? "restored" : "deleted";
|
||||
|
||||
return ApiResponse<object>.SuccessResponse(new { InvoiceId = id, IsActive = isActive }, $"Invoice has been {action} successfully.", 200);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Explicit cancellation handling to avoid misclassification as an error.
|
||||
_logger.LogError(null, "DeletePurchaseInvoiceAsync operation was canceled. InvoiceId: {InvoiceId}, TenantId: {TenantId}, EmployeeId: {EmployeeId}",
|
||||
id, tenantId, loggedInEmployee.Id);
|
||||
return ApiResponse<object>.ErrorResponse("The operation was canceled.", "DeletePurchaseInvoiceAsync was canceled by the caller.", 499);
|
||||
}
|
||||
catch (DbUpdateException dbEx)
|
||||
{
|
||||
// Database-related error with structured logging.
|
||||
_logger.LogError(dbEx, "Database update error in DeletePurchaseInvoiceAsync. InvoiceId: {InvoiceId}, TenantId: {TenantId}, EmployeeId: {EmployeeId}",
|
||||
id, tenantId, loggedInEmployee.Id);
|
||||
return ApiResponse<object>.ErrorResponse("An error occurred while updating the invoice.", "Database update exception occurred in DeletePurchaseInvoiceAsync.", 500);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Catch-all for any unexpected failures.
|
||||
_logger.LogError(ex, "Unexpected error in DeletePurchaseInvoiceAsync. InvoiceId: {InvoiceId}, TenantId: {TenantId}, EmployeeId: {EmployeeId}",
|
||||
id, tenantId, loggedInEmployee.Id);
|
||||
return ApiResponse<object>.ErrorResponse("An unexpected error occurred while updating the invoice status.", "Unhandled exception in DeletePurchaseInvoiceAsync.", 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
@ -1073,7 +1230,7 @@ namespace Marco.Pms.Services.Service
|
||||
// Note: We project only what we need or map later to avoid EF translation issues with complex Mappers.
|
||||
var purchaseInvoiceEntity = await context.PurchaseInvoiceDetails
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(pid => pid.Id == model.PurchaseInvoiceId && pid.IsActive && pid.TenantId == tenantId, ct);
|
||||
.FirstOrDefaultAsync(pid => pid.Id == model.PurchaseInvoiceId && pid.TenantId == tenantId, ct);
|
||||
|
||||
if (purchaseInvoiceEntity == null)
|
||||
{
|
||||
|
||||
@ -15,6 +15,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||
Task<ApiResponse<PurchaseInvoiceDetailsVM>> GetPurchaseInvoiceDetailsAsync(Guid id, Employee loggedInEmployee, Guid tenantId, CancellationToken ct);
|
||||
Task<ApiResponse<PurchaseInvoiceListVM>> CreatePurchaseInvoiceAsync(PurchaseInvoiceDto model, Employee loggedInEmployee, Guid tenantId, CancellationToken ct);
|
||||
Task<ApiResponse<object>> UpdatePurchaseInvoiceAsync(Guid id, PurchaseInvoiceDetails purchaseInvoice, PurchaseInvoiceDto model, Employee loggedInEmployee, Guid tenantId, CancellationToken ct);
|
||||
Task<ApiResponse<object>> DeletePurchaseInvoiceAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId, CancellationToken ct);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user