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);
|
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
|
#endregion
|
||||||
|
|
||||||
#region =================================================================== Delivery Challan Functions ===================================================================
|
#region =================================================================== Delivery Challan Functions ===================================================================
|
||||||
@ -186,7 +204,7 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
var notification = new
|
var notification = new
|
||||||
{
|
{
|
||||||
LoggedInUserId = loggedInEmployee.Id,
|
LoggedInUserId = loggedInEmployee.Id,
|
||||||
Keyword = "Delivery_Challan",
|
Keyword = "Purchase_Invoice_Payment",
|
||||||
Response = response.Data
|
Response = response.Data
|
||||||
};
|
};
|
||||||
await _signalR.SendNotificationAsync(notification);
|
await _signalR.SendNotificationAsync(notification);
|
||||||
|
|||||||
@ -340,7 +340,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
.FirstOrDefaultAsync(ct);
|
.FirstOrDefaultAsync(ct);
|
||||||
|
|
||||||
// 3. Validation: Handle Not Found immediately
|
// 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);
|
_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);
|
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
|
#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.
|
// Note: We project only what we need or map later to avoid EF translation issues with complex Mappers.
|
||||||
var purchaseInvoiceEntity = await context.PurchaseInvoiceDetails
|
var purchaseInvoiceEntity = await context.PurchaseInvoiceDetails
|
||||||
.AsNoTracking()
|
.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)
|
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<PurchaseInvoiceDetailsVM>> GetPurchaseInvoiceDetailsAsync(Guid id, Employee loggedInEmployee, Guid tenantId, CancellationToken ct);
|
||||||
Task<ApiResponse<PurchaseInvoiceListVM>> CreatePurchaseInvoiceAsync(PurchaseInvoiceDto model, 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>> 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
|
#endregion
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user