mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 07:13:12 +00:00
- DispatchWorkOrder junction table for 1:N dispatch-to-WO relationship - Make Dispatch.WorkOrderId nullable (backward compat) - Add AcceptToken and AcknowledgedAt to Dispatch model - Dispatch_DTO accepts WorkOrderIds array - DispatchToVendor creates junction rows, email lists all WOs in table - GetDispatches queries both junction table and direct FK - GetDispatchById includes workOrders list from junction table - Migration with DispatchWorkOrder table
1381 lines
66 KiB
C#
1381 lines
66 KiB
C#
using Api.SeaHavenIndustries.DTOs;
|
|
using Data.SeaHavenIndustries;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Components.Forms;
|
|
using Microsoft.AspNetCore.Hosting;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Data.SqlClient;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.VisualBasic;
|
|
using SendGrid.Helpers.Mail;
|
|
using System.IO;
|
|
using System.Net.Http.Headers;
|
|
using System.Security.Claims;
|
|
using static System.Runtime.InteropServices.JavaScript.JSType;
|
|
|
|
namespace Api.SeaHavenIndustries.Controllers
|
|
{
|
|
// [Authorize]
|
|
[ApiController]
|
|
[Route("api/WorkOrder")]
|
|
[Route("api/workorders")]
|
|
public class WorkOrderController : Controller
|
|
{
|
|
private readonly UserManager<ApplicationUser> _userManager;
|
|
private readonly ApplicationDbContext _db;
|
|
private readonly IWebHostEnvironment _webHostEnvironment;
|
|
IHttpContextAccessor _httpContext;
|
|
private readonly Helper.SendMessage _sendMessage;
|
|
public WorkOrderController(UserManager<ApplicationUser> userManager, ApplicationDbContext db, IWebHostEnvironment webHostEnvironment, IHttpContextAccessor httpContext, Helper.SendMessage sendMessage)
|
|
{
|
|
_userManager = userManager;
|
|
_db = db;
|
|
_webHostEnvironment = webHostEnvironment;
|
|
_httpContext = httpContext;
|
|
_sendMessage = sendMessage;
|
|
}
|
|
[HttpPost]
|
|
[Route("AddWorkorder")]
|
|
public async Task<IActionResult> Addworkorder([FromBody] Workorder_DTO model)
|
|
{
|
|
try
|
|
{
|
|
string userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
WorkOrder workOrder = new WorkOrder();
|
|
workOrder.InternalWONumber = await GenerateInternalWONumber();
|
|
workOrder.WorkerOrderNumber = model.WorkerOrderNumber;
|
|
workOrder.WorkerOrderTitle = model.WorkerOrderTitle;
|
|
workOrder.Description = model.Description;
|
|
workOrder.TT = model.TT;
|
|
workOrder.PO = model.PO;
|
|
workOrder.AssignTo = model.AssignTo;
|
|
workOrder.DueDate = model.DueDate;
|
|
workOrder.Priority = model.Priority;
|
|
workOrder.istemplate = model.istemplate;
|
|
//workOrder.CategoryId = model.CategoryId;
|
|
workOrder.LocationId = model.LocationId;
|
|
workOrder.Status = "Open";
|
|
workOrder.istemplate = model.istemplate;
|
|
workOrder.BeforPhotoIssue = model.BeforPhotoIssue;
|
|
workOrder.AfterPhotoIssue = model.AfterPhotoIssue;
|
|
workOrder.SignOffName = model.SignOffName;
|
|
workOrder.SignOffSignature = model.SignOffSignature;
|
|
|
|
workOrder.WorkOrderContacts = workOrder.WorkOrderContacts == null ? new List<WorkOrderContacts>() : workOrder.WorkOrderContacts;
|
|
List<int> contactIds = model.GetContactIds();
|
|
workOrder.WorkOrderCategories = workOrder.WorkOrderCategories == null ? new List<WorkOrderCategories>() : workOrder.WorkOrderCategories;
|
|
List<int> categoryIds = model.GetCategoryIds();
|
|
|
|
// Create new records for the associations (pseudo-code)
|
|
foreach (int contactId in contactIds)
|
|
{
|
|
// Create association with contact
|
|
workOrder.WorkOrderContacts.Add(new WorkOrderContacts { ContactId = contactId });
|
|
}
|
|
|
|
foreach (int categoryId in categoryIds)
|
|
{
|
|
// Create association with category
|
|
workOrder.WorkOrderCategories.Add(new WorkOrderCategories { CategoryId = categoryId });
|
|
}
|
|
|
|
#region Images of workorder
|
|
workOrder.workOrderAttachments = workOrder.workOrderAttachments == null ? new List<WorkOrderAttachments>() : workOrder.workOrderAttachments;
|
|
if (model.Attachments != null)
|
|
{
|
|
foreach (var attachment in model.Attachments)
|
|
{
|
|
string fileName = ContentDispositionHeaderValue.Parse(attachment.ContentDisposition).FileName.Trim('"');
|
|
string uniqueFileName = $"{Guid.NewGuid()}_{fileName}";
|
|
|
|
string uploadProfilePath = Path.Combine("Assets", "Documents");
|
|
string fullPath = Path.Combine(_webHostEnvironment.WebRootPath, uploadProfilePath, uniqueFileName);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
|
|
|
|
// Copy the file to the server
|
|
using (var ms = System.IO.File.Create(fullPath))
|
|
{
|
|
await attachment.CopyToAsync(ms);
|
|
}
|
|
|
|
// Update the user's image URL
|
|
var request = _httpContext.HttpContext.Request;
|
|
var domain = $"{request.Scheme}://{request.Host}";
|
|
var imagesPath = domain + "/" + Path.Combine(uploadProfilePath, uniqueFileName);
|
|
workOrder.workOrderAttachments.Add(new WorkOrderAttachments { Attachments = imagesPath });
|
|
}
|
|
}
|
|
|
|
if (model.BeforPhotoAttachment != null)
|
|
{
|
|
string fileName = ContentDispositionHeaderValue.Parse(model.BeforPhotoAttachment.ContentDisposition).FileName.Trim('"');
|
|
string uniqueFileName = $"{Guid.NewGuid()}_{fileName}";
|
|
|
|
string uploadProfilePath = Path.Combine("Assets", "Documents");
|
|
string fullPath = Path.Combine(_webHostEnvironment.WebRootPath, uploadProfilePath, uniqueFileName);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
|
|
// Copy the file to the server
|
|
using (var ms = System.IO.File.Create(fullPath))
|
|
{
|
|
await model.BeforPhotoAttachment.CopyToAsync(ms);
|
|
}
|
|
// Update the user's image URL
|
|
var request = _httpContext.HttpContext.Request;
|
|
var domain = $"{request.Scheme}://{request.Host}";
|
|
workOrder.BeforPhotoAttachment = domain + "/" + Path.Combine(uploadProfilePath, uniqueFileName);
|
|
}
|
|
if (model.AfterPhotoAttachment != null)
|
|
{
|
|
string fileName = ContentDispositionHeaderValue.Parse(model.AfterPhotoAttachment.ContentDisposition).FileName.Trim('"');
|
|
string uniqueFileName = $"{Guid.NewGuid()}_{fileName}";
|
|
|
|
string uploadProfilePath = Path.Combine("Assets", "Documents");
|
|
string fullPath = Path.Combine(_webHostEnvironment.WebRootPath, uploadProfilePath, uniqueFileName);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
|
|
// Copy the file to the server
|
|
using (var ms = System.IO.File.Create(fullPath))
|
|
{
|
|
await model.AfterPhotoAttachment.CopyToAsync(ms);
|
|
}
|
|
// Update the user's image URL
|
|
var request = _httpContext.HttpContext.Request;
|
|
var domain = $"{request.Scheme}://{request.Host}";
|
|
workOrder.AfterPhotoAttachment = domain + "/" + Path.Combine(uploadProfilePath, uniqueFileName);
|
|
}
|
|
if (model.SignOffAttachment != null)
|
|
{
|
|
string fileName = ContentDispositionHeaderValue.Parse(model.SignOffAttachment.ContentDisposition).FileName.Trim('"');
|
|
string uniqueFileName = $"{Guid.NewGuid()}_{fileName}";
|
|
|
|
string uploadProfilePath = Path.Combine("Assets", "Documents");
|
|
string fullPath = Path.Combine(_webHostEnvironment.WebRootPath, uploadProfilePath, uniqueFileName);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
|
|
// Copy the file to the server
|
|
using (var ms = System.IO.File.Create(fullPath))
|
|
{
|
|
await model.SignOffAttachment.CopyToAsync(ms);
|
|
}
|
|
// Update the user's image URL
|
|
var request = _httpContext.HttpContext.Request;
|
|
var domain = $"{request.Scheme}://{request.Host}";
|
|
workOrder.SignOffAttachment = domain + "/" + Path.Combine(uploadProfilePath, uniqueFileName);
|
|
}
|
|
#endregion
|
|
_db.Add(workOrder);
|
|
_db.SaveChanges();
|
|
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200" });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
|
}
|
|
}
|
|
|
|
|
|
[HttpPost]
|
|
[HttpPut("{id:int}")]
|
|
[Route("EditWorkorder")]
|
|
public async Task<IActionResult> Editworkorder([FromBody] EditWorkorder_DTO model)
|
|
{
|
|
try
|
|
{
|
|
var workOrder = _db.workOrders.Where(w => w.Id == model.Id).FirstOrDefault();
|
|
if (workOrder == null)
|
|
return BadRequest(new Response { Status = "Error", Message = "Work order not found" });
|
|
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
var now = DateTime.UtcNow;
|
|
void LogChange(string field, string? oldVal, string? newVal)
|
|
{
|
|
if (oldVal == newVal) return;
|
|
_db.WorkOrderAuditLogs.Add(new WorkOrderAuditLog
|
|
{
|
|
WorkOrderId = workOrder.Id,
|
|
UserId = userId,
|
|
FieldName = field,
|
|
OldValue = oldVal ?? "",
|
|
NewValue = newVal ?? "",
|
|
Action = "edit",
|
|
CreatedAt = now
|
|
});
|
|
}
|
|
|
|
LogChange("WorkOrderNumber", workOrder.WorkerOrderNumber, model.WorkerOrderNumber);
|
|
LogChange("Title", workOrder.WorkerOrderTitle, model.WorkerOrderTitle);
|
|
LogChange("Description", workOrder.Description, model.Description);
|
|
LogChange("TT", workOrder.TT, model.TT);
|
|
LogChange("PO", workOrder.PO, model.PO);
|
|
LogChange("AssignTo", workOrder.AssignTo, model.AssignTo);
|
|
LogChange("DueDate", workOrder.DueDate?.ToString("yyyy-MM-dd"), model.DueDate?.ToString("yyyy-MM-dd"));
|
|
LogChange("Priority", workOrder.Priority, model.Priority);
|
|
LogChange("LocationId", workOrder.LocationId?.ToString(), model.LocationId?.ToString());
|
|
LogChange("Status", workOrder.Status, model.Status);
|
|
LogChange("Problem", workOrder.Problem, model.Problem);
|
|
LogChange("Trade", workOrder.Trade, model.Trade);
|
|
LogChange("SubTrade", workOrder.SubTrade, model.SubTrade);
|
|
LogChange("VendorNTE", workOrder.VendorNTE?.ToString("F2"), model.VendorNTE?.ToString("F2"));
|
|
LogChange("ScheduledDate", workOrder.ScheduledDate?.ToString("yyyy-MM-dd"), model.ScheduledDate?.ToString("yyyy-MM-dd"));
|
|
LogChange("CompletedDate", workOrder.CompletedDate?.ToString("yyyy-MM-dd"), model.CompletedDate?.ToString("yyyy-MM-dd"));
|
|
LogChange("Source", workOrder.Source, model.Source);
|
|
|
|
workOrder.WorkerOrderNumber = model.WorkerOrderNumber;
|
|
workOrder.WorkerOrderTitle = model.WorkerOrderTitle;
|
|
workOrder.Description = model.Description;
|
|
workOrder.TT = model.TT;
|
|
workOrder.PO = model.PO;
|
|
workOrder.AssignTo = model.AssignTo;
|
|
workOrder.DueDate = model.DueDate;
|
|
workOrder.Priority = model.Priority;
|
|
workOrder.LocationId = model.LocationId;
|
|
workOrder.Status = model.Status;
|
|
workOrder.Problem = model.Problem;
|
|
workOrder.Trade = model.Trade;
|
|
workOrder.SubTrade = model.SubTrade;
|
|
workOrder.VendorNTE = model.VendorNTE;
|
|
workOrder.ScheduledDate = model.ScheduledDate;
|
|
workOrder.CompletedDate = model.CompletedDate;
|
|
workOrder.Source = model.Source;
|
|
workOrder.BeforPhotoIssue = model.BeforPhotoIssue;
|
|
workOrder.AfterPhotoIssue = model.AfterPhotoIssue;
|
|
workOrder.SignOffName = model.SignOffName;
|
|
workOrder.SignOffSignature = model.SignOffSignature;
|
|
var existcontact = _db.WorkOrderContacts.Where(w => w.WorkorderId == model.Id).ToList();
|
|
if (existcontact.Count() > 0)
|
|
{
|
|
_db.WorkOrderContacts.RemoveRange(existcontact);
|
|
}
|
|
var existcategory = _db.workOrderCategories.Where(w => w.WorkorderId == model.Id).ToList();
|
|
if (existcategory.Count() > 0)
|
|
{
|
|
_db.workOrderCategories.RemoveRange(existcategory);
|
|
}
|
|
workOrder.WorkOrderContacts = workOrder.WorkOrderContacts == null ? new List<WorkOrderContacts>() : workOrder.WorkOrderContacts;
|
|
List<int> contactIds = model.GetContactIds();
|
|
|
|
workOrder.WorkOrderCategories = workOrder.WorkOrderCategories == null ? new List<WorkOrderCategories>() : workOrder.WorkOrderCategories;
|
|
List<int> categoryIds = model.GetCategoryIds();
|
|
|
|
// Create new records for the associations (pseudo-code)
|
|
foreach (int contactId in contactIds)
|
|
{
|
|
// Create association with contact
|
|
workOrder.WorkOrderContacts.Add(new WorkOrderContacts { ContactId = contactId });
|
|
}
|
|
|
|
foreach (int categoryId in categoryIds)
|
|
{
|
|
// Create association with category
|
|
workOrder.WorkOrderCategories.Add(new WorkOrderCategories { CategoryId = categoryId });
|
|
}
|
|
|
|
#region Images of workorder
|
|
workOrder.workOrderAttachments = workOrder.workOrderAttachments == null ? new List<WorkOrderAttachments>() : workOrder.workOrderAttachments;
|
|
if (model.Attachments != null)
|
|
{
|
|
foreach (var attachment in model.Attachments)
|
|
{
|
|
string fileName = ContentDispositionHeaderValue.Parse(attachment.ContentDisposition).FileName.Trim('"');
|
|
string uniqueFileName = $"{Guid.NewGuid()}_{fileName}";
|
|
|
|
string uploadProfilePath = Path.Combine("Assets", "Documents");
|
|
string fullPath = Path.Combine(_webHostEnvironment.WebRootPath, uploadProfilePath, uniqueFileName);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
|
|
|
|
using (var ms = System.IO.File.Create(fullPath))
|
|
{
|
|
await attachment.CopyToAsync(ms);
|
|
}
|
|
|
|
var request = _httpContext.HttpContext.Request;
|
|
var domain = $"{request.Scheme}://{request.Host}";
|
|
var imagesPath = domain + "/" + Path.Combine(uploadProfilePath, uniqueFileName);
|
|
workOrder.workOrderAttachments.Add(new WorkOrderAttachments { Attachments = imagesPath });
|
|
}
|
|
}
|
|
|
|
if (model.BeforPhotoAttachment != null)
|
|
{
|
|
string fileName = ContentDispositionHeaderValue.Parse(model.BeforPhotoAttachment.ContentDisposition).FileName.Trim('"');
|
|
string uniqueFileName = $"{Guid.NewGuid()}_{fileName}";
|
|
|
|
string uploadProfilePath = Path.Combine("Assets", "Documents");
|
|
string fullPath = Path.Combine(_webHostEnvironment.WebRootPath, uploadProfilePath, uniqueFileName);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
|
|
// Copy the file to the server
|
|
using (var ms = System.IO.File.Create(fullPath))
|
|
{
|
|
await model.BeforPhotoAttachment.CopyToAsync(ms);
|
|
}
|
|
// Update the user's image URL
|
|
var request = _httpContext.HttpContext.Request;
|
|
var domain = $"{request.Scheme}://{request.Host}";
|
|
workOrder.BeforPhotoAttachment = domain + "/" + Path.Combine(uploadProfilePath, uniqueFileName);
|
|
}
|
|
if (model.AfterPhotoAttachment != null)
|
|
{
|
|
string fileName = ContentDispositionHeaderValue.Parse(model.AfterPhotoAttachment.ContentDisposition).FileName.Trim('"');
|
|
string uniqueFileName = $"{Guid.NewGuid()}_{fileName}";
|
|
|
|
string uploadProfilePath = Path.Combine("Assets", "Documents");
|
|
string fullPath = Path.Combine(_webHostEnvironment.WebRootPath, uploadProfilePath, uniqueFileName);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
|
|
// Copy the file to the server
|
|
using (var ms = System.IO.File.Create(fullPath))
|
|
{
|
|
await model.AfterPhotoAttachment.CopyToAsync(ms);
|
|
}
|
|
// Update the user's image URL
|
|
var request = _httpContext.HttpContext.Request;
|
|
var domain = $"{request.Scheme}://{request.Host}";
|
|
workOrder.AfterPhotoAttachment = domain + "/" + Path.Combine(uploadProfilePath, uniqueFileName);
|
|
}
|
|
if (model.SignOffAttachment != null)
|
|
{
|
|
string fileName = ContentDispositionHeaderValue.Parse(model.SignOffAttachment.ContentDisposition).FileName.Trim('"');
|
|
string uniqueFileName = $"{Guid.NewGuid()}_{fileName}";
|
|
|
|
string uploadProfilePath = Path.Combine("Assets", "Documents");
|
|
string fullPath = Path.Combine(_webHostEnvironment.WebRootPath, uploadProfilePath, uniqueFileName);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
|
|
// Copy the file to the server
|
|
using (var ms = System.IO.File.Create(fullPath))
|
|
{
|
|
await model.SignOffAttachment.CopyToAsync(ms);
|
|
}
|
|
// Update the user's image URL
|
|
var request = _httpContext.HttpContext.Request;
|
|
var domain = $"{request.Scheme}://{request.Host}";
|
|
workOrder.SignOffAttachment = domain + "/" + Path.Combine(uploadProfilePath, uniqueFileName);
|
|
}
|
|
#endregion
|
|
_db.SaveChanges();
|
|
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200" });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
|
}
|
|
}
|
|
// -----------------------------------
|
|
// GET: api/WorkOrder/GetWorkOrderList
|
|
// -----------------------------------
|
|
[HttpGet("GetWorkOrderList")]
|
|
public IActionResult GetWorkOrderList(string? search = "", int page = 1, int pageSize = 12, string? sortBy = "lastUpdated", string? sortDir = "desc", string? status = "", string? assignee = "")
|
|
{
|
|
search ??= "";
|
|
|
|
var query = _db.workOrders
|
|
.Include(w => w.Locations)
|
|
.Include(w => w.AssignToUser)
|
|
.Where(w => w.istemplate != true);
|
|
|
|
if (!string.IsNullOrWhiteSpace(status))
|
|
query = query.Where(w => w.Status == status);
|
|
|
|
if (assignee == "__unassigned")
|
|
query = query.Where(w => w.AssignTo == null || w.AssignTo == "");
|
|
else if (!string.IsNullOrWhiteSpace(assignee))
|
|
query = query.Where(w => w.AssignToUser != null && (w.AssignToUser.FirstName + " " + w.AssignToUser.LastName).Contains(assignee));
|
|
|
|
if (!string.IsNullOrWhiteSpace(search))
|
|
{
|
|
var s = search.ToLower();
|
|
query = query.Where(w =>
|
|
(w.InternalWONumber ?? "").ToLower().Contains(s) ||
|
|
(w.WorkerOrderNumber ?? "").ToLower().Contains(s) ||
|
|
(w.WorkerOrderTitle ?? "").ToLower().Contains(s) ||
|
|
(w.Locations.Title ?? "").ToLower().Contains(s) ||
|
|
(w.Locations.Name ?? "").ToLower().Contains(s)
|
|
);
|
|
}
|
|
|
|
var totalCount = query.Count();
|
|
|
|
var isAsc = sortDir?.ToLower() == "asc";
|
|
IOrderedQueryable<WorkOrder> ordered = (sortBy?.ToLower()) switch
|
|
{
|
|
"number" => isAsc ? query.OrderBy(w => w.InternalWONumber) : query.OrderByDescending(w => w.InternalWONumber),
|
|
"title" => isAsc ? query.OrderBy(w => w.WorkerOrderTitle) : query.OrderByDescending(w => w.WorkerOrderTitle),
|
|
"location" => isAsc ? query.OrderBy(w => w.Locations != null ? w.Locations.Name : "") : query.OrderByDescending(w => w.Locations != null ? w.Locations.Name : ""),
|
|
"priority" => isAsc ? query.OrderBy(w => w.Priority) : query.OrderByDescending(w => w.Priority),
|
|
"status" => isAsc ? query.OrderBy(w => w.Status) : query.OrderByDescending(w => w.Status),
|
|
"createddate" => isAsc ? query.OrderBy(w => w.CreatedDate) : query.OrderByDescending(w => w.CreatedDate),
|
|
"assignedto" => isAsc ? query.OrderBy(w => w.AssignToUser != null ? w.AssignToUser.FirstName : "") : query.OrderByDescending(w => w.AssignToUser != null ? w.AssignToUser.FirstName : ""),
|
|
"lastupdated" => isAsc ? query.OrderBy(w => w.LastModificationTime) : query.OrderByDescending(w => w.LastModificationTime),
|
|
_ => query.OrderByDescending(w => w.Id)
|
|
};
|
|
|
|
var data = ordered
|
|
.Skip((page - 1) * pageSize)
|
|
.Take(pageSize)
|
|
.Select(w => new
|
|
{
|
|
id = w.Id,
|
|
internalWONumber = w.InternalWONumber,
|
|
workOrderNumber = w.WorkerOrderNumber,
|
|
number = w.InternalWONumber ?? w.WorkerOrderNumber,
|
|
title = w.WorkerOrderTitle,
|
|
location = w.Locations != null ? w.Locations.Name : "",
|
|
locationName = w.Locations != null ? w.Locations.Name : "",
|
|
priority = w.Priority,
|
|
status = w.Status,
|
|
createdDate = w.CreatedDate,
|
|
dueDate = w.DueDate,
|
|
assignedTo = w.AssignToUser != null ? w.AssignToUser.FirstName + " " + w.AssignToUser.LastName : "",
|
|
employeeName = w.AssignToUser != null ? w.AssignToUser.FirstName + " " + w.AssignToUser.LastName : "",
|
|
lastUpdated = new[] {
|
|
w.LastModificationTime,
|
|
w.Comments.Max(c => (DateTime?)c.CreatedDate),
|
|
_db.WorkOrderAuditLogs.Where(a => a.WorkOrderId == w.Id).Max(a => (DateTime?)a.CreatedAt)
|
|
}.Where(d => d.HasValue).Max()
|
|
})
|
|
.ToList();
|
|
|
|
var viewModel = new Pagination_DTO
|
|
{
|
|
Data = data,
|
|
PageNumber = page,
|
|
PageSize = pageSize,
|
|
TotalCount = totalCount,
|
|
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
|
|
};
|
|
|
|
return Ok(viewModel);
|
|
}
|
|
|
|
|
|
[HttpDelete]
|
|
[Route("DeleteWorkorder")]
|
|
public IActionResult Deleteworkorder([FromBody] EditWorkorder_DTO model)
|
|
{
|
|
int id = model.Id.HasValue ? model.Id.Value : 0;
|
|
if (id == 0)
|
|
{
|
|
return BadRequest("Id is required");
|
|
}
|
|
try
|
|
{
|
|
var exist = _db.workOrders.Where(w => w.Id == id).FirstOrDefault();
|
|
if (exist != null)
|
|
{
|
|
var quotes = _db.Quotes.Where(w => w.WorkorderId == id).ToList();
|
|
if (quotes.Count() > 0)
|
|
{
|
|
foreach (var itm in quotes)
|
|
{
|
|
var existquotes = _db.Quotes.Where(w => w.Id == itm.Id).FirstOrDefault();
|
|
existquotes.WorkorderId = null;
|
|
}
|
|
}
|
|
var attachments = _db.workOrderAttachments.Where(w => w.WorkorderId == id).ToList();
|
|
if (attachments.Count() > 0)
|
|
{
|
|
_db.RemoveRange(attachments);
|
|
}
|
|
var contacts = _db.WorkOrderContacts.Where(w => w.WorkorderId == id).ToList();
|
|
if (contacts.Count() > 0)
|
|
{
|
|
_db.RemoveRange(contacts);
|
|
}
|
|
var category = _db.workOrderCategories.Where(w => w.WorkorderId == id).ToList();
|
|
if (category.Count() > 0)
|
|
{
|
|
_db.RemoveRange(category);
|
|
}
|
|
var comments = _db.Comments.Where(w => w.WorkerOrderId == id).ToList();
|
|
if (comments.Count() > 0)
|
|
{
|
|
_db.RemoveRange(comments);
|
|
}
|
|
_db.Remove(exist);
|
|
|
|
}
|
|
else
|
|
{
|
|
return BadRequest("ID not matched!");
|
|
}
|
|
_db.SaveChanges();
|
|
return Ok(new DataResponse { Message = "Removed Successfully", Status = "200" });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
|
}
|
|
}
|
|
|
|
[HttpGet]
|
|
[Route("GetFilteredWorkorder")]
|
|
public IActionResult GetFilteredWorkorder(
|
|
[FromQuery] string[] assignto,
|
|
[FromQuery] int[]? location,
|
|
[FromQuery] string[]? priority,
|
|
[FromQuery] string[]? status,
|
|
[FromQuery] string[]? duedate,
|
|
[FromQuery] string search = "",
|
|
[FromQuery] string sort = "Sort Order",
|
|
[FromQuery] string sortby = "Sort By",
|
|
int page = 1,
|
|
int pageSize = 10)
|
|
{
|
|
try
|
|
{
|
|
IQueryable<WorkOrder> query = _db.workOrders.Where(w => w.istemplate != true).Include(w => w.Locations);
|
|
|
|
if (!string.IsNullOrEmpty(search))
|
|
{
|
|
var s = search.ToLower();
|
|
query = query.Where(w =>
|
|
(w.WorkerOrderTitle ?? "").ToLower().Contains(s) ||
|
|
(w.WorkerOrderNumber ?? "").ToLower().Contains(s) ||
|
|
(w.Locations != null && (w.Locations.Title ?? "").ToLower().Contains(s)));
|
|
}
|
|
|
|
if (assignto != null && assignto.Length > 0)
|
|
{
|
|
if (assignto[0] != "" && assignto[0] != null)
|
|
query = query.Where(w => assignto.Contains(w.AssignTo));
|
|
}
|
|
|
|
if (location != null && location.Length > 0)
|
|
{
|
|
if (location[0] != 0)
|
|
query = query.Where(w => location.Contains(w.LocationId ?? 0));
|
|
}
|
|
|
|
if (priority != null && priority.Length > 0)
|
|
{
|
|
if (priority[0] != "" && priority[0] != null)
|
|
query = query.Where(w => priority.Contains(w.Priority));
|
|
}
|
|
|
|
if (status != null && status.Length > 0)
|
|
{
|
|
if (status[0] != "" && status[0] != null)
|
|
query = query.Where(w => status.Contains(w.Status));
|
|
}
|
|
var data = query.ToList();
|
|
|
|
// Perform client-side filtering for due dates
|
|
data = data.Where(w =>
|
|
duedate == null || duedate.Length == 0 || duedate[0] == null ||
|
|
duedate.Any(dd => w.DueDate.HasValue && CheckDueDate(dd, w.DueDate.Value.Date, DateTime.UtcNow.Date))).ToList();
|
|
|
|
// Apply sorting
|
|
if (!string.IsNullOrEmpty(sort) && !string.IsNullOrEmpty(sortby) && sortby != "")
|
|
{
|
|
switch (sortby)
|
|
{
|
|
case "Name":
|
|
data = (sort.ToLower() == "ascending") ? data.OrderBy(w => w.WorkerOrderTitle).ToList() : data.OrderByDescending(w => w.WorkerOrderTitle).ToList();
|
|
break;
|
|
case "Due Date":
|
|
data = (sort.ToLower() == "ascending") ? data.OrderBy(w => w.DueDate).ToList() : data.OrderByDescending(w => w.DueDate).ToList();
|
|
break;
|
|
case "Severity":
|
|
data = (sort.ToLower() == "ascending") ? data.OrderBy(w => w.Priority).ToList() : data.OrderByDescending(w => w.Priority).ToList();
|
|
break;
|
|
default:
|
|
// Handle default case or throw an exception
|
|
break;
|
|
}
|
|
}
|
|
|
|
var totalCount = data.Count();
|
|
var pagedData = data
|
|
.Skip((page - 1) * pageSize)
|
|
.Take(pageSize)
|
|
.Select(s => new { s.Id, s.WorkerOrderTitle, s.DueDate, s.Status, s.Priority, Name = s.Locations != null ? s.Locations.Name : "" })
|
|
.OrderByDescending(d => d.Name)
|
|
.ToList();
|
|
|
|
var totalPages1 = (int)Math.Ceiling(totalCount / (double)pageSize);
|
|
|
|
// Create a view model containing the paged items and pagination metadata
|
|
var viewModel = new Pagination_DTO
|
|
{
|
|
Data = pagedData,
|
|
PageNumber = page,
|
|
PageSize = pageSize,
|
|
TotalCount = totalCount,
|
|
TotalPages = totalPages1
|
|
};
|
|
|
|
return Ok(viewModel);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Handle exception and return a default result or an error response
|
|
return StatusCode(500, "Internal Server Error");
|
|
}
|
|
}
|
|
|
|
[HttpGet("{id:int}")]
|
|
[HttpGet("GetWorkorderById")]
|
|
public async Task<IActionResult> GetWorkorderById(int id)
|
|
{
|
|
var exist = await _db.workOrders.Include(w => w.Locations).Include(w => w.Comments).Include(w => w.Quotes).Include(w => w.Dispatches).ThenInclude(d => d.Vendor).Where(w => w.Id == id).Select(s => new
|
|
{
|
|
s.Id,
|
|
s.InternalWONumber,
|
|
s.WorkerOrderNumber,
|
|
title = s.WorkerOrderTitle,
|
|
s.Description,
|
|
s.CreatedDate,
|
|
s.DueDate,
|
|
s.ScheduledDate,
|
|
s.CompletedDate,
|
|
s.Status,
|
|
assignee = s.AssignToUser != null ? s.AssignToUser.FirstName : "",
|
|
location = s.Locations != null ? s.Locations.Name : "",
|
|
locationId = s.LocationId,
|
|
locationZip = s.Locations != null ? (s.Locations.ZipCode ?? ExtractZip(s.Locations.Address)) : "",
|
|
priority = s.Priority,
|
|
s.Problem,
|
|
s.Trade,
|
|
s.SubTrade,
|
|
s.VendorNTE,
|
|
s.Source,
|
|
s.PO,
|
|
s.TT,
|
|
poc = s.WorkOrderContacts.Select(c => new { c.Id, name = c.POC != null ? (c.POC.FirstName + " " + c.POC.LastName).Trim() : "", email = c.POC != null ? c.POC.Email : "", phone = c.POC != null ? c.POC.PhoneNumber : "" }),
|
|
category = s.WorkOrderCategories.Select(c => new { c.Id, name = c.Category != null ? c.Category.Name : "" }),
|
|
Attachments = s.workOrderAttachments.Select(a => new { a.Id, attachment = a.Attachments }),
|
|
s.BeforPhotoIssue,
|
|
s.BeforPhotoAttachment,
|
|
s.AfterPhotoIssue,
|
|
s.AfterPhotoAttachment,
|
|
s.SignOffName,
|
|
s.SignOffAttachment,
|
|
s.SignOffSignature,
|
|
Comments = s.Comments.Select(c => new { c.Id, c.CreatedDate, c.Commenttext, c.Commenter, c.RecordType, c.CommentType, c.Documents, userName = c.ApplicationUser != null ? c.ApplicationUser.FirstName : "" }),
|
|
quotes = s.Quotes.Select(q => new { code = q.QuotId, q.Id }),
|
|
auditLog = _db.WorkOrderAuditLogs
|
|
.Where(a => a.WorkOrderId == s.Id)
|
|
.OrderByDescending(a => a.CreatedAt)
|
|
.Select(a => new {
|
|
a.Id,
|
|
a.FieldName,
|
|
a.OldValue,
|
|
a.NewValue,
|
|
a.Action,
|
|
a.CreatedAt,
|
|
userName = a.User != null ? a.User.FirstName + " " + a.User.LastName : ""
|
|
}).ToList(),
|
|
dispatches = s.Dispatches.Select(d => new {
|
|
d.Id,
|
|
d.DispatchNumber,
|
|
d.PONumber,
|
|
d.NTEAmount,
|
|
d.Description,
|
|
d.Status,
|
|
d.ScheduledDate,
|
|
d.CompletedDate,
|
|
d.DispatchedAt,
|
|
d.EmailSent,
|
|
vendorName = d.Vendor != null ? d.Vendor.CompanyName : "",
|
|
vendorEmail = d.Vendor != null ? d.Vendor.Email : "",
|
|
vendorId = d.VendorId
|
|
}).ToList()
|
|
}).FirstOrDefaultAsync();
|
|
if (exist == null)
|
|
{
|
|
|
|
return BadRequest(new Response { Status = "Error", Message = "ID not found!" });
|
|
}
|
|
else
|
|
{
|
|
return Ok(exist);
|
|
}
|
|
}
|
|
[HttpGet]
|
|
[Route("GetFilteredWorkorder2")]
|
|
public IActionResult GetFilteredWorkorder2([FromQuery] string[] assignto, [FromQuery] int[]? location, [FromQuery] string[]? priority, [FromQuery] string[]? status, [FromQuery] string[]? duedate, [FromQuery] string search = "", [FromQuery] string sort = "Sort Order", [FromQuery] string sortby = "Sort By", int page = 1, int pageSize = 10)
|
|
{
|
|
try
|
|
{
|
|
var totalCount = _db.workOrders.Where(w => w.istemplate != true).Count();
|
|
if (sort == "Sort Order" && sortby == "Sort By" && assignto.Count() == 0 && (duedate == null || duedate.Count() == 0) && location.Count() == 0 && (priority == null || priority.Count() == 0) && (status == null || status.Count() == 0) && search == "")
|
|
{
|
|
var data1 = _db.workOrders.Where(w => w.istemplate != true).Include(w => w.Locations).ToListAsync();
|
|
return Ok(data1);
|
|
}
|
|
//if (sort == "Sort Order" && sortby == "Sort By" && assignto.Count() == 0 && (duedate == null || duedate.Count() == 0) && location.Count() == 0 && (priority == null || priority.Count() == 0) && (status == null || status.Count() == 0) && search == "")
|
|
//{
|
|
// var data1 = _db.workOrders.Where(w => w.istemplate != true).Include(w => w.Locations).Select(s => new {s.Id, s.WorkerOrderTitle, s.DueDate, s.Status, s.Priority, s.Locations.Name }).OrderByDescending(d => d.Name).ToList().Skip((page - 1) * pageSize)
|
|
// .Take(pageSize);
|
|
|
|
// // Calculate the number of pages
|
|
// var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
|
|
|
|
// // Create a view model containing the paged items and pagination metadata
|
|
// var viewModel1 = new Pagination_DTO
|
|
// {
|
|
// Data = data1,
|
|
// PageNumber = page,
|
|
// PageSize = pageSize,
|
|
// TotalCount = totalCount,
|
|
// TotalPages = totalPages
|
|
// };
|
|
// return Ok(viewModel1);
|
|
//}
|
|
|
|
var currentDate = DateTime.UtcNow.Date;
|
|
var data = _db.workOrders.Include(w => w.Locations)
|
|
.Where(w => w.istemplate != true && (((w.WorkerOrderTitle ?? "").ToLower().Contains(search.ToLower()) || (w.WorkerOrderNumber ?? "").ToLower().Contains(search.ToLower()) || (w.Locations != null && (w.Locations.Title ?? "").ToLower().Contains(search.ToLower()))) &&
|
|
(assignto.Count() == 0 || assignto.Contains(w.AssignTo)) &&
|
|
(location.Count() == 0 || (w.LocationId.HasValue && location.Contains(w.LocationId.Value))) &&
|
|
(priority.Count() == 0 || priority.Contains(w.Priority)) &&
|
|
((status.Count() == 0 || status.Contains(w.Status))
|
|
)))
|
|
.ToList();
|
|
|
|
// Perform client-side filtering for due dates
|
|
data = data.Where(w =>
|
|
duedate.Count() == 0 ||
|
|
duedate.Any(dd => w.DueDate.HasValue && CheckDueDate(dd, w.DueDate.Value.Date, currentDate))).ToList();
|
|
|
|
// Apply sorting
|
|
if (!string.IsNullOrEmpty(sort) && !string.IsNullOrEmpty(sortby) && sortby != "")
|
|
{
|
|
switch (sortby)
|
|
{
|
|
case "Name":
|
|
data = (sort.ToLower() == "ascending") ? data.OrderBy(w => w.WorkerOrderTitle).ToList() : data.OrderByDescending(w => w.WorkerOrderTitle).ToList();
|
|
break;
|
|
case "Due Date":
|
|
data = (sort.ToLower() == "ascending") ? data.OrderBy(w => w.DueDate).ToList() : data.OrderByDescending(w => w.DueDate).ToList();
|
|
break;
|
|
case "Severity":
|
|
data = (sort.ToLower() == "ascending") ? data.OrderBy(w => w.Priority).ToList() : data.OrderByDescending(w => w.Priority).ToList();
|
|
break;
|
|
default:
|
|
// Handle default case or throw an exception
|
|
break;
|
|
}
|
|
}
|
|
var newdata = data.Select(s => new { s.Id, s.WorkerOrderTitle, s.DueDate, s.Status, s.Priority, Name = s.Locations != null ? s.Locations.Name : "" }).OrderByDescending(d => d.Name).ToList().Skip((page - 1) * pageSize)
|
|
.Take(pageSize);
|
|
var totalPages1 = (int)Math.Ceiling(totalCount / (double)pageSize);
|
|
|
|
// Create a view model containing the paged items and pagination metadata
|
|
var viewModel = new Pagination_DTO
|
|
{
|
|
Data = newdata,
|
|
PageNumber = page,
|
|
PageSize = pageSize,
|
|
TotalCount = totalCount,
|
|
TotalPages = totalPages1
|
|
};
|
|
return Ok(viewModel);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
var totalCount = _db.workOrders.Where(w => w.istemplate != true).Count();
|
|
var data1 = _db.workOrders.Where(w => w.istemplate != true).Include(w => w.Locations).Select(s => new
|
|
{
|
|
s.Id,
|
|
s.WorkerOrderTitle,
|
|
s.DueDate,
|
|
s.Status,
|
|
s.Priority,
|
|
Name = s.Locations != null ? s.Locations.Name : ""
|
|
}).OrderByDescending(d => d.Id).ToList().Skip((page - 1) * pageSize)
|
|
.Take(pageSize);
|
|
|
|
// Calculate the number of pages
|
|
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
|
|
|
|
// Create a view model containing the paged items and pagination metadata
|
|
var viewModel = new Pagination_DTO
|
|
{
|
|
Data = data1,
|
|
PageNumber = page,
|
|
PageSize = pageSize,
|
|
TotalCount = totalCount,
|
|
TotalPages = totalPages
|
|
};
|
|
return Ok(viewModel);
|
|
}
|
|
}
|
|
bool CheckDueDate(string dd, DateTime dueDate, DateTime currentDate)
|
|
{
|
|
return dd switch
|
|
{
|
|
"Today" => dueDate == currentDate,
|
|
"Tomorrow" => dueDate == currentDate.AddDays(1),
|
|
"Next Week" => dueDate >= currentDate.AddDays(1) && dueDate <= currentDate.AddDays(7),
|
|
"Next Month" => dueDate >= currentDate.AddDays(1) && dueDate <= currentDate.AddMonths(1),
|
|
"Over Due" => dueDate < currentDate,
|
|
_ => false
|
|
};
|
|
}
|
|
|
|
[HttpPost]
|
|
[Route("ChangeStatus")]
|
|
public async Task<IActionResult> ChangeStatus(int id, string status)
|
|
{
|
|
try
|
|
{
|
|
var data = await _db.workOrders.Where(w => w.Id == id).FirstOrDefaultAsync();
|
|
if (data == null)
|
|
return BadRequest(new Response { Status = "Error", Message = "Work order not found" });
|
|
if (data.Status != status)
|
|
{
|
|
_db.WorkOrderAuditLogs.Add(new WorkOrderAuditLog
|
|
{
|
|
WorkOrderId = id,
|
|
UserId = User.FindFirstValue(ClaimTypes.NameIdentifier),
|
|
FieldName = "Status",
|
|
OldValue = data.Status ?? "",
|
|
NewValue = status,
|
|
Action = "status_change",
|
|
CreatedAt = DateTime.UtcNow
|
|
});
|
|
}
|
|
data.Status = status;
|
|
await _db.SaveChangesAsync();
|
|
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200", Data = data });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
|
}
|
|
}
|
|
|
|
[HttpPost]
|
|
[Route("ChangeAssignment")]
|
|
public async Task<IActionResult> ChangeAssignment(int id, string? assignTo)
|
|
{
|
|
try
|
|
{
|
|
var data = await _db.workOrders.Where(w => w.Id == id).FirstOrDefaultAsync();
|
|
if (data == null)
|
|
return BadRequest(new Response { Status = "Error", Message = "Work order not found" });
|
|
if (data.AssignTo != assignTo)
|
|
{
|
|
var oldUser = data.AssignTo != null ? await _db.Users.FindAsync(data.AssignTo) : null;
|
|
var newUser = assignTo != null ? await _db.Users.FindAsync(assignTo) : null;
|
|
_db.WorkOrderAuditLogs.Add(new WorkOrderAuditLog
|
|
{
|
|
WorkOrderId = id,
|
|
UserId = User.FindFirstValue(ClaimTypes.NameIdentifier),
|
|
FieldName = "AssignedTo",
|
|
OldValue = oldUser != null ? (oldUser.FirstName + " " + oldUser.LastName).Trim() : "",
|
|
NewValue = newUser != null ? (newUser.FirstName + " " + newUser.LastName).Trim() : "Unassigned",
|
|
Action = "assignment_change",
|
|
CreatedAt = DateTime.UtcNow
|
|
});
|
|
}
|
|
data.AssignTo = assignTo;
|
|
await _db.SaveChangesAsync();
|
|
|
|
var assignedUser = assignTo != null ? await _db.Users.FindAsync(assignTo) : null;
|
|
var assignedName = assignedUser != null ? (assignedUser.FirstName + " " + assignedUser.LastName).Trim() : "";
|
|
return Ok(new { message = "Updated Successfully", assignedTo = assignedName });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
|
}
|
|
}
|
|
|
|
[HttpPost]
|
|
[Route("AddComment")]
|
|
public async Task<IActionResult> AddComment([FromForm] Comments_DTO model)
|
|
{
|
|
try
|
|
{
|
|
var userId = _httpContext?.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "";
|
|
Comments comments = new Comments();
|
|
comments.CreatedDate = DateTime.Now;
|
|
comments.UserId = userId;
|
|
comments.WorkerOrderId = model.WorkorderId;
|
|
comments.Commenttext = model.Text;
|
|
comments.CommentType = model.CommentType ?? "vendor";
|
|
if (model.Document != null)
|
|
{
|
|
string fileName = ContentDispositionHeaderValue.Parse(model.Document.ContentDisposition).FileName.Trim('"');
|
|
string uniqueFileName = $"{Guid.NewGuid()}_{fileName}";
|
|
|
|
string uploadProfilePath = Path.Combine("Assets", "Documents");
|
|
string fullPath = Path.Combine(_webHostEnvironment.WebRootPath, uploadProfilePath, uniqueFileName);
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
|
|
// Copy the file to the server
|
|
using (var ms = System.IO.File.Create(fullPath))
|
|
{
|
|
await model.Document.CopyToAsync(ms);
|
|
}
|
|
// Update the user's image URL
|
|
var request = _httpContext.HttpContext.Request;
|
|
var domain = $"{request.Scheme}://{request.Host}";
|
|
comments.Documents = domain + "/" + Path.Combine(uploadProfilePath, uniqueFileName);
|
|
}
|
|
_db.Comments.Add(comments);
|
|
await _db.SaveChangesAsync();
|
|
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200", Data = comments });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
|
}
|
|
}
|
|
|
|
[HttpPost]
|
|
[Route("AddCommentJson")]
|
|
public async Task<IActionResult> AddCommentJson([FromBody] Comments_DTO model)
|
|
{
|
|
try
|
|
{
|
|
var userId = _httpContext?.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "";
|
|
var comment = new Comments
|
|
{
|
|
CreatedDate = DateTime.UtcNow,
|
|
UserId = userId,
|
|
WorkerOrderId = model.WorkorderId,
|
|
Commenttext = model.Text,
|
|
CommentType = model.CommentType ?? "internal",
|
|
};
|
|
_db.Comments.Add(comment);
|
|
await _db.SaveChangesAsync();
|
|
|
|
var user = await _db.Users.FindAsync(userId);
|
|
return Ok(new
|
|
{
|
|
comment.Id,
|
|
comment.CreatedDate,
|
|
comment.Commenttext,
|
|
comment.CommentType,
|
|
userName = user != null ? (user.FirstName + " " + user.LastName).Trim() : ""
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
|
}
|
|
}
|
|
|
|
[HttpGet]
|
|
[Route("GetComments")]
|
|
public async Task<IActionResult> GetComments()
|
|
{
|
|
var data = await _db.Comments.Include(i => i.ApplicationUser).Select(s => new
|
|
{
|
|
s.Id,
|
|
s.Commenttext,
|
|
FirstName = s.ApplicationUser != null ? s.ApplicationUser.FirstName : "",
|
|
s.Documents
|
|
}).ToListAsync();
|
|
return Ok(data);
|
|
}
|
|
|
|
[HttpGet]
|
|
[Route("GetCommentsByWorkorderId")]
|
|
public async Task<IActionResult> GetCommentsByWorkorderId(int woid)
|
|
{
|
|
var data = await _db.Comments.Where(w => w.WorkerOrderId == woid).Include(i => i.ApplicationUser).Select(s => new
|
|
{
|
|
s.Id,
|
|
s.Commenttext,
|
|
FirstName = s.ApplicationUser != null ? s.ApplicationUser.FirstName : "",
|
|
s.Documents
|
|
}).ToListAsync();
|
|
return Ok(data);
|
|
}
|
|
[HttpGet]
|
|
[Route("GetworkordersDD")]
|
|
public async Task<IActionResult> GetworkordersDD()
|
|
{
|
|
_db.ChangeTracker.Clear();
|
|
|
|
var data = await _db.workOrders.Where(w => w.istemplate != true).Include(w => w.Locations).Include(w => w.AssignToUser).ToListAsync();
|
|
return Ok(data);
|
|
}
|
|
[HttpGet]
|
|
[Route("Getworkorders")]
|
|
public async Task<IActionResult> Getworkorders()
|
|
{
|
|
_db.ChangeTracker.Clear();
|
|
var data = await _db.workOrders
|
|
.Where(w => w.istemplate != true)
|
|
.AsSplitQuery()
|
|
.Include(w => w.Locations)
|
|
.Include(w => w.AssignToUser).Select(s => new WorkorderFilterVM
|
|
{
|
|
Id = s.Id,
|
|
Priority = s.Priority,
|
|
Status = s.Status,
|
|
WorkerOrderNumber = s.WorkerOrderNumber,
|
|
WorkerOrderTitle = s.WorkerOrderTitle,
|
|
DueDate = s.DueDate,
|
|
AssignToUserName = s.AssignToUser != null ? s.AssignToUser.FirstName : "",
|
|
LocationName = s.Locations != null ? s.Locations.Name : "",
|
|
LocationId = s.LocationId,
|
|
PO = s.PO,
|
|
TT = s.TT,
|
|
}).ToListAsync();
|
|
return Ok(data);
|
|
}
|
|
[HttpPost]
|
|
[Route("DispatchToVendor")]
|
|
public async Task<IActionResult> DispatchToVendor([FromBody] Dispatch_DTO model)
|
|
{
|
|
try
|
|
{
|
|
var workOrders = await _db.workOrders.Include(w => w.Locations)
|
|
.Where(w => model.WorkOrderIds.Contains(w.Id))
|
|
.ToListAsync();
|
|
if (workOrders.Count == 0)
|
|
return BadRequest(new Response { Status = "Error", Message = "No work orders found" });
|
|
|
|
var primaryWO = workOrders.First();
|
|
|
|
var lastPO = await _db.Dispatches
|
|
.Where(d => d.PONumber != null)
|
|
.OrderByDescending(d => d.PONumber)
|
|
.Select(d => d.PONumber)
|
|
.FirstOrDefaultAsync();
|
|
int nextPO = 1;
|
|
if (lastPO != null && lastPO.StartsWith("VPO-") && int.TryParse(lastPO.Substring(4), out var lastNum))
|
|
nextPO = lastNum + 1;
|
|
|
|
var lastDSP = await _db.Dispatches
|
|
.Where(d => d.DispatchNumber != null)
|
|
.OrderByDescending(d => d.DispatchNumber)
|
|
.Select(d => d.DispatchNumber)
|
|
.FirstOrDefaultAsync();
|
|
int nextDSP = 1;
|
|
if (lastDSP != null && lastDSP.StartsWith("DSP-") && int.TryParse(lastDSP.Substring(4), out var lastDspNum))
|
|
nextDSP = lastDspNum + 1;
|
|
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
var currentUser = userId != null ? await _db.Users.FindAsync(userId) : null;
|
|
var senderName = currentUser != null ? (currentUser.FirstName + " " + currentUser.LastName).Trim() : "";
|
|
var dispatches = new List<object>();
|
|
|
|
foreach (var vendorId in model.VendorIds)
|
|
{
|
|
var vendor = await _db.Vendors.FindAsync(vendorId);
|
|
if (vendor == null) continue;
|
|
|
|
var poNumber = $"VPO-{nextPO:D5}";
|
|
nextPO++;
|
|
var dispatchNumber = $"DSP-{nextDSP:D5}";
|
|
nextDSP++;
|
|
|
|
var replyTo = $"wo-{primaryWO.InternalWONumber}-{dispatchNumber}@int.seahaven.com";
|
|
|
|
var dispatch = new Dispatch
|
|
{
|
|
WorkOrderId = primaryWO.Id,
|
|
VendorId = vendorId,
|
|
DispatchNumber = dispatchNumber,
|
|
PONumber = poNumber,
|
|
NTEAmount = model.NTEAmount,
|
|
Description = model.Description,
|
|
Status = "Sent",
|
|
ScheduledDate = model.ScheduledDate,
|
|
DispatchedAt = DateTime.UtcNow,
|
|
ReplyToAddress = replyTo,
|
|
AcceptToken = Guid.NewGuid().ToString(),
|
|
DispatchWorkOrders = workOrders.Select(wo => new DispatchWorkOrder { WorkOrderId = wo.Id }).ToList()
|
|
};
|
|
|
|
var woListHtml = string.Join("", workOrders.Select(wo =>
|
|
$"<tr><td style='padding:6px 8px;border:1px solid #ddd;'>{wo.InternalWONumber}</td>" +
|
|
$"<td style='padding:6px 8px;border:1px solid #ddd;'>{wo.WorkerOrderTitle}</td>" +
|
|
$"<td style='padding:6px 8px;border:1px solid #ddd;'>{wo.Priority}</td>" +
|
|
$"<td style='padding:6px 8px;border:1px solid #ddd;'>{wo.DueDate?.ToString("yyyy-MM-dd") ?? "N/A"}</td></tr>"));
|
|
|
|
var locationName = primaryWO.Locations?.Name ?? "";
|
|
var locationAddr = primaryWO.Locations?.Address ?? "";
|
|
var subject = $"[{dispatchNumber}] {(workOrders.Count > 1 ? $"{workOrders.Count} Work Orders" : primaryWO.WorkerOrderTitle)} at {locationName}";
|
|
|
|
var html = $@"
|
|
<h2>Work Order Dispatch — {dispatchNumber}</h2>
|
|
<p><strong>Location:</strong> {locationName} {locationAddr}</p>
|
|
<p><strong>Vendor PO:</strong> {poNumber} <strong>NTE:</strong> ${model.NTEAmount?.ToString("F2") ?? "N/A"}</p>
|
|
<h3>Work Orders</h3>
|
|
<table style='border-collapse:collapse;width:100%;font-family:Arial,sans-serif;'>
|
|
<tr style='background:#f5f5f5;'>
|
|
<th style='padding:6px 8px;border:1px solid #ddd;text-align:left;'>WO #</th>
|
|
<th style='padding:6px 8px;border:1px solid #ddd;text-align:left;'>Title</th>
|
|
<th style='padding:6px 8px;border:1px solid #ddd;text-align:left;'>Priority</th>
|
|
<th style='padding:6px 8px;border:1px solid #ddd;text-align:left;'>Due Date</th>
|
|
</tr>
|
|
{woListHtml}
|
|
</table>
|
|
<h3>Description</h3>
|
|
<p>{model.Description ?? primaryWO.Description}</p>
|
|
<br/><p style='color:#666;font-size:13px;'>{senderName}<br/>Sea Haven Industries</p>
|
|
<hr/><p style='color:#999;font-size:12px;'>Reply to this email to communicate about this dispatch.</p>";
|
|
|
|
var emailSent = await _sendMessage.SendDispatchEmail(vendor.Email, subject, html, replyTo);
|
|
dispatch.EmailSent = emailSent;
|
|
|
|
_db.Dispatches.Add(dispatch);
|
|
|
|
foreach (var wo in workOrders)
|
|
{
|
|
_db.WorkOrderAuditLogs.Add(new WorkOrderAuditLog
|
|
{
|
|
WorkOrderId = wo.Id,
|
|
UserId = userId,
|
|
FieldName = "Dispatch",
|
|
OldValue = "",
|
|
NewValue = $"{vendor.CompanyName} ({dispatchNumber})",
|
|
Action = "dispatch",
|
|
CreatedAt = DateTime.UtcNow
|
|
});
|
|
}
|
|
|
|
dispatches.Add(new { dispatch.Id, dispatchNumber, poNumber, vendorName = vendor.CompanyName, dispatch.Status, workOrderCount = workOrders.Count });
|
|
}
|
|
|
|
await _db.SaveChangesAsync();
|
|
return Ok(new { message = "Dispatched", dispatches });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
|
}
|
|
}
|
|
|
|
[HttpGet("GetDispatches/{workOrderId}")]
|
|
public async Task<IActionResult> GetDispatches(int workOrderId)
|
|
{
|
|
var dispatchIds = await _db.DispatchWorkOrders
|
|
.Where(dwo => dwo.WorkOrderId == workOrderId)
|
|
.Select(dwo => dwo.DispatchId)
|
|
.Distinct()
|
|
.ToListAsync();
|
|
|
|
var directIds = await _db.Dispatches
|
|
.Where(d => d.WorkOrderId == workOrderId)
|
|
.Select(d => d.Id)
|
|
.ToListAsync();
|
|
|
|
var allIds = dispatchIds.Union(directIds).Distinct().ToList();
|
|
|
|
var data = await _db.Dispatches
|
|
.Where(d => allIds.Contains(d.Id))
|
|
.Include(d => d.Vendor)
|
|
.OrderByDescending(d => d.DispatchedAt)
|
|
.Select(d => new
|
|
{
|
|
d.Id,
|
|
d.DispatchNumber,
|
|
d.PONumber,
|
|
d.NTEAmount,
|
|
d.Description,
|
|
d.Status,
|
|
d.ScheduledDate,
|
|
d.CompletedDate,
|
|
d.DispatchedAt,
|
|
d.EmailSent,
|
|
vendorName = d.Vendor != null ? d.Vendor.CompanyName : "",
|
|
vendorEmail = d.Vendor != null ? d.Vendor.Email : "",
|
|
vendorId = d.VendorId
|
|
})
|
|
.ToListAsync();
|
|
return Ok(data);
|
|
}
|
|
|
|
[HttpGet("GetDispatch/{id}")]
|
|
public async Task<IActionResult> GetDispatchById(int id)
|
|
{
|
|
var d = await _db.Dispatches
|
|
.Include(x => x.Vendor)
|
|
.Include(x => x.WorkOrder)
|
|
.Where(x => x.Id == id)
|
|
.Select(x => new
|
|
{
|
|
x.Id,
|
|
x.DispatchNumber,
|
|
x.PONumber,
|
|
x.NTEAmount,
|
|
x.Description,
|
|
x.Status,
|
|
x.ScheduledDate,
|
|
x.CompletedDate,
|
|
x.DispatchedAt,
|
|
x.EmailSent,
|
|
x.WorkOrderId,
|
|
x.AcknowledgedAt,
|
|
woNumber = x.WorkOrder != null ? x.WorkOrder.InternalWONumber : "",
|
|
woTitle = x.WorkOrder != null ? x.WorkOrder.WorkerOrderTitle : "",
|
|
vendorName = x.Vendor != null ? x.Vendor.CompanyName : "",
|
|
vendorEmail = x.Vendor != null ? x.Vendor.Email : "",
|
|
vendorId = x.VendorId,
|
|
workOrders = _db.DispatchWorkOrders
|
|
.Where(dwo => dwo.DispatchId == x.Id)
|
|
.Select(dwo => new {
|
|
dwo.WorkOrderId,
|
|
woNumber = dwo.WorkOrder != null ? dwo.WorkOrder.InternalWONumber : "",
|
|
woTitle = dwo.WorkOrder != null ? dwo.WorkOrder.WorkerOrderTitle : ""
|
|
}).ToList(),
|
|
comments = _db.Comments
|
|
.Where(c => c.DispatchId == x.Id)
|
|
.OrderBy(c => c.CreatedDate)
|
|
.Select(c => new
|
|
{
|
|
c.Id,
|
|
c.CreatedDate,
|
|
c.Commenttext,
|
|
c.Commenter,
|
|
c.CommentType,
|
|
userName = c.ApplicationUser != null ? c.ApplicationUser.FirstName + " " + c.ApplicationUser.LastName : ""
|
|
}).ToList()
|
|
}).FirstOrDefaultAsync();
|
|
|
|
if (d == null)
|
|
return NotFound(new Response { Status = "Error", Message = "Dispatch not found" });
|
|
return Ok(d);
|
|
}
|
|
|
|
[HttpPost("UpdateDispatch")]
|
|
public async Task<IActionResult> UpdateDispatch([FromBody] UpdateDispatch_DTO model)
|
|
{
|
|
try
|
|
{
|
|
var dispatch = await _db.Dispatches.FindAsync(model.Id);
|
|
if (dispatch == null)
|
|
return BadRequest(new Response { Status = "Error", Message = "Dispatch not found" });
|
|
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
|
|
if (dispatch.Status != model.Status)
|
|
{
|
|
_db.WorkOrderAuditLogs.Add(new WorkOrderAuditLog
|
|
{
|
|
WorkOrderId = dispatch.WorkOrderId ?? 0,
|
|
UserId = userId,
|
|
FieldName = $"Dispatch {dispatch.DispatchNumber} Status",
|
|
OldValue = dispatch.Status ?? "",
|
|
NewValue = model.Status ?? "",
|
|
Action = "dispatch_status_change",
|
|
CreatedAt = DateTime.UtcNow
|
|
});
|
|
}
|
|
|
|
dispatch.Status = model.Status;
|
|
dispatch.NTEAmount = model.NTEAmount;
|
|
dispatch.ScheduledDate = model.ScheduledDate;
|
|
dispatch.CompletedDate = model.CompletedDate;
|
|
dispatch.Description = model.Description;
|
|
|
|
if (model.Status == "Completed" && dispatch.CompletedDate == null)
|
|
dispatch.CompletedDate = DateTime.UtcNow;
|
|
|
|
await _db.SaveChangesAsync();
|
|
return Ok(new { message = "Dispatch updated" });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
|
}
|
|
}
|
|
|
|
[HttpPost("AddDispatchComment")]
|
|
public async Task<IActionResult> AddDispatchComment([FromBody] DispatchComment_DTO model)
|
|
{
|
|
try
|
|
{
|
|
var dispatch = await _db.Dispatches.Include(d => d.Vendor).Include(d => d.WorkOrder)
|
|
.FirstOrDefaultAsync(d => d.Id == model.DispatchId);
|
|
if (dispatch == null)
|
|
return BadRequest(new Response { Status = "Error", Message = "Dispatch not found" });
|
|
|
|
var userId = _httpContext?.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "";
|
|
|
|
var comment = new Comments
|
|
{
|
|
CreatedDate = DateTime.UtcNow,
|
|
UserId = userId,
|
|
WorkerOrderId = dispatch.WorkOrderId,
|
|
DispatchId = dispatch.Id,
|
|
Commenttext = model.Text,
|
|
CommentType = "vendor",
|
|
};
|
|
_db.Comments.Add(comment);
|
|
await _db.SaveChangesAsync();
|
|
|
|
var user = await _db.Users.FindAsync(userId);
|
|
var senderName = user != null ? (user.FirstName + " " + user.LastName).Trim() : "";
|
|
|
|
if (model.SendEmail && dispatch.Vendor?.Email != null && dispatch.WorkOrder != null)
|
|
{
|
|
var subject = $"[WO-{dispatch.WorkOrder.InternalWONumber}-{dispatch.DispatchNumber}] {dispatch.WorkOrder.WorkerOrderTitle}";
|
|
var html = $"<p>{model.Text}</p><br/><p style='color:#666;font-size:13px;'>{senderName}<br/>Sea Haven Industries</p><hr/><p style='color:#999;font-size:12px;'>Reply to this email to respond.</p>";
|
|
await _sendMessage.SendDispatchEmail(dispatch.Vendor.Email, subject, html, dispatch.ReplyToAddress);
|
|
}
|
|
return Ok(new
|
|
{
|
|
comment.Id,
|
|
comment.CreatedDate,
|
|
comment.Commenttext,
|
|
comment.CommentType,
|
|
userName = user != null ? (user.FirstName + " " + user.LastName).Trim() : ""
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
|
}
|
|
}
|
|
|
|
private static string? ExtractZip(string? address)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(address)) return null;
|
|
var match = System.Text.RegularExpressions.Regex.Match(address, @"\b(\d{5})(?:-\d{4})?\b");
|
|
return match.Success ? match.Groups[1].Value : null;
|
|
}
|
|
|
|
private async Task<string> GenerateInternalWONumber()
|
|
{
|
|
var lastWO = await _db.workOrders
|
|
.Where(w => w.InternalWONumber != null)
|
|
.OrderByDescending(w => w.InternalWONumber)
|
|
.Select(w => w.InternalWONumber)
|
|
.FirstOrDefaultAsync();
|
|
|
|
int next = 10000001;
|
|
if (lastWO != null && int.TryParse(lastWO, out var lastNum))
|
|
next = lastNum + 1;
|
|
|
|
return next.ToString("D8");
|
|
}
|
|
|
|
public class WorkorderFilterVM
|
|
{
|
|
public int? Id { get; set; }
|
|
public int? LocationId { get; set; }
|
|
public string? WorkerOrderNumber { get; set; }
|
|
public string? BillingAddress { get; set; } = "Amazon.com Services LLC 410 Terry Ave,North Seattle, Washington, 98109";
|
|
|
|
public string? WorkerOrderTitle { get; set; }
|
|
public string? LocationName { get; set; }
|
|
public string? AssignToUserName { get; set; }
|
|
public string? Priority { get; set; }
|
|
public string? Status { get; set; }
|
|
public DateTime? DueDate { get; set; }
|
|
public string FormattedDueDate
|
|
{
|
|
get { return DueDate.HasValue ? DueDate.Value.ToString("MM/dd/yyyy") : ""; }
|
|
}
|
|
public string? Dispatcher { get; set; }
|
|
public string? Scheduler { get; set; }
|
|
public string? PO { get; set; }
|
|
public string? TT { get; set; }
|
|
}
|
|
}
|
|
}
|