mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 09:33:13 +00:00
Brings in dev's Phase 5 (PR #17) + vendor PRs (#25/#28/#29) atop the Phase 6/7 + flagColor base (PR #22). Preserves dev Phase 1-5 behavior and PR #22 Phase 6/7 + flagColor behavior. Conflict resolutions (16 files): - Migrations Phase4_SearchIndexes/.Designer + Phase5_DomainEvents/.Designer: take dev (Phase4 incl. SQL Server SiteCode/InternalWONumber index-compat shrink fix; Phase5 identical). ModelSnapshot union: Vendor CompanyId index + Phase7 ServiceNotes/ExternalWorkOrderId index. - ApplicationDbContext: keep dev SiteCode/InternalWONumber MaxLength (Phase1-5 + unguarded model test) + HEAD CompletionDocTemplate/ExternalWorkOrderId. - WorkOrderAuditService: unify on dev async staging API; convert Phase6 CompletionService 2 call sites to await StageFieldChangedAsync (drops HEAD sync duplicate; only callers, no test refs). - Hosted services: take HEAD (retry-on-failure, coherent with Phase7 WorkOrderJobRunStateAccessor/OpsHealth). Program.cs keeps dev vendor DI (ClamAV/VendorDocumentScanWorker/ArgumentExceptionFilter) + HEAD Phase7. - WorkOrderController: keep HEAD Phase6/7 service params + dev doc comment. - VendorController/WorkOrderBoardCreateService/QueryFilters/appsettings: union / dev-correct. - WorkOrderBoardUpdateServiceTests: union of HEAD (Phase6/7+flagColor) and dev (Phase1-5) test methods. Verified WorkOrderType.Other (enum 99) is a legit category, not an overdue sentinel; overdue uses dedicated OperationalFlags.PastDue + IsPastDue, and 'Overdue' is rejected as a WorkOrderType (no PR #23 import needed). Removed dev duplicate Api.Options.WorkOrderJobRunState (HEAD defines it in Services.Implementation alongside the Accessor; Services cannot reference Api).
141 lines
5.6 KiB
C#
141 lines
5.6 KiB
C#
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;
|
|
|
|
public WorkOrderBoardService(IWorkOrderBoardDataService boardDataService)
|
|
{
|
|
_boardDataService = boardDataService;
|
|
}
|
|
|
|
public async Task<WorkOrderBoardResponseDto> GetBoardAsync(
|
|
WorkOrderBoardQueryDto query,
|
|
string? currentUserId)
|
|
{
|
|
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.Search,
|
|
currentUserId);
|
|
|
|
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)
|
|
{
|
|
var raw = await _boardDataService.GetBoardRowByIdAsync(workOrderId);
|
|
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,
|
|
WorkOrderType = row.WorkOrderType,
|
|
IsPastDue = WorkOrderDerivedFields.IsPastDue(row.ScheduledDate, 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,
|
|
FlagColor = row.FlagColor,
|
|
PrimaryDispatchId = row.PrimaryDispatchId,
|
|
RowVersion = row.RowVersion,
|
|
DispatchRowVersion = row.DispatchRowVersion
|
|
};
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|