550 lines
17 KiB
C#
550 lines
17 KiB
C#
using _0_Framework.Application;
|
|
using AccountManagement.Application.Contracts.Task;
|
|
using AccountManagement.Application.Contracts.Ticket;
|
|
using AccountManagement.Application.Contracts.TicketAccessAccount;
|
|
using CompanyManagment.App.Contracts.Workshop;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using ServiceHost.BaseControllers;
|
|
|
|
namespace ServiceHost.Areas.Admin.Controllers;
|
|
|
|
/// <summary>
|
|
/// API controller for admin ticket management functionality
|
|
/// </summary>
|
|
public class TicketController : AdminBaseController
|
|
{
|
|
private readonly ITicketApplication _ticketApplication;
|
|
private readonly IWorkshopApplication _workshopApplication;
|
|
private readonly IAuthHelper _authHelper;
|
|
private readonly ITicketAccessAccountApplication _ticketAccessAccountApplication;
|
|
|
|
/// <summary>
|
|
/// Initialize a new instance of the TicketController
|
|
/// </summary>
|
|
public TicketController(
|
|
ITicketApplication ticketApplication,
|
|
IWorkshopApplication workshopApplication,
|
|
IAuthHelper authHelper,
|
|
ITicketAccessAccountApplication ticketAccessAccountApplication)
|
|
{
|
|
_ticketApplication = ticketApplication;
|
|
_workshopApplication = workshopApplication;
|
|
_authHelper = authHelper;
|
|
_ticketAccessAccountApplication = ticketAccessAccountApplication;
|
|
}
|
|
|
|
private bool HasTicketAccess()
|
|
{
|
|
return _ticketAccessAccountApplication.HasTicketAccess(_authHelper.CurrentAccountId());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get ticket overview including types count and trash count
|
|
/// </summary>
|
|
/// <param name="searchModel">Search criteria for tickets</param>
|
|
/// <returns>Ticket overview data including counts by type</returns>
|
|
[HttpGet]
|
|
public IActionResult GetTickets([FromQuery] TicketSearchModel searchModel)
|
|
{
|
|
if (!HasTicketAccess())
|
|
return Forbid();
|
|
|
|
var typesCount = _ticketApplication.GetTypesCountOfTicketForAdmin();
|
|
var trashCount = _ticketApplication.GetDeletedTicket().Count();
|
|
|
|
return Ok(new
|
|
{
|
|
success = true,
|
|
data = new
|
|
{
|
|
typesCount,
|
|
trashCount
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get paginated ticket data with filtering options
|
|
/// </summary>
|
|
/// <param name="pageIndex">Page index for pagination (default: 1)</param>
|
|
/// <param name="status">Filter by ticket status</param>
|
|
/// <param name="ticketNumber">Filter by ticket number</param>
|
|
/// <param name="generalSearch">General search term</param>
|
|
/// <returns>Paginated list of tickets matching the criteria</returns>
|
|
[HttpGet("data")]
|
|
public IActionResult GetTicketData([FromQuery] int pageIndex = 1, [FromQuery] string status = "",
|
|
[FromQuery] string ticketNumber = "", [FromQuery] string generalSearch = "")
|
|
{
|
|
if (!HasTicketAccess())
|
|
return Forbid();
|
|
|
|
var searchModel = new TicketSearchModel()
|
|
{
|
|
PageIndex = pageIndex,
|
|
Status = status,
|
|
TicketNumber = generalSearch,
|
|
GeneralSearch = generalSearch,
|
|
};
|
|
|
|
List<TicketViewModel> tickets;
|
|
tickets = status != "زباله" ? _ticketApplication.GetAll(searchModel) : _ticketApplication.GetDeletedTicket();
|
|
|
|
return Ok(new
|
|
{
|
|
success = true,
|
|
data = tickets,
|
|
pageIndex = tickets.Count
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get detailed information about a specific ticket
|
|
/// </summary>
|
|
/// <param name="ticketId">The ID of the ticket to retrieve</param>
|
|
/// <returns>Detailed ticket information including workshop name</returns>
|
|
[HttpGet("{ticketId}/detail")]
|
|
public IActionResult GetTicketDetail(long ticketId)
|
|
{
|
|
if (!HasTicketAccess())
|
|
return Forbid();
|
|
|
|
var ticket = _ticketApplication.GetDetails(ticketId);
|
|
if (ticket == null)
|
|
return NotFound();
|
|
|
|
ticket.WorkshopName = _workshopApplication.GetDetails(ticket.WorkshopId).WorkshopFullName;
|
|
|
|
return Ok(new
|
|
{
|
|
success = true,
|
|
data = ticket
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get all messages for a specific ticket
|
|
/// </summary>
|
|
/// <param name="ticketId">The ID of the ticket</param>
|
|
/// <returns>Ticket details including all messages</returns>
|
|
[HttpGet("{ticketId}/messages")]
|
|
public IActionResult GetTicketMessages(long ticketId)
|
|
{
|
|
if (!HasTicketAccess())
|
|
return Forbid();
|
|
|
|
var ticketDetail = _ticketApplication.GetDetails(ticketId);
|
|
if (ticketDetail == null)
|
|
return NotFound();
|
|
|
|
return Ok(new
|
|
{
|
|
success = true,
|
|
data = ticketDetail
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Submit an admin response to a ticket
|
|
/// </summary>
|
|
/// <param name="command">Response details including ticket ID and response text</param>
|
|
/// <returns>Operation result indicating success or failure</returns>
|
|
[HttpPost("response")]
|
|
public IActionResult SaveAdminResponse([FromBody] ResponseTicket command)
|
|
{
|
|
if (!HasTicketAccess())
|
|
return Forbid();
|
|
|
|
command.AdminId = _authHelper.CurrentAccountId();
|
|
command.Response = command.Response?.Replace("\n", "<br>");
|
|
|
|
var result = _ticketApplication.AdminResponseTicket(command);
|
|
|
|
return Ok(new
|
|
{
|
|
success = result.IsSuccedded,
|
|
message = result.Message
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Upload a media file for ticket
|
|
/// </summary>
|
|
/// <param name="media">The file to upload</param>
|
|
/// <returns>Upload result with file ID if successful</returns>
|
|
[HttpPost("upload")]
|
|
public IActionResult UploadFile(IFormFile media)
|
|
{
|
|
if (!HasTicketAccess())
|
|
return Forbid();
|
|
|
|
var accountId = _authHelper.CurrentAccountId();
|
|
var operation = _ticketApplication.UploadMedia(media, accountId);
|
|
|
|
return Ok(new
|
|
{
|
|
success = operation.IsSuccedded,
|
|
message = operation.Message,
|
|
id = operation.SendId,
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delete a media file from the system
|
|
/// </summary>
|
|
/// <param name="mediaId">The ID of the media file to delete</param>
|
|
/// <returns>Operation result indicating success or failure</returns>
|
|
[HttpDelete("media/{mediaId}")]
|
|
public IActionResult DeleteFile(long mediaId)
|
|
{
|
|
if (!HasTicketAccess())
|
|
return Forbid();
|
|
|
|
var operation = _ticketApplication.RemoveMedia(mediaId);
|
|
|
|
return Ok(new
|
|
{
|
|
success = operation.IsSuccedded,
|
|
message = operation.Message,
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove all temporary uploaded files for the current user
|
|
/// </summary>
|
|
/// <returns>Operation result indicating success</returns>
|
|
[HttpDelete("temp-files")]
|
|
public IActionResult RemoveAllTempFiles()
|
|
{
|
|
if (!HasTicketAccess())
|
|
return Forbid();
|
|
|
|
var accId = _authHelper.CurrentAccountId();
|
|
_ticketApplication.RemoveTempUploadedFiles(accId);
|
|
|
|
return Ok(new
|
|
{
|
|
success = true,
|
|
message = "فایلهای موقت با موفقیت حذف شدند"
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Assign a task from a ticket to specific users
|
|
/// </summary>
|
|
/// <param name="request">Task assignment details including description, title, and assignees</param>
|
|
/// <returns>Operation result indicating success or failure</returns>
|
|
[HttpPost("assign-task")]
|
|
public IActionResult AssignTask([FromBody] CreateTaskRequest request)
|
|
{
|
|
if (!HasTicketAccess())
|
|
return Forbid();
|
|
|
|
var command = new CreateTask
|
|
{
|
|
SenderId = _authHelper.CurrentAccountId(),
|
|
Description = request.Description?.Replace("\n", "<br>"),
|
|
Title = request.Title,
|
|
EndTaskDate = request.EndTaskDate,
|
|
EndTaskTime = request.EndTaskTime,
|
|
ReceiverId = request.ReceiverId ?? new List<long>(),
|
|
PositionId = request.PositionId ?? new List<long>(),
|
|
UploadedMedia = request.UploadedMedia ?? new List<long>()
|
|
};
|
|
|
|
var result = _ticketApplication.AssignTicket(command, request.TicketId);
|
|
|
|
return Ok(new
|
|
{
|
|
success = result.IsSuccedded,
|
|
message = result.Message,
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Download or view a file from the system
|
|
/// </summary>
|
|
/// <param name="filePath">The file path on the server</param>
|
|
/// <param name="id">The file ID for naming purposes</param>
|
|
/// <returns>File content for download or viewing</returns>
|
|
[HttpGet("file")]
|
|
public IActionResult GetFile([FromQuery] string filePath, [FromQuery] long id)
|
|
{
|
|
if (!HasTicketAccess())
|
|
return Forbid();
|
|
|
|
if (string.IsNullOrEmpty(filePath) || !System.IO.File.Exists(filePath))
|
|
return NotFound();
|
|
|
|
var contentType = Tools.GetContentTypeImage(Path.GetExtension(filePath));
|
|
if (string.IsNullOrWhiteSpace(contentType))
|
|
{
|
|
byte[] fileContent = System.IO.File.ReadAllBytes(filePath);
|
|
var extension = Tools.GetContentType(Path.GetExtension(filePath));
|
|
return File(fileContent, extension, $"Task_{id}{Path.GetExtension(filePath)}");
|
|
}
|
|
else
|
|
{
|
|
return PhysicalFile(filePath, contentType);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Download or play a voice file
|
|
/// </summary>
|
|
/// <param name="filePath">The voice file path on the server</param>
|
|
/// <returns>Voice file content as audio/ogg</returns>
|
|
[HttpGet("voice")]
|
|
public IActionResult GetVoice([FromQuery] string filePath)
|
|
{
|
|
if (!HasTicketAccess())
|
|
return Forbid();
|
|
|
|
if (string.IsNullOrEmpty(filePath) || !System.IO.File.Exists(filePath))
|
|
return NotFound();
|
|
|
|
byte[] voiceContent = System.IO.File.ReadAllBytes(filePath);
|
|
return File(voiceContent, "audio/ogg", "Task_voice.ogg");
|
|
}
|
|
|
|
/// <summary>
|
|
/// View a picture file
|
|
/// </summary>
|
|
/// <param name="filePath">The image file path on the server</param>
|
|
/// <returns>Image file content</returns>
|
|
[HttpGet("picture")]
|
|
public IActionResult GetPicture([FromQuery] string filePath)
|
|
{
|
|
if (!HasTicketAccess())
|
|
return Forbid();
|
|
|
|
if (string.IsNullOrEmpty(filePath) || !System.IO.File.Exists(filePath))
|
|
return NotFound();
|
|
|
|
var contentType = Tools.GetContentTypeImage(Path.GetExtension(filePath));
|
|
return PhysicalFile(filePath, contentType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accept a pending admin response
|
|
/// </summary>
|
|
/// <param name="adminResId">The ID of the admin response to accept</param>
|
|
/// <returns>Operation result indicating success or failure</returns>
|
|
[HttpPost("response/{adminResId}/accept")]
|
|
public IActionResult AcceptPendingAdminResponse(long adminResId)
|
|
{
|
|
if (!HasTicketAccess())
|
|
return Forbid();
|
|
|
|
var result = _ticketApplication.AcceptPendingAdminResponse(adminResId);
|
|
|
|
return Ok(new
|
|
{
|
|
success = result.IsSuccedded,
|
|
message = result.Message
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reject a pending admin response
|
|
/// </summary>
|
|
/// <param name="adminResId">The ID of the admin response to reject</param>
|
|
/// <returns>Operation result indicating success or failure</returns>
|
|
[HttpPost("response/{adminResId}/reject")]
|
|
public IActionResult RejectPendingAdminResponse(long adminResId)
|
|
{
|
|
if (!HasTicketAccess())
|
|
return Forbid();
|
|
|
|
var result = _ticketApplication.RejectPendingAdminResponse(adminResId);
|
|
|
|
return Ok(new
|
|
{
|
|
success = result.IsSuccedded,
|
|
message = result.Message
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Edit and accept a pending admin response
|
|
/// </summary>
|
|
/// <param name="adminResId">The ID of the admin response to edit</param>
|
|
/// <param name="request">The new response text</param>
|
|
/// <returns>Operation result indicating success or failure</returns>
|
|
[HttpPut("response/{adminResId}")]
|
|
public IActionResult EditPendingAdminResponse(long adminResId, [FromBody] EditResponseRequest request)
|
|
{
|
|
if (!HasTicketAccess())
|
|
return Forbid();
|
|
|
|
var result = _ticketApplication.EditAndAcceptPendingAdminResponse(adminResId, request.NewResponse.Replace("\n", "<br>"));
|
|
|
|
return Ok(new
|
|
{
|
|
success = result.IsSuccedded,
|
|
message = result.Message
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Close a ticket (mark as closed)
|
|
/// </summary>
|
|
/// <param name="ticketId">The ID of the ticket to close</param>
|
|
/// <returns>Operation result indicating success or failure</returns>
|
|
[HttpPost("{ticketId}/close")]
|
|
public IActionResult CloseTicket(long ticketId)
|
|
{
|
|
if (!HasTicketAccess())
|
|
return Forbid();
|
|
|
|
var result = _ticketApplication.CloseTicket(ticketId);
|
|
|
|
return Ok(new
|
|
{
|
|
success = result.IsSuccedded,
|
|
message = result.Message
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delete a ticket (move to trash)
|
|
/// </summary>
|
|
/// <param name="ticketId">The ID of the ticket to delete</param>
|
|
/// <returns>Operation result indicating success or failure</returns>
|
|
[HttpDelete("{ticketId}")]
|
|
public IActionResult DeleteTicket(long ticketId)
|
|
{
|
|
if (!HasTicketAccess())
|
|
return Forbid();
|
|
|
|
var result = _ticketApplication.DeleteTicket(ticketId);
|
|
|
|
return Ok(new
|
|
{
|
|
success = result.IsSuccedded,
|
|
message = result.Message
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get current ticket counts by status (open, closed, answered, pending, etc.)
|
|
/// </summary>
|
|
/// <returns>Ticket counts grouped by status</returns>
|
|
[HttpGet("counts")]
|
|
public IActionResult GetTicketCounts()
|
|
{
|
|
if (!HasTicketAccess())
|
|
return Forbid();
|
|
|
|
var typesCount = _ticketApplication.GetTypesCountOfTicketForAdmin();
|
|
var deletedCount = _ticketApplication.GetDeletedTicket().Count();
|
|
|
|
return Ok(new
|
|
{
|
|
success = true,
|
|
data = new
|
|
{
|
|
open = typesCount.Open,
|
|
closed = typesCount.Closed,
|
|
answered = typesCount.Answered,
|
|
pending = typesCount.Pending,
|
|
all = typesCount.All,
|
|
deleted = deletedCount
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restore a deleted ticket from trash
|
|
/// </summary>
|
|
/// <param name="ticketId">The ID of the ticket to restore</param>
|
|
/// <returns>Operation result indicating success or failure</returns>
|
|
[HttpPost("{ticketId}/restore")]
|
|
public IActionResult RestoreDeletedTicket(long ticketId)
|
|
{
|
|
if (!HasTicketAccess())
|
|
return Forbid();
|
|
|
|
var result = _ticketApplication.RestoreDeletedTicket(ticketId);
|
|
|
|
return Ok(new
|
|
{
|
|
success = result.IsSuccedded,
|
|
message = result.Message
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if the current user has access to ticket functionality
|
|
/// </summary>
|
|
/// <returns>Access status for the current user</returns>
|
|
[HttpGet("access")]
|
|
public IActionResult CheckTicketAccess()
|
|
{
|
|
var accountId = _authHelper.CurrentAccountId();
|
|
var hasAccess = _ticketAccessAccountApplication.HasTicketAccess(accountId);
|
|
|
|
return Ok(new
|
|
{
|
|
success = true,
|
|
hasAccess
|
|
});
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Request model for creating and assigning tasks from tickets
|
|
/// </summary>
|
|
public class CreateTaskRequest
|
|
{
|
|
/// <summary>
|
|
/// Task description
|
|
/// </summary>
|
|
public string Description { get; set; }
|
|
|
|
/// <summary>
|
|
/// Task title
|
|
/// </summary>
|
|
public string Title { get; set; }
|
|
|
|
/// <summary>
|
|
/// Task end date
|
|
/// </summary>
|
|
public string EndTaskDate { get; set; }
|
|
|
|
/// <summary>
|
|
/// Task end time
|
|
/// </summary>
|
|
public string EndTaskTime { get; set; }
|
|
|
|
/// <summary>
|
|
/// List of receiver IDs for task assignment
|
|
/// </summary>
|
|
public List<long> ReceiverId { get; set; }
|
|
|
|
/// <summary>
|
|
/// List of position IDs for task assignment
|
|
/// </summary>
|
|
public List<long> PositionId { get; set; }
|
|
|
|
/// <summary>
|
|
/// List of uploaded media file IDs
|
|
/// </summary>
|
|
public List<long> UploadedMedia { get; set; }
|
|
|
|
/// <summary>
|
|
/// The ticket ID to assign task from
|
|
/// </summary>
|
|
public long TicketId { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Request model for editing admin responses
|
|
/// </summary>
|
|
public class EditResponseRequest
|
|
{
|
|
/// <summary>
|
|
/// The new response text
|
|
/// </summary>
|
|
public string NewResponse { get; set; }
|
|
} |