mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 08:23:12 +00:00
* feat(work-orders): enrich board search overdue filters and 0-based paging * fix(work-orders): align stacked services with CI build * fix(tests): pass userDataService in comment service unit test * fix(work-orders): use dedicated overdue query flag Stop treating WorkOrderType.Other as an overdue sentinel. Board and advanced search now accept overdue=true while types=Other filters real Other rows; combining both uses OR. * test(work-orders): cover overdue date/status boundary and Other type-filter Lock the PR #23 overdue regression boundary through the public advanced search service. Prove overdue filtering is driven by past-due date plus non-terminal status, not by the WorkOrderType.Other sentinel: - Other + future/not-completed excluded from overdue - past-due + Scheduled included; past-due + Completed/Canceled excluded - types=[PM, Other] keeps real Other rows and does not pull past-due rows - assert 0-based paging (Page=0) is preserved alongside overdue/type filters --------- Co-authored-by: Arthur Bassi <arthur.winiarski.ranger@outlook.com> Co-authored-by: Alexandre Brandizzi <alex_brandizzi@hotmail.com>
95 lines
4 KiB
C#
95 lines
4 KiB
C#
using Data.SeaHavenIndustries;
|
|
using Data.SeaHavenIndustries.Enums;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using SeaHaven.DataServices.Helpers;
|
|
using SeaHaven.DataServices.Interfaces;
|
|
|
|
namespace SeaHaven.DataServices.Implementation
|
|
{
|
|
public class WorkOrderBoardDataService : IWorkOrderBoardDataService
|
|
{
|
|
private readonly ApplicationDbContext _context;
|
|
|
|
public WorkOrderBoardDataService(ApplicationDbContext context)
|
|
{
|
|
_context = context;
|
|
}
|
|
|
|
public async Task<WorkOrderBoardQueryResult> GetBoardRowsAsync(WorkOrderBoardQuery query)
|
|
{
|
|
var weekStartDate = query.WeekStart.ToDateTime(TimeOnly.MinValue);
|
|
var weekEndDate = query.WeekEnd.ToDateTime(TimeOnly.MinValue);
|
|
|
|
var baseQuery = WorkOrderBoardQueryFilters.ApplyBaseScope(_context.workOrders.AsNoTracking());
|
|
baseQuery = WorkOrderBoardQueryFilters.ApplyDispatcherFilter(
|
|
baseQuery, query.Dispatchers, query.MyWorkOrders, query.CurrentUserId);
|
|
baseQuery = WorkOrderBoardQueryFilters.ApplyTypeFilter(baseQuery, query.Types, query.Overdue);
|
|
|
|
var scheduledBase = baseQuery.Where(w =>
|
|
(w.ScheduledDate != null
|
|
&& w.ScheduledDate.Value.Date >= weekStartDate.Date
|
|
&& w.ScheduledDate.Value.Date <= weekEndDate.Date)
|
|
|| (w.ScheduleWeekOnly == true && w.TargetWeek == query.WeekStart));
|
|
|
|
var unscheduledBase = baseQuery.Where(w =>
|
|
w.ScheduledDate == null
|
|
&& !LifecycleStatusSets.Terminal.Contains(w.LifecycleStatus));
|
|
|
|
var scheduledTotal = await scheduledBase.CountAsync();
|
|
|
|
var normalizedSearch = WorkOrderBoardSearchFilter.NormalizeSearch(query.Search);
|
|
if (normalizedSearch != null)
|
|
{
|
|
scheduledBase = WorkOrderBoardSearchFilter.Apply(scheduledBase, normalizedSearch);
|
|
unscheduledBase = WorkOrderBoardSearchFilter.Apply(unscheduledBase, normalizedSearch);
|
|
}
|
|
|
|
var scheduledRows = await WorkOrderBoardProjection.ProjectRowsAsync(
|
|
scheduledBase.OrderBy(w => w.ScheduledDate).ThenBy(w => w.InternalWONumber),
|
|
isUnscheduled: false);
|
|
var unscheduledRows = await WorkOrderBoardProjection.ProjectRowsAsync(
|
|
unscheduledBase.OrderBy(w => w.InternalWONumber),
|
|
isUnscheduled: true);
|
|
|
|
return new WorkOrderBoardQueryResult(scheduledRows, unscheduledRows, scheduledTotal);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<DispatcherLookupRow>> GetDispatcherLookupsAsync()
|
|
{
|
|
return await _context.Users
|
|
.AsNoTracking()
|
|
.Where(u => u.IsDeleted != true)
|
|
.OrderBy(u => u.FirstName)
|
|
.ThenBy(u => u.LastName)
|
|
.Select(u => new DispatcherLookupRow(
|
|
u.Id,
|
|
u.FirstName ?? "",
|
|
u.LastName,
|
|
u.Initials,
|
|
u.Color))
|
|
.ToListAsync();
|
|
}
|
|
|
|
public async Task<WorkOrderBoardRawRow?> GetBoardRowByIdAsync(int workOrderId)
|
|
{
|
|
var query = _context.workOrders
|
|
.AsNoTracking()
|
|
.Where(w => w.Id == workOrderId && w.istemplate != true && (w.IsDeleted != true || w.IsDeleted == null));
|
|
|
|
var rows = await WorkOrderBoardProjection.ProjectRowsAsync(query, isUnscheduled: false);
|
|
return rows.FirstOrDefault();
|
|
}
|
|
|
|
public async Task<bool> InternalWoNumberExistsAsync(string normalizedWoNumber, int excludeWorkOrderId)
|
|
{
|
|
return await _context.workOrders
|
|
.AsNoTracking()
|
|
.AnyAsync(w =>
|
|
w.Id != excludeWorkOrderId
|
|
&& w.istemplate != true
|
|
&& (w.IsDeleted != true || w.IsDeleted == null)
|
|
&& w.InternalWONumber == normalizedWoNumber);
|
|
}
|
|
|
|
}
|
|
}
|