mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 09:33:13 +00:00
Add set-once IsAddOn with server cutoff at create, board DTO exposure, legacy type-7 backfill, and Types=AddOn search compat. Aligns with FE PR #61 frozen contract.
154 lines
6.2 KiB
C#
154 lines
6.2 KiB
C#
using System.Security.Claims;
|
|
using Data.SeaHavenIndustries.Enums;
|
|
using SeaHaven.DataServices.Interfaces;
|
|
using SeaHaven.Services.DTOs;
|
|
using SeaHaven.Services.Helpers;
|
|
using SeaHaven.Services.Interfaces;
|
|
|
|
namespace SeaHaven.Services.Implementation
|
|
{
|
|
public class WorkOrderBoardService : IWorkOrderBoardService
|
|
{
|
|
private readonly IWorkOrderBoardDataService _boardDataService;
|
|
private readonly IWorkOrderAccountResolver _accountResolver;
|
|
|
|
public WorkOrderBoardService(
|
|
IWorkOrderBoardDataService boardDataService,
|
|
IWorkOrderAccountResolver accountResolver)
|
|
{
|
|
_boardDataService = boardDataService;
|
|
_accountResolver = accountResolver;
|
|
}
|
|
|
|
public async Task<WorkOrderBoardResponseDto> GetBoardAsync(
|
|
WorkOrderBoardQueryDto query,
|
|
ClaimsPrincipal user,
|
|
string? currentUserId)
|
|
{
|
|
var accountId = _accountResolver.ResolveAccountFilter(user);
|
|
var weekStart = query.WeekStart;
|
|
var weekEnd = query.WeekEnd ?? weekStart.AddDays(4);
|
|
|
|
var validationError = WorkOrderOperationalWeek.ValidateWeekWindow(weekStart, weekEnd);
|
|
if (validationError != null)
|
|
throw new ArgumentException(validationError);
|
|
|
|
var dataQuery = new WorkOrderBoardQuery(
|
|
weekStart,
|
|
weekEnd,
|
|
query.Dispatchers,
|
|
query.MyWorkOrders,
|
|
query.Types,
|
|
query.Overdue,
|
|
query.Search,
|
|
currentUserId,
|
|
accountId);
|
|
|
|
var result = await _boardDataService.GetBoardRowsAsync(dataQuery);
|
|
var utcNow = DateTime.UtcNow;
|
|
|
|
return new WorkOrderBoardResponseDto
|
|
{
|
|
WeekStart = weekStart,
|
|
WeekEnd = weekEnd,
|
|
Counts = new WorkOrderBoardCountsDto
|
|
{
|
|
Total = result.ScheduledTotalBeforeSearch,
|
|
Returned = result.ScheduledRows.Count
|
|
},
|
|
Scheduled = result.ScheduledRows.Select(r => MapRawRow(r, utcNow)).ToList(),
|
|
Unscheduled = result.UnscheduledRows.Select(r => MapRawRow(r, utcNow)).ToList()
|
|
};
|
|
}
|
|
|
|
public async Task<IReadOnlyList<DispatcherLookupDto>> GetDispatcherLookupsAsync()
|
|
{
|
|
var rows = await _boardDataService.GetDispatcherLookupsAsync();
|
|
return rows.Select(r => new DispatcherLookupDto
|
|
{
|
|
Id = r.Id,
|
|
Name = FormatName(r.FirstName, r.LastName),
|
|
Initials = r.Initials,
|
|
Color = r.Color
|
|
}).ToList();
|
|
}
|
|
|
|
public async Task<WorkOrderBoardRowDto?> GetBoardRowAsync(int workOrderId, ClaimsPrincipal user)
|
|
{
|
|
var accountId = _accountResolver.ResolveAccountFilter(user);
|
|
var raw = await _boardDataService.GetBoardRowByIdAsync(workOrderId, accountId);
|
|
return raw == null ? null : MapRawRow(raw, DateTime.UtcNow);
|
|
}
|
|
|
|
public static WorkOrderBoardRowDto MapRawRow(WorkOrderBoardRawRow row, DateTime utcNow)
|
|
{
|
|
// Appt column: date prefers dispatch appointment (vendor slot), then WO ScheduledDate.
|
|
// Time prefers WO ScheduledStart/End (dispatcher window), then dispatch datetime.
|
|
var apptStart = row.ScheduledStart ?? row.DispatchApptDate;
|
|
var apptEnd = row.ScheduledEnd;
|
|
|
|
return new WorkOrderBoardRowDto
|
|
{
|
|
Id = row.Id,
|
|
WoNumber = row.InternalWONumber,
|
|
RescheduleCount = row.RescheduleCount,
|
|
CarriedOver = row.CarriedOver,
|
|
IsAddOn = row.IsAddOn,
|
|
WorkOrderType = row.WorkOrderType,
|
|
IsPastDue = WorkOrderDerivedFields.IsPastDue(row.DueDate, row.LifecycleStatus, utcNow),
|
|
SiteCode = row.SiteCode,
|
|
LocationName = row.LocationName,
|
|
PocName = row.PocName,
|
|
PocPhone = row.PocPhone,
|
|
PocNotes = row.PocNotes,
|
|
LifecycleStatus = row.LifecycleStatus,
|
|
LifecycleStatusLabel = WorkOrderDerivedFields.GetLifecycleStatusLabel(row.LifecycleStatus),
|
|
LegacyStatus = row.LegacyStatus,
|
|
DispatcherId = row.AssignTo,
|
|
DispatcherName = FormatName(row.DispatcherFirstName, row.DispatcherLastName),
|
|
Initials = row.Initials,
|
|
Color = row.Color,
|
|
DueDate = row.DueDate,
|
|
ScheduledDate = row.ScheduledDate,
|
|
TargetWeek = row.TargetWeek,
|
|
ScheduleWeekOnly = row.ScheduleWeekOnly,
|
|
DayGroup = WorkOrderDerivedFields.GetDayGroup(row.ScheduledDate),
|
|
VendorId = row.VendorId,
|
|
VendorName = row.VendorName,
|
|
TechName = row.TechName,
|
|
TechPhone = row.TechPhone,
|
|
ApptDate = row.DispatchApptDate ?? row.ScheduledDate,
|
|
ApptTime = WorkOrderDerivedFields.FormatApptTime(apptStart, apptEnd),
|
|
Pm = row.Trade ?? row.Problem,
|
|
ServiceNotes = row.ServiceNotes,
|
|
ExtraServices = ParseExtraServices(row.ExtraServices),
|
|
DocStatus = row.DocStatus,
|
|
CompletedDate = row.CompletedDate,
|
|
FlagColor = row.FlagColor,
|
|
PrimaryDispatchId = row.PrimaryDispatchId,
|
|
RowVersion = row.RowVersion,
|
|
DispatchRowVersion = row.DispatchRowVersion,
|
|
PendingUpliftCount = row.PendingUpliftCount
|
|
};
|
|
}
|
|
|
|
private static string FormatName(string? firstName, string? lastName)
|
|
=> $"{firstName ?? ""} {lastName ?? ""}".Trim();
|
|
|
|
private static List<string>? ParseExtraServices(string? json)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(json))
|
|
return null;
|
|
|
|
try
|
|
{
|
|
var list = System.Text.Json.JsonSerializer.Deserialize<List<string>>(json);
|
|
return list is { Count: > 0 } ? list : null;
|
|
}
|
|
catch (System.Text.Json.JsonException)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
}
|