Initial commit
This commit is contained in:
@@ -0,0 +1,611 @@
|
||||
using AutoMapper;
|
||||
using PowderCoating.Shared.Constants;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PowderCoating.Application.DTOs.Common;
|
||||
using PowderCoating.Application.DTOs.Equipment;
|
||||
using PowderCoating.Application.DTOs.Maintenance;
|
||||
using PowderCoating.Application.Interfaces;
|
||||
using PowderCoating.Core.Entities;
|
||||
using PowderCoating.Core.Enums;
|
||||
using PowderCoating.Core.Interfaces;
|
||||
using PowderCoating.Infrastructure.Data;
|
||||
using PowderCoating.Shared.Constants;
|
||||
|
||||
namespace PowderCoating.Web.Controllers;
|
||||
|
||||
[Authorize(Policy = AppConstants.Policies.CanManageEquipment)]
|
||||
public class EquipmentController : Controller
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IFileService _fileService; // Legacy - kept for backward compatibility
|
||||
private readonly IEquipmentManualService _manualService;
|
||||
private readonly ITenantContext _tenantContext;
|
||||
private readonly ILogger<EquipmentController> _logger;
|
||||
|
||||
public EquipmentController(
|
||||
IUnitOfWork unitOfWork,
|
||||
IMapper mapper,
|
||||
IFileService fileService,
|
||||
IEquipmentManualService manualService,
|
||||
ITenantContext tenantContext,
|
||||
ILogger<EquipmentController> logger)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_mapper = mapper;
|
||||
_fileService = fileService;
|
||||
_manualService = manualService;
|
||||
_tenantContext = tenantContext;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays the paginated equipment list with optional keyword search and status filter.
|
||||
/// Search covers name, equipment number, serial number, manufacturer, and model so
|
||||
/// shop staff can find a piece of equipment using any identifier they have on hand.
|
||||
/// Sorting is resolved server-side via a switch expression so column names never reach
|
||||
/// raw SQL.
|
||||
/// </summary>
|
||||
public async Task<IActionResult> Index(
|
||||
string? searchTerm,
|
||||
EquipmentStatus? statusFilter,
|
||||
string? sortColumn,
|
||||
string sortDirection = "asc",
|
||||
int pageNumber = 1,
|
||||
int pageSize = 25)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Create and validate grid request
|
||||
var gridRequest = new GridRequest
|
||||
{
|
||||
PageNumber = pageNumber,
|
||||
PageSize = pageSize,
|
||||
SortColumn = sortColumn ?? "Name",
|
||||
SortDirection = sortDirection,
|
||||
SearchTerm = searchTerm
|
||||
};
|
||||
gridRequest.Validate();
|
||||
|
||||
// Build search and status filter
|
||||
System.Linq.Expressions.Expression<Func<Equipment, bool>>? filter = null;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchTerm) && statusFilter.HasValue)
|
||||
{
|
||||
// Both search and status filter
|
||||
var search = searchTerm.ToLower();
|
||||
var status = statusFilter.Value;
|
||||
filter = e => (e.EquipmentName.ToLower().Contains(search)
|
||||
|| (e.EquipmentNumber != null && e.EquipmentNumber.ToLower().Contains(search))
|
||||
|| (e.SerialNumber != null && e.SerialNumber.ToLower().Contains(search))
|
||||
|| (e.Manufacturer != null && e.Manufacturer.ToLower().Contains(search))
|
||||
|| (e.Model != null && e.Model.ToLower().Contains(search)))
|
||||
&& e.Status == status;
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(searchTerm))
|
||||
{
|
||||
// Search only
|
||||
var search = searchTerm.ToLower();
|
||||
filter = e => e.EquipmentName.ToLower().Contains(search)
|
||||
|| (e.EquipmentNumber != null && e.EquipmentNumber.ToLower().Contains(search))
|
||||
|| (e.SerialNumber != null && e.SerialNumber.ToLower().Contains(search))
|
||||
|| (e.Manufacturer != null && e.Manufacturer.ToLower().Contains(search))
|
||||
|| (e.Model != null && e.Model.ToLower().Contains(search));
|
||||
}
|
||||
else if (statusFilter.HasValue)
|
||||
{
|
||||
// Status filter only
|
||||
var status = statusFilter.Value;
|
||||
filter = e => e.Status == status;
|
||||
}
|
||||
|
||||
// Build orderBy function
|
||||
Func<IQueryable<Equipment>, IOrderedQueryable<Equipment>> orderBy = gridRequest.SortColumn switch
|
||||
{
|
||||
"Name" => q => gridRequest.SortDirection == "asc" ? q.OrderBy(e => e.EquipmentName) : q.OrderByDescending(e => e.EquipmentName),
|
||||
"EquipmentCode" => q => gridRequest.SortDirection == "asc" ? q.OrderBy(e => e.EquipmentNumber) : q.OrderByDescending(e => e.EquipmentNumber),
|
||||
"Status" => q => gridRequest.SortDirection == "asc" ? q.OrderBy(e => e.Status) : q.OrderByDescending(e => e.Status),
|
||||
"PurchaseDate" => q => gridRequest.SortDirection == "asc" ? q.OrderBy(e => e.PurchaseDate) : q.OrderByDescending(e => e.PurchaseDate),
|
||||
"NextMaintenanceDate" => q => gridRequest.SortDirection == "asc" ? q.OrderBy(e => e.NextScheduledMaintenance) : q.OrderByDescending(e => e.NextScheduledMaintenance),
|
||||
_ => q => q.OrderBy(e => e.EquipmentName)
|
||||
};
|
||||
|
||||
// Get paged data
|
||||
var (items, totalCount) = await _unitOfWork.Equipment.GetPagedAsync(
|
||||
gridRequest.PageNumber,
|
||||
gridRequest.PageSize,
|
||||
filter,
|
||||
orderBy);
|
||||
|
||||
// Map to DTOs
|
||||
var equipmentDtos = _mapper.Map<List<EquipmentListDto>>(items);
|
||||
|
||||
// Create paged result
|
||||
var pagedResult = new PagedResult<EquipmentListDto>
|
||||
{
|
||||
Items = equipmentDtos,
|
||||
PageNumber = gridRequest.PageNumber,
|
||||
PageSize = gridRequest.PageSize,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
|
||||
// Set ViewBag for sorting and filters
|
||||
ViewBag.SearchTerm = searchTerm;
|
||||
ViewBag.StatusFilter = statusFilter;
|
||||
ViewBag.SortColumn = gridRequest.SortColumn;
|
||||
ViewBag.SortDirection = gridRequest.SortDirection;
|
||||
|
||||
return View(pagedResult);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error retrieving equipment");
|
||||
TempData["Error"] = "An error occurred while loading equipment.";
|
||||
return View(new PagedResult<EquipmentListDto>());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the equipment detail page. Maintenance history is loaded via a separate
|
||||
/// FindAsync call (not eager loading on the Equipment entity) and sorted descending
|
||||
/// by ScheduledDate so the most recent maintenance tasks appear first. This gives
|
||||
/// technicians an at-a-glance view of the equipment's service history.
|
||||
/// </summary>
|
||||
public async Task<IActionResult> Details(int? id)
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var equipment = await _unitOfWork.Equipment.GetByIdAsync(id.Value);
|
||||
if (equipment == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var equipmentDto = _mapper.Map<EquipmentDto>(equipment);
|
||||
|
||||
// Load maintenance history
|
||||
var maintenanceRecords = await _unitOfWork.MaintenanceRecords
|
||||
.FindAsync(m => m.EquipmentId == id.Value);
|
||||
var maintenanceList = _mapper.Map<List<MaintenanceListDto>>(maintenanceRecords.OrderByDescending(m => m.ScheduledDate));
|
||||
|
||||
ViewBag.MaintenanceHistory = maintenanceList;
|
||||
|
||||
return View(equipmentDto);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error retrieving equipment {EquipmentId}", id);
|
||||
TempData["Error"] = "An error occurred while loading the equipment.";
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the equipment creation form, defaulting status to Operational because new
|
||||
/// equipment entering the shop is presumed ready to use until a maintenance issue
|
||||
/// is logged. The full EquipmentStatus enum is passed to the view for the status
|
||||
/// dropdown so adding new statuses to the enum automatically appears in the form.
|
||||
/// </summary>
|
||||
public IActionResult Create()
|
||||
{
|
||||
var dto = new CreateEquipmentDto
|
||||
{
|
||||
Status = EquipmentStatus.Operational.ToString()
|
||||
};
|
||||
ViewBag.StatusList = Enum.GetValues<EquipmentStatus>();
|
||||
return View(dto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Persists a new equipment record and sets IsActive to true. The equipment starts
|
||||
/// with no manual file; manuals are uploaded separately via <see cref="UploadManual"/>
|
||||
/// to avoid coupling file I/O to the core create transaction.
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Create(CreateEquipmentDto dto)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
ViewBag.StatusList = Enum.GetValues<EquipmentStatus>();
|
||||
return View(dto);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var equipment = _mapper.Map<Equipment>(dto);
|
||||
equipment.CreatedAt = DateTime.UtcNow;
|
||||
equipment.IsActive = true;
|
||||
|
||||
await _unitOfWork.Equipment.AddAsync(equipment);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
|
||||
TempData["Success"] = "Equipment created successfully.";
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error creating equipment");
|
||||
TempData["Error"] = "An error occurred while creating the equipment.";
|
||||
ViewBag.StatusList = Enum.GetValues<EquipmentStatus>();
|
||||
return View(dto);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the equipment edit form, pre-populated via AutoMapper. The status dropdown
|
||||
/// is reloaded from the enum so it always reflects any additions to <see cref="EquipmentStatus"/>.
|
||||
/// </summary>
|
||||
public async Task<IActionResult> Edit(int? id)
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var equipment = await _unitOfWork.Equipment.GetByIdAsync(id.Value);
|
||||
if (equipment == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var dto = _mapper.Map<UpdateEquipmentDto>(equipment);
|
||||
ViewBag.StatusList = Enum.GetValues<EquipmentStatus>();
|
||||
return View(dto);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error retrieving equipment {EquipmentId} for edit", id);
|
||||
TempData["Error"] = "An error occurred while loading the equipment.";
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Persists edits to an existing equipment record. The Notes field is captured before
|
||||
/// AutoMapper runs and restored if the submitted dto.Notes is blank, because the Notes
|
||||
/// field doubles as legacy file metadata storage (the original upload path was stored
|
||||
/// there). This prevents an Edit save from silently erasing a manual reference that
|
||||
/// was uploaded before the dedicated ManualFilePath column existed.
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Edit(int id, UpdateEquipmentDto dto)
|
||||
{
|
||||
if (id != dto.Id)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
ViewBag.StatusList = Enum.GetValues<EquipmentStatus>();
|
||||
return View(dto);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var equipment = await _unitOfWork.Equipment.GetByIdAsync(id);
|
||||
if (equipment == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
// Preserve Notes field (contains file metadata)
|
||||
var preservedNotes = equipment.Notes;
|
||||
_mapper.Map(dto, equipment);
|
||||
|
||||
// If dto.Notes is empty, restore the preserved notes (file metadata)
|
||||
if (string.IsNullOrWhiteSpace(dto.Notes))
|
||||
{
|
||||
equipment.Notes = preservedNotes;
|
||||
}
|
||||
|
||||
equipment.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
await _unitOfWork.Equipment.UpdateAsync(equipment);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
|
||||
TempData["Success"] = "Equipment updated successfully.";
|
||||
return RedirectToAction(nameof(Details), new { id });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error updating equipment {EquipmentId}", id);
|
||||
TempData["Error"] = "An error occurred while updating the equipment.";
|
||||
ViewBag.StatusList = Enum.GetValues<EquipmentStatus>();
|
||||
return View(dto);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the equipment delete confirmation page, showing a full summary so the user
|
||||
/// can verify they are removing the correct piece of equipment before confirming.
|
||||
/// </summary>
|
||||
public async Task<IActionResult> Delete(int? id)
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var equipment = await _unitOfWork.Equipment.GetByIdAsync(id.Value);
|
||||
if (equipment == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var equipmentDto = _mapper.Map<EquipmentDto>(equipment);
|
||||
return View(equipmentDto);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error retrieving equipment {EquipmentId} for delete", id);
|
||||
TempData["Error"] = "An error occurred while loading the equipment.";
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Soft-deletes the equipment record and cleans up any associated manual file from
|
||||
/// both the new filesystem storage (ManualFilePath via <see cref="IEquipmentManualService"/>)
|
||||
/// and the legacy uploads folder (Notes field starting with "uploads/"). File cleanup
|
||||
/// runs before the soft delete so that orphaned files on disk are not left behind if
|
||||
/// the delete succeeds.
|
||||
/// </summary>
|
||||
[HttpPost, ActionName("Delete")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> DeleteConfirmed(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var equipment = await _unitOfWork.Equipment.GetByIdAsync(id);
|
||||
if (equipment == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
// Delete associated manual file if exists (new filesystem storage)
|
||||
if (!string.IsNullOrEmpty(equipment.ManualFilePath))
|
||||
{
|
||||
await _manualService.DeleteEquipmentManualAsync(equipment.ManualFilePath);
|
||||
}
|
||||
// Delete associated manual file if exists (legacy uploads folder)
|
||||
else if (!string.IsNullOrWhiteSpace(equipment.Notes) && equipment.Notes.StartsWith("uploads/"))
|
||||
{
|
||||
await _fileService.DeleteFileAsync(equipment.Notes);
|
||||
}
|
||||
|
||||
await _unitOfWork.Equipment.SoftDeleteAsync(equipment);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
|
||||
TempData["Success"] = "Equipment deleted successfully.";
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error deleting equipment {EquipmentId}", id);
|
||||
TempData["Error"] = "An error occurred while deleting the equipment.";
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accepts an equipment manual file upload and saves it to the filesystem via
|
||||
/// <see cref="IEquipmentManualService"/>. If a manual already exists (either in the
|
||||
/// new ManualFilePath column or in the legacy Notes/uploads path), it is deleted first
|
||||
/// to prevent orphaned files accumulating on disk. Returns JSON { success, message,
|
||||
/// fileName } so the Details page can update the manual section without a full reload.
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> UploadManual(int equipmentId, IFormFile manualFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (manualFile == null || manualFile.Length == 0)
|
||||
{
|
||||
return Json(new { success = false, message = "No file was uploaded." });
|
||||
}
|
||||
|
||||
var companyId = _tenantContext.GetCurrentCompanyId();
|
||||
if (companyId == null)
|
||||
{
|
||||
return Json(new { success = false, message = "User does not have a company ID." });
|
||||
}
|
||||
|
||||
var equipment = await _unitOfWork.Equipment.GetByIdAsync(equipmentId);
|
||||
if (equipment == null)
|
||||
{
|
||||
return Json(new { success = false, message = "Equipment not found." });
|
||||
}
|
||||
|
||||
// Delete old manual if exists (new filesystem storage)
|
||||
if (!string.IsNullOrEmpty(equipment.ManualFilePath))
|
||||
{
|
||||
await _manualService.DeleteEquipmentManualAsync(equipment.ManualFilePath);
|
||||
}
|
||||
// Delete old manual if exists (legacy uploads folder)
|
||||
else if (!string.IsNullOrWhiteSpace(equipment.Notes) && equipment.Notes.StartsWith("uploads/"))
|
||||
{
|
||||
await _fileService.DeleteFileAsync(equipment.Notes);
|
||||
equipment.Notes = null; // Clear legacy field
|
||||
}
|
||||
|
||||
// Save new file to filesystem
|
||||
var (success, filePath, errorMessage) = await _manualService.SaveEquipmentManualAsync(
|
||||
manualFile,
|
||||
companyId.Value,
|
||||
equipmentId);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
return Json(new { success = false, message = errorMessage });
|
||||
}
|
||||
|
||||
// Update equipment record
|
||||
equipment.ManualFilePath = filePath;
|
||||
equipment.ManualFileName = manualFile.FileName;
|
||||
equipment.ManualFileSize = manualFile.Length;
|
||||
equipment.ManualContentType = manualFile.ContentType;
|
||||
equipment.ManualUploadedDate = DateTime.UtcNow;
|
||||
equipment.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
await _unitOfWork.Equipment.UpdateAsync(equipment);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Equipment {EquipmentId} manual uploaded to filesystem", equipmentId);
|
||||
|
||||
return Json(new
|
||||
{
|
||||
success = true,
|
||||
message = "Manual uploaded successfully.",
|
||||
fileName = manualFile.FileName
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error uploading manual for equipment {EquipmentId}", equipmentId);
|
||||
return Json(new { success = false, message = "An error occurred while uploading the file." });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Streams the equipment manual file back to the browser as a file download. Checks
|
||||
/// the new ManualFilePath column first, then falls back to the legacy Notes/uploads
|
||||
/// path for equipment whose manuals were uploaded before the ManualFilePath column
|
||||
/// was added. For legacy files, the GUID prefix (added by the old upload service) is
|
||||
/// stripped from the download filename so the user sees the original filename.
|
||||
/// </summary>
|
||||
public async Task<IActionResult> DownloadManual(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var equipment = await _unitOfWork.Equipment.GetByIdAsync(id);
|
||||
if (equipment == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
// Try new filesystem storage first
|
||||
if (!string.IsNullOrEmpty(equipment.ManualFilePath))
|
||||
{
|
||||
var (success, fileContent, contentType, errorMessage) = await _manualService.GetEquipmentManualAsync(equipment.ManualFilePath);
|
||||
if (success)
|
||||
{
|
||||
var fileName = equipment.ManualFileName ?? Path.GetFileName(equipment.ManualFilePath);
|
||||
return File(fileContent, contentType, fileName);
|
||||
}
|
||||
|
||||
TempData["Error"] = errorMessage;
|
||||
return RedirectToAction(nameof(Details), new { id });
|
||||
}
|
||||
|
||||
// Fallback to legacy storage (uploads folder)
|
||||
if (!string.IsNullOrWhiteSpace(equipment.Notes) && equipment.Notes.StartsWith("uploads/"))
|
||||
{
|
||||
var result = await _fileService.GetFileAsync(equipment.Notes);
|
||||
if (!result.Success)
|
||||
{
|
||||
TempData["Error"] = result.ErrorMessage;
|
||||
return RedirectToAction(nameof(Details), new { id });
|
||||
}
|
||||
|
||||
var fileName = Path.GetFileName(equipment.Notes);
|
||||
// Remove GUID prefix from filename for download
|
||||
if (fileName.Length > 37 && fileName[36] == '-')
|
||||
{
|
||||
fileName = fileName.Substring(37);
|
||||
}
|
||||
|
||||
return File(result.FileContent, result.ContentType, fileName);
|
||||
}
|
||||
|
||||
return NotFound();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error downloading manual for equipment {EquipmentId}", id);
|
||||
TempData["Error"] = "An error occurred while downloading the file.";
|
||||
return RedirectToAction(nameof(Details), new { id });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the equipment manual from disk and clears the manual metadata fields on the
|
||||
/// equipment entity. Handles both the current filesystem storage path (ManualFilePath)
|
||||
/// and the legacy Notes/uploads path so that manuals uploaded under either storage
|
||||
/// scheme can be removed without leaving orphaned files. Returns JSON so the Details
|
||||
/// page can remove the download link without a full reload.
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> DeleteManual(int equipmentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var equipment = await _unitOfWork.Equipment.GetByIdAsync(equipmentId);
|
||||
if (equipment == null)
|
||||
{
|
||||
return Json(new { success = false, message = "Equipment not found." });
|
||||
}
|
||||
|
||||
// Check if manual exists
|
||||
if (string.IsNullOrEmpty(equipment.ManualFilePath) && string.IsNullOrWhiteSpace(equipment.Notes))
|
||||
{
|
||||
return Json(new { success = false, message = "No manual found." });
|
||||
}
|
||||
|
||||
// Delete from new filesystem storage if exists
|
||||
if (!string.IsNullOrEmpty(equipment.ManualFilePath))
|
||||
{
|
||||
var (success, errorMessage) = await _manualService.DeleteEquipmentManualAsync(equipment.ManualFilePath);
|
||||
if (!success)
|
||||
{
|
||||
return Json(new { success = false, message = errorMessage });
|
||||
}
|
||||
|
||||
equipment.ManualFilePath = null;
|
||||
equipment.ManualFileName = null;
|
||||
equipment.ManualFileSize = null;
|
||||
equipment.ManualContentType = null;
|
||||
equipment.ManualUploadedDate = null;
|
||||
}
|
||||
// Delete from legacy storage if exists
|
||||
else if (!string.IsNullOrWhiteSpace(equipment.Notes) && equipment.Notes.StartsWith("uploads/"))
|
||||
{
|
||||
var result = await _fileService.DeleteFileAsync(equipment.Notes);
|
||||
if (!result.Success)
|
||||
{
|
||||
return Json(new { success = false, message = result.ErrorMessage });
|
||||
}
|
||||
|
||||
equipment.Notes = null;
|
||||
}
|
||||
|
||||
equipment.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
await _unitOfWork.Equipment.UpdateAsync(equipment);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Equipment {EquipmentId} manual deleted", equipmentId);
|
||||
|
||||
return Json(new { success = true, message = "Manual deleted successfully." });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error deleting manual for equipment {EquipmentId}", equipmentId);
|
||||
return Json(new { success = false, message = "An error occurred while deleting the file." });
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user