mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 07:13:12 +00:00
Pass ClaimsPrincipal into GetCommentsAsync and filter via GetAllForAccountAsync so account-scoped callers cannot enumerate cross-tenant comments. ADR + cross-account tests updated.
648 lines
28 KiB
C#
648 lines
28 KiB
C#
using System.Security.Claims;
|
|
using Data.SeaHavenIndustries;
|
|
using Data.SeaHavenIndustries.Enums;
|
|
using FluentValidation;
|
|
using SeaHaven.DataServices.Interfaces;
|
|
using SeaHaven.DataServices.Models;
|
|
using SeaHaven.Services.DTOs;
|
|
using SeaHaven.Services.Exceptions;
|
|
using SeaHaven.Services.Interfaces;
|
|
using SeaHaven.Services.Validation;
|
|
|
|
namespace SeaHaven.Services.Implementation
|
|
{
|
|
public class WorkOrderService : IWorkOrderService
|
|
{
|
|
private readonly IWorkOrderDataService _workOrderDataService;
|
|
private readonly ICommentDataService _commentDataService;
|
|
private readonly IUserDataService _userDataService;
|
|
private readonly IQuotesDataService _quotesDataService;
|
|
private readonly IFileStoragePort _fileStorage;
|
|
private readonly ICreateWorkOrderValidation _createValidator;
|
|
private readonly IUpdateWorkOrderValidation _updateValidator;
|
|
private readonly IWorkOrderAccountResolver _accountResolver;
|
|
|
|
public WorkOrderService(
|
|
IWorkOrderDataService workOrderDataService,
|
|
ICommentDataService commentDataService,
|
|
IUserDataService userDataService,
|
|
IQuotesDataService quotesDataService,
|
|
IFileStoragePort fileStorage,
|
|
ICreateWorkOrderValidation createValidator,
|
|
IUpdateWorkOrderValidation updateValidator,
|
|
IWorkOrderAccountResolver accountResolver)
|
|
{
|
|
_workOrderDataService = workOrderDataService;
|
|
_commentDataService = commentDataService;
|
|
_userDataService = userDataService;
|
|
_quotesDataService = quotesDataService;
|
|
_fileStorage = fileStorage;
|
|
_createValidator = createValidator;
|
|
_updateValidator = updateValidator;
|
|
_accountResolver = accountResolver;
|
|
}
|
|
|
|
public async Task<WorkOrderDTO?> GetWorkOrderByIdAsync(int id)
|
|
{
|
|
var workOrder = await _workOrderDataService.GetByIdAsync(id);
|
|
return workOrder == null ? null : WorkOrderDTO.MapToModel(workOrder);
|
|
}
|
|
|
|
public async Task<WorkOrderDTO?> GetWorkOrderByIdWithDetailsAsync(int id)
|
|
{
|
|
var workOrder = await _workOrderDataService.GetByIdWithDetailsAsync(id);
|
|
return workOrder == null ? null : WorkOrderDTO.MapToModel(workOrder);
|
|
}
|
|
|
|
public async Task<IEnumerable<WorkOrderDTO>> GetAllWorkOrdersAsync()
|
|
{
|
|
var workOrders = await _workOrderDataService.GetAllAsync();
|
|
return workOrders.Select(WorkOrderDTO.MapToModel);
|
|
}
|
|
|
|
public async Task<PagedResult<WorkOrderDTO>> GetWorkOrdersPagedAsync(int page, int pageSize, string? search = null, string? status = null, string? priority = null)
|
|
{
|
|
var (items, totalCount) = await _workOrderDataService.GetPagedAsync(page, pageSize, search, status, priority);
|
|
return new PagedResult<WorkOrderDTO>
|
|
{
|
|
Items = items.Select(WorkOrderDTO.MapToModel),
|
|
TotalCount = totalCount,
|
|
Page = page,
|
|
PageSize = pageSize
|
|
};
|
|
}
|
|
|
|
public async Task<int> CreateWorkOrderAsync(CreateWorkOrderDTO dto, ClaimsPrincipal user, string userId)
|
|
{
|
|
var validationResult = await _createValidator.ValidateAsync(dto);
|
|
if (!validationResult.IsValid)
|
|
throw new ValidationException(validationResult.Errors);
|
|
|
|
var accountId = await _accountResolver.ResolveForAuthenticatedCreateAsync(
|
|
user, dto.Customer, CancellationToken.None);
|
|
|
|
var workOrder = CreateWorkOrderDTO.MapToEntity(dto);
|
|
workOrder.AccountId = accountId;
|
|
workOrder.createdby = userId;
|
|
workOrder.CreatedDate = DateTime.UtcNow;
|
|
workOrder.InternalWONumber = await _workOrderDataService.GenerateNextInternalWONumberAsync();
|
|
|
|
var saved = await _workOrderDataService.AddAsync(workOrder);
|
|
await LogAuditAsync(saved.Id, userId, AuditAction.Create, "Work order created");
|
|
return saved.Id;
|
|
}
|
|
|
|
public async Task<bool> UpdateWorkOrderAsync(int id, UpdateWorkOrderDTO dto, string userId)
|
|
{
|
|
var validationResult = await _updateValidator.ValidateAsync(dto);
|
|
if (!validationResult.IsValid)
|
|
throw new ValidationException(validationResult.Errors);
|
|
|
|
var existingWorkOrder = await _workOrderDataService.GetByIdAsync(id);
|
|
if (existingWorkOrder == null)
|
|
return false;
|
|
|
|
var changes = new List<string>();
|
|
changes.AddRange(TrackChanges(existingWorkOrder, dto));
|
|
|
|
UpdateWorkOrderDTO.UpdateEntity(dto, existingWorkOrder);
|
|
await _workOrderDataService.UpdateAsync(existingWorkOrder);
|
|
|
|
if (changes.Any())
|
|
await LogAuditAsync(id, userId, AuditAction.Edit, string.Join(", ", changes));
|
|
|
|
return true;
|
|
}
|
|
|
|
public async Task<bool> DeleteWorkOrderAsync(int id, string userId)
|
|
{
|
|
if (!await _workOrderDataService.ExistsAsync(id))
|
|
return false;
|
|
|
|
await _workOrderDataService.DeleteWorkOrderAttachmentsAsync(id);
|
|
await _workOrderDataService.DeleteWorkOrderContactsAsync(id);
|
|
await _workOrderDataService.DeleteWorkOrderCategoriesAsync(id);
|
|
await _workOrderDataService.DeleteAsync(id);
|
|
await LogAuditAsync(id, userId, AuditAction.Delete, "Work order deleted");
|
|
return true;
|
|
}
|
|
|
|
public async Task<bool> WorkOrderExistsAsync(int id) => await _workOrderDataService.ExistsAsync(id);
|
|
public async Task<string> GenerateNextInternalWONumberAsync() => await _workOrderDataService.GenerateNextInternalWONumberAsync();
|
|
public async Task<int> GetTotalWorkOrderCountAsync() => await _workOrderDataService.CountAsync();
|
|
|
|
public async Task<(IEnumerable<WorkOrderListItemReadModel> Items, int TotalCount)> GetWorkOrderListPagedAsync(
|
|
int page, int pageSize, string? search, string? status, string? assignee,
|
|
int? locationId, string? sortBy, string? sortDir, int? accountId = null)
|
|
{
|
|
return await _workOrderDataService.GetWorkOrderListPagedAsync(
|
|
page, pageSize, search, status, assignee, locationId, sortBy, sortDir, accountId);
|
|
}
|
|
|
|
public async Task<WorkOrderDetailReadModel?> GetWorkOrderDetailAsync(int id, int? accountId = null)
|
|
{
|
|
return await _workOrderDataService.GetWorkOrderDetailAsync(id, accountId);
|
|
}
|
|
|
|
public async Task<FilteredWorkOrderPageDTO> GetFilteredWorkOrdersAsync(
|
|
string[] assignto, int[]? location, string[]? priority,
|
|
string[]? status, string[]? duedate, string search,
|
|
string sort, string sortby, int page, int pageSize, int? accountId = null)
|
|
{
|
|
var allWorkOrders = await _workOrderDataService.GetNonTemplateWorkOrdersWithLocationsAsync(accountId);
|
|
|
|
var data = allWorkOrders.AsEnumerable();
|
|
|
|
if (!string.IsNullOrEmpty(search))
|
|
{
|
|
var s = search.ToLower();
|
|
data = data.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 && assignto[0] != "" && assignto[0] != null)
|
|
data = data.Where(w => assignto.Contains(w.AssignTo));
|
|
|
|
if (location != null && location.Length > 0 && location[0] != 0)
|
|
data = data.Where(w => location.Contains(w.LocationId ?? 0));
|
|
|
|
if (priority != null && priority.Length > 0 && priority[0] != "" && priority[0] != null)
|
|
data = data.Where(w => priority.Contains(w.Priority));
|
|
|
|
if (status != null && status.Length > 0 && status[0] != "" && status[0] != null)
|
|
data = data.Where(w => status.Contains(w.Status));
|
|
|
|
var dataList = data.ToList();
|
|
|
|
dataList = dataList.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();
|
|
|
|
if (!string.IsNullOrEmpty(sort) && !string.IsNullOrEmpty(sortby) && sortby != "")
|
|
{
|
|
switch (sortby)
|
|
{
|
|
case "Name":
|
|
dataList = sort.ToLower() == "ascending"
|
|
? dataList.OrderBy(w => w.WorkerOrderTitle).ToList()
|
|
: dataList.OrderByDescending(w => w.WorkerOrderTitle).ToList();
|
|
break;
|
|
case "Due Date":
|
|
dataList = sort.ToLower() == "ascending"
|
|
? dataList.OrderBy(w => w.DueDate).ToList()
|
|
: dataList.OrderByDescending(w => w.DueDate).ToList();
|
|
break;
|
|
case "Severity":
|
|
dataList = sort.ToLower() == "ascending"
|
|
? dataList.OrderBy(w => w.Priority).ToList()
|
|
: dataList.OrderByDescending(w => w.Priority).ToList();
|
|
break;
|
|
}
|
|
}
|
|
|
|
var totalCount = dataList.Count;
|
|
var pagedData = dataList
|
|
.Skip((page - 1) * pageSize)
|
|
.Take(pageSize)
|
|
.Select(s => new FilteredWorkOrderReadModel
|
|
{
|
|
Id = s.Id,
|
|
WorkerOrderTitle = s.WorkerOrderTitle,
|
|
DueDate = s.DueDate,
|
|
Status = s.Status,
|
|
Priority = s.Priority,
|
|
Name = s.Locations != null ? s.Locations.Name : ""
|
|
})
|
|
.OrderByDescending(d => d.Name)
|
|
.ToList();
|
|
|
|
return new FilteredWorkOrderPageDTO
|
|
{
|
|
Data = pagedData,
|
|
PageNumber = page,
|
|
PageSize = pageSize,
|
|
TotalCount = totalCount,
|
|
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
|
|
};
|
|
}
|
|
|
|
public async Task<FilteredWorkOrderPageDTO> GetFilteredWorkOrders2Async(
|
|
string[] assignto, int[]? location, string[]? priority,
|
|
string[]? status, string[]? duedate, string search,
|
|
string sort, string sortby, int page, int pageSize, int? accountId = null)
|
|
{
|
|
location ??= Array.Empty<int>();
|
|
priority ??= Array.Empty<string>();
|
|
status ??= Array.Empty<string>();
|
|
duedate ??= Array.Empty<string>();
|
|
search ??= string.Empty;
|
|
|
|
var allWorkOrders = await _workOrderDataService.GetNonTemplateWorkOrdersWithLocationsAsync(accountId);
|
|
var totalCount = allWorkOrders.Count;
|
|
|
|
var currentDate = DateTime.UtcNow.Date;
|
|
var data = allWorkOrders.Where(w =>
|
|
(((w.WorkerOrderTitle ?? "").Contains(search, StringComparison.OrdinalIgnoreCase) ||
|
|
(w.WorkerOrderNumber ?? "").Contains(search, StringComparison.OrdinalIgnoreCase) ||
|
|
(w.Locations != null &&
|
|
(w.Locations.Title ?? "").Contains(search, StringComparison.OrdinalIgnoreCase))) &&
|
|
(assignto.Length == 0 || assignto.Contains(w.AssignTo)) &&
|
|
(location.Length == 0 || (w.LocationId.HasValue && location.Contains(w.LocationId.Value))) &&
|
|
(priority.Length == 0 || priority.Contains(w.Priority)) &&
|
|
(status.Length == 0 || status.Contains(w.Status)))).ToList();
|
|
|
|
data = data.Where(w =>
|
|
duedate.Length == 0 ||
|
|
duedate.Any(dd => w.DueDate.HasValue && CheckDueDate(dd, w.DueDate.Value.Date, currentDate))).ToList();
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
var newdata = data
|
|
.Select(s => new FilteredWorkOrderReadModel
|
|
{
|
|
Id = s.Id,
|
|
WorkerOrderTitle = s.WorkerOrderTitle,
|
|
DueDate = s.DueDate,
|
|
Status = s.Status,
|
|
Priority = s.Priority,
|
|
Name = s.Locations != null ? s.Locations.Name : ""
|
|
})
|
|
.OrderByDescending(d => d.Name)
|
|
.ToList()
|
|
.Skip((page - 1) * pageSize)
|
|
.Take(pageSize)
|
|
.ToList();
|
|
|
|
return new FilteredWorkOrderPageDTO
|
|
{
|
|
Data = newdata,
|
|
PageNumber = page,
|
|
PageSize = pageSize,
|
|
TotalCount = totalCount,
|
|
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
|
|
};
|
|
}
|
|
|
|
public async Task<WorkOrderDTO?> ChangeStatusAsync(int id, string status, string userId)
|
|
{
|
|
var workOrder = await _workOrderDataService.GetByIdAsync(id);
|
|
if (workOrder == null) return null;
|
|
|
|
if (workOrder.Status != status)
|
|
{
|
|
await _workOrderDataService.AddAuditLogAsync(new WorkOrderAuditLog
|
|
{
|
|
WorkOrderId = id,
|
|
UserId = userId,
|
|
FieldName = "Status",
|
|
OldValue = workOrder.Status ?? "",
|
|
NewValue = status,
|
|
Action = AuditAction.StatusChange.ToString(),
|
|
CreatedAt = DateTime.UtcNow
|
|
});
|
|
}
|
|
|
|
workOrder.Status = status;
|
|
await _workOrderDataService.UpdateAsync(workOrder);
|
|
|
|
return WorkOrderDTO.MapToModel(workOrder);
|
|
}
|
|
|
|
public async Task<ChangeAssignmentResult?> ChangeAssignmentAsync(int id, string? assignTo, string userId)
|
|
{
|
|
var workOrder = await _workOrderDataService.GetByIdAsync(id);
|
|
if (workOrder == null) return null;
|
|
|
|
if (workOrder.AssignTo != assignTo)
|
|
{
|
|
var oldUser = workOrder.AssignTo != null ? await _userDataService.GetByIdAsync(workOrder.AssignTo) : null;
|
|
var newUser = assignTo != null ? await _userDataService.GetByIdAsync(assignTo) : null;
|
|
|
|
await _workOrderDataService.AddAuditLogAsync(new WorkOrderAuditLog
|
|
{
|
|
WorkOrderId = id,
|
|
UserId = userId,
|
|
FieldName = "AssignedTo",
|
|
OldValue = oldUser != null ? (oldUser.FirstName + " " + oldUser.LastName).Trim() : "",
|
|
NewValue = newUser != null ? (newUser.FirstName + " " + newUser.LastName).Trim() : "Unassigned",
|
|
Action = AuditAction.AssignmentChange.ToString(),
|
|
CreatedAt = DateTime.UtcNow
|
|
});
|
|
}
|
|
|
|
workOrder.AssignTo = assignTo;
|
|
await _workOrderDataService.UpdateAsync(workOrder);
|
|
|
|
var assignedUser = assignTo != null ? await _userDataService.GetByIdAsync(assignTo) : null;
|
|
return new ChangeAssignmentResult
|
|
{
|
|
Message = "Updated Successfully",
|
|
AssignedTo = assignedUser != null ? (assignedUser.FirstName + " " + assignedUser.LastName).Trim() : ""
|
|
};
|
|
}
|
|
|
|
public async Task<int> CreateWorkOrderWithDetailsAsync(
|
|
CreateWorkOrderWithDetailsInput input,
|
|
ClaimsPrincipal user,
|
|
string userId)
|
|
{
|
|
var createDto = new CreateWorkOrderDTO
|
|
{
|
|
WorkerOrderTitle = input.WorkerOrderTitle,
|
|
Description = input.Description,
|
|
Status = WorkOrderStatus.Open,
|
|
Priority = Enum.TryParse<WorkOrderPriority>(input.Priority, out var p) ? p : WorkOrderPriority.Medium,
|
|
LocationId = input.LocationId,
|
|
AssignTo = input.AssignTo,
|
|
DueDate = input.DueDate,
|
|
Customer = input.Customer
|
|
};
|
|
|
|
var workOrderId = await CreateWorkOrderAsync(createDto, user, userId);
|
|
|
|
if (input.ContactIds != null && input.ContactIds.Any())
|
|
await _workOrderDataService.AddWorkOrderContactsAsync(workOrderId, input.ContactIds);
|
|
|
|
if (input.CategoryIds != null && input.CategoryIds.Any())
|
|
await _workOrderDataService.AddWorkOrderCategoriesAsync(workOrderId, input.CategoryIds);
|
|
|
|
await SaveAttachmentsAndPhotos(workOrderId, input.Attachments, input.BeforPhotoAttachment,
|
|
input.AfterPhotoAttachment, input.SignOffAttachment);
|
|
|
|
return workOrderId;
|
|
}
|
|
|
|
public async Task<bool> UpdateWorkOrderWithDetailsAsync(UpdateWorkOrderWithDetailsInput input, string userId)
|
|
{
|
|
var updateDto = new UpdateWorkOrderDTO
|
|
{
|
|
WorkerOrderTitle = input.WorkerOrderTitle,
|
|
Description = input.Description,
|
|
Status = Enum.TryParse<WorkOrderStatus>(input.Status, out var st) ? st : null,
|
|
Priority = Enum.TryParse<WorkOrderPriority>(input.Priority, out var pr) ? pr : null,
|
|
LocationId = input.LocationId,
|
|
AssignTo = input.AssignTo,
|
|
DueDate = input.DueDate,
|
|
CompletedDate = input.CompletedDate
|
|
};
|
|
|
|
var success = await UpdateWorkOrderAsync(input.Id, updateDto, userId);
|
|
if (!success) return false;
|
|
|
|
await _workOrderDataService.DeleteWorkOrderContactsAsync(input.Id);
|
|
await _workOrderDataService.DeleteWorkOrderCategoriesAsync(input.Id);
|
|
|
|
if (input.ContactIds != null && input.ContactIds.Any())
|
|
await _workOrderDataService.AddWorkOrderContactsAsync(input.Id, input.ContactIds);
|
|
|
|
if (input.CategoryIds != null && input.CategoryIds.Any())
|
|
await _workOrderDataService.AddWorkOrderCategoriesAsync(input.Id, input.CategoryIds);
|
|
|
|
await SaveAttachmentsAndPhotos(input.Id, input.Attachments, input.BeforPhotoAttachment,
|
|
input.AfterPhotoAttachment, input.SignOffAttachment);
|
|
|
|
return true;
|
|
}
|
|
|
|
public async Task<bool> DeleteWorkOrderCascadeAsync(int id, string userId)
|
|
{
|
|
var success = await DeleteWorkOrderAsync(id, userId);
|
|
if (!success) return false;
|
|
|
|
await _quotesDataService.UnlinkFromWorkOrderAsync(id);
|
|
|
|
var comments = await _commentDataService.GetByWorkOrderIdAsync(id);
|
|
foreach (var comment in comments)
|
|
await _commentDataService.DeleteAsync(comment.Id);
|
|
|
|
return true;
|
|
}
|
|
|
|
public async Task<Comments> AddCommentAsync(AddCommentInput input, ClaimsPrincipal user, string userId)
|
|
{
|
|
await EnsureWorkOrderInAccountScopeAsync(input.WorkorderId, user);
|
|
|
|
var comment = new Comments
|
|
{
|
|
UserId = userId,
|
|
WorkerOrderId = input.WorkorderId,
|
|
Commenttext = input.Text,
|
|
CommentType = input.CommentType ?? "vendor"
|
|
};
|
|
|
|
if (input.Document != null)
|
|
{
|
|
var fileUrl = await _fileStorage.SaveFileAsync(input.Document);
|
|
comment.Documents = fileUrl;
|
|
}
|
|
|
|
return await _commentDataService.AddAsync(comment);
|
|
}
|
|
|
|
public async Task<CommentResult> AddCommentJsonAsync(AddCommentInput input, ClaimsPrincipal user, string userId)
|
|
{
|
|
await EnsureWorkOrderInAccountScopeAsync(input.WorkorderId, user);
|
|
|
|
var comment = new Comments
|
|
{
|
|
UserId = userId,
|
|
WorkerOrderId = input.WorkorderId,
|
|
Commenttext = input.Text,
|
|
CommentType = input.CommentType ?? "internal"
|
|
};
|
|
|
|
var saved = await _commentDataService.AddAsync(comment);
|
|
var displayUser = await _userDataService.GetByIdAsync(userId);
|
|
|
|
return new CommentResult
|
|
{
|
|
Id = saved.Id,
|
|
CreatedDate = saved.CreatedDate,
|
|
Commenttext = saved.Commenttext,
|
|
CommentType = saved.CommentType,
|
|
UserName = displayUser != null ? (displayUser.FirstName + " " + displayUser.LastName).Trim() : ""
|
|
};
|
|
}
|
|
|
|
public async Task<IEnumerable<CommentListItemReadModel>> GetCommentsAsync(ClaimsPrincipal user)
|
|
{
|
|
var accountId = _accountResolver.ResolveAccountFilter(user);
|
|
var comments = await _commentDataService.GetAllForAccountAsync(accountId);
|
|
return comments.Select(s => new CommentListItemReadModel
|
|
{
|
|
Id = s.Id,
|
|
Commenttext = s.Commenttext,
|
|
FirstName = s.ApplicationUser != null ? s.ApplicationUser.FirstName : "",
|
|
Documents = s.Documents
|
|
}).ToList();
|
|
}
|
|
|
|
public async Task<IEnumerable<CommentListItemReadModel>?> GetCommentsByWorkorderIdAsync(
|
|
int woid,
|
|
ClaimsPrincipal user)
|
|
{
|
|
var accountId = _accountResolver.ResolveAccountFilter(user);
|
|
var workOrder = await _workOrderDataService.GetWorkOrderDetailAsync(woid, accountId);
|
|
if (workOrder == null)
|
|
return null;
|
|
|
|
var comments = await _commentDataService.GetByWorkOrderIdAsync(woid);
|
|
return comments.Select(s => new CommentListItemReadModel
|
|
{
|
|
Id = s.Id,
|
|
Commenttext = s.Commenttext,
|
|
FirstName = s.ApplicationUser != null ? s.ApplicationUser.FirstName : "",
|
|
Documents = s.Documents
|
|
}).ToList();
|
|
}
|
|
|
|
private async Task EnsureWorkOrderInAccountScopeAsync(int workOrderId, ClaimsPrincipal user)
|
|
{
|
|
var accountId = _accountResolver.ResolveAccountFilter(user);
|
|
var workOrder = await _workOrderDataService.GetWorkOrderDetailAsync(workOrderId, accountId);
|
|
if (workOrder == null)
|
|
throw new WorkOrderBoardValidationException("NotFound", "Work order not found.");
|
|
}
|
|
|
|
public async Task<IEnumerable<WorkOrder>> GetWorkordersDDAsync(int? accountId = null)
|
|
{
|
|
var data = await _workOrderDataService.GetNonTemplateWorkOrdersWithLocationsAsync(accountId);
|
|
return data;
|
|
}
|
|
|
|
public async Task<IEnumerable<WorkorderFilterVM>> GetWorkordersAsync(int? accountId = null)
|
|
{
|
|
var workOrders = await _workOrderDataService.GetAllWithDetailsAsync(accountId);
|
|
return workOrders.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
|
|
}).ToList();
|
|
}
|
|
|
|
private async Task SaveAttachmentsAndPhotos(int workOrderId,
|
|
List<Microsoft.AspNetCore.Http.IFormFile>? attachments,
|
|
Microsoft.AspNetCore.Http.IFormFile? beforPhoto,
|
|
Microsoft.AspNetCore.Http.IFormFile? afterPhoto,
|
|
Microsoft.AspNetCore.Http.IFormFile? signOffPhoto)
|
|
{
|
|
if (attachments != null)
|
|
{
|
|
foreach (var attachment in attachments)
|
|
{
|
|
var fileUrl = await _fileStorage.SaveFileAsync(attachment);
|
|
await _workOrderDataService.AddWorkOrderAttachmentAsync(new WorkOrderAttachments
|
|
{
|
|
WorkorderId = workOrderId,
|
|
Attachments = fileUrl
|
|
});
|
|
}
|
|
}
|
|
|
|
if (beforPhoto != null)
|
|
{
|
|
var fileUrl = await _fileStorage.SaveFileAsync(beforPhoto);
|
|
var workOrder = await _workOrderDataService.GetByIdAsync(workOrderId);
|
|
if (workOrder != null)
|
|
{
|
|
workOrder.BeforPhotoAttachment = fileUrl;
|
|
await _workOrderDataService.UpdateAsync(workOrder);
|
|
}
|
|
}
|
|
|
|
if (afterPhoto != null)
|
|
{
|
|
var fileUrl = await _fileStorage.SaveFileAsync(afterPhoto);
|
|
var workOrder = await _workOrderDataService.GetByIdAsync(workOrderId);
|
|
if (workOrder != null)
|
|
{
|
|
workOrder.AfterPhotoAttachment = fileUrl;
|
|
await _workOrderDataService.UpdateAsync(workOrder);
|
|
}
|
|
}
|
|
|
|
if (signOffPhoto != null)
|
|
{
|
|
var fileUrl = await _fileStorage.SaveFileAsync(signOffPhoto);
|
|
var workOrder = await _workOrderDataService.GetByIdAsync(workOrderId);
|
|
if (workOrder != null)
|
|
{
|
|
workOrder.SignOffAttachment = fileUrl;
|
|
await _workOrderDataService.UpdateAsync(workOrder);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static 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
|
|
};
|
|
}
|
|
|
|
private List<string> TrackChanges(WorkOrder existing, UpdateWorkOrderDTO dto)
|
|
{
|
|
var changes = new List<string>();
|
|
if (dto.WorkerOrderTitle != null && dto.WorkerOrderTitle != existing.WorkerOrderTitle)
|
|
changes.Add($"Title: {existing.WorkerOrderTitle} → {dto.WorkerOrderTitle}");
|
|
if (dto.Status.HasValue && dto.Status.ToString() != existing.Status)
|
|
changes.Add($"Status: {existing.Status} → {dto.Status}");
|
|
if (dto.Priority.HasValue && dto.Priority.ToString() != existing.Priority)
|
|
changes.Add($"Priority: {existing.Priority} → {dto.Priority}");
|
|
if (dto.AssignTo != null && dto.AssignTo != existing.AssignTo)
|
|
changes.Add($"Assigned To: {existing.AssignTo} → {dto.AssignTo}");
|
|
if (dto.DueDate.HasValue && dto.DueDate != existing.DueDate)
|
|
changes.Add($"Due Date: {existing.DueDate:yyyy-MM-dd} → {dto.DueDate:yyyy-MM-dd}");
|
|
return changes;
|
|
}
|
|
|
|
private async Task LogAuditAsync(int workOrderId, string userId, AuditAction action, string description)
|
|
{
|
|
await _workOrderDataService.AddAuditLogAsync(new WorkOrderAuditLog
|
|
{
|
|
WorkOrderId = workOrderId,
|
|
Action = action.ToString(),
|
|
FieldName = description,
|
|
UserId = userId,
|
|
CreatedAt = DateTime.UtcNow
|
|
});
|
|
}
|
|
}
|
|
}
|