mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-05 03:42:12 +00:00
feat(work-orders): persist Aveta Extra Docs media category
Round-trip category 5 on media POST/PATCH/GET and project hasAvetaDocument so pending vs attached survives reopen.
This commit is contained in:
parent
7245897d56
commit
7c7c6bc525
11 changed files with 232 additions and 2 deletions
|
|
@ -165,7 +165,8 @@ namespace Data.SeaHavenIndustries.Enums
|
||||||
Before = 1,
|
Before = 1,
|
||||||
After = 2,
|
After = 2,
|
||||||
Extra = 3,
|
Extra = 3,
|
||||||
Completion = 4
|
Completion = 4,
|
||||||
|
Aveta = 5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Data.SeaHavenIndustries;
|
using Data.SeaHavenIndustries;
|
||||||
|
using Data.SeaHavenIndustries.Enums;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using SeaHaven.DataServices.Interfaces;
|
using SeaHaven.DataServices.Interfaces;
|
||||||
|
|
||||||
|
|
@ -20,6 +21,9 @@ namespace SeaHaven.DataServices.Helpers
|
||||||
w.CarriedOver,
|
w.CarriedOver,
|
||||||
w.IsAddOn,
|
w.IsAddOn,
|
||||||
w.AvetaRequired,
|
w.AvetaRequired,
|
||||||
|
HasAvetaDocument = w.workOrderAttachments!.Any(a =>
|
||||||
|
(a.IsDeleted == null || a.IsDeleted == false)
|
||||||
|
&& a.Category == WorkOrderMediaCategory.Aveta),
|
||||||
w.OriginalDate,
|
w.OriginalDate,
|
||||||
w.OriginalWeek,
|
w.OriginalWeek,
|
||||||
w.WorkOrderType,
|
w.WorkOrderType,
|
||||||
|
|
@ -180,6 +184,7 @@ namespace SeaHaven.DataServices.Helpers
|
||||||
w.PrimaryUpliftAmount,
|
w.PrimaryUpliftAmount,
|
||||||
w.PrimaryDispatchStatus,
|
w.PrimaryDispatchStatus,
|
||||||
w.AvetaRequired,
|
w.AvetaRequired,
|
||||||
|
w.HasAvetaDocument,
|
||||||
w.OriginalDate,
|
w.OriginalDate,
|
||||||
w.OriginalWeek,
|
w.OriginalWeek,
|
||||||
isUnscheduled);
|
isUnscheduled);
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ namespace SeaHaven.DataServices.Interfaces
|
||||||
decimal? PrimaryUpliftAmount,
|
decimal? PrimaryUpliftAmount,
|
||||||
string? PrimaryDispatchStatus,
|
string? PrimaryDispatchStatus,
|
||||||
bool AvetaRequired,
|
bool AvetaRequired,
|
||||||
|
bool HasAvetaDocument,
|
||||||
DateOnly? OriginalDate,
|
DateOnly? OriginalDate,
|
||||||
DateOnly? OriginalWeek,
|
DateOnly? OriginalWeek,
|
||||||
bool IsUnscheduled);
|
bool IsUnscheduled);
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ namespace SeaHaven.Services.DTOs
|
||||||
public int CarriedOver { get; set; }
|
public int CarriedOver { get; set; }
|
||||||
public bool IsAddOn { get; set; }
|
public bool IsAddOn { get; set; }
|
||||||
public bool AvetaRequired { get; set; }
|
public bool AvetaRequired { get; set; }
|
||||||
|
public bool HasAvetaDocument { get; set; }
|
||||||
public DateOnly? OriginalDate { get; set; }
|
public DateOnly? OriginalDate { get; set; }
|
||||||
public DateOnly? OriginalWeek { get; set; }
|
public DateOnly? OriginalWeek { get; set; }
|
||||||
public WorkOrderType? WorkOrderType { get; set; }
|
public WorkOrderType? WorkOrderType { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,13 @@ namespace SeaHaven.Services.Helpers
|
||||||
return false;
|
return false;
|
||||||
if (!IsCoreLocked(status))
|
if (!IsCoreLocked(status))
|
||||||
return true;
|
return true;
|
||||||
return (category ?? WorkOrderMediaCategory.Extra) == WorkOrderMediaCategory.Extra;
|
return IsExtraDocsFamily(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsExtraDocsFamily(WorkOrderMediaCategory? category)
|
||||||
|
{
|
||||||
|
var resolved = category ?? WorkOrderMediaCategory.Extra;
|
||||||
|
return resolved == WorkOrderMediaCategory.Extra || resolved == WorkOrderMediaCategory.Aveta;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool ShouldBlockStatusChangeWhenPastDue(string field, bool isPastDue)
|
public static bool ShouldBlockStatusChangeWhenPastDue(string field, bool isPastDue)
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
CarriedOver = row.CarriedOver,
|
CarriedOver = row.CarriedOver,
|
||||||
IsAddOn = row.IsAddOn,
|
IsAddOn = row.IsAddOn,
|
||||||
AvetaRequired = row.AvetaRequired,
|
AvetaRequired = row.AvetaRequired,
|
||||||
|
HasAvetaDocument = row.HasAvetaDocument,
|
||||||
OriginalDate = row.OriginalDate,
|
OriginalDate = row.OriginalDate,
|
||||||
OriginalWeek = row.OriginalWeek,
|
OriginalWeek = row.OriginalWeek,
|
||||||
WorkOrderType = row.WorkOrderType,
|
WorkOrderType = row.WorkOrderType,
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
CarriedOver = row.CarriedOver,
|
CarriedOver = row.CarriedOver,
|
||||||
IsAddOn = row.IsAddOn,
|
IsAddOn = row.IsAddOn,
|
||||||
AvetaRequired = row.AvetaRequired,
|
AvetaRequired = row.AvetaRequired,
|
||||||
|
HasAvetaDocument = row.HasAvetaDocument,
|
||||||
WorkOrderType = row.WorkOrderType,
|
WorkOrderType = row.WorkOrderType,
|
||||||
IsPastDue = row.IsPastDue,
|
IsPastDue = row.IsPastDue,
|
||||||
SiteCode = row.SiteCode,
|
SiteCode = row.SiteCode,
|
||||||
|
|
|
||||||
|
|
@ -87,8 +87,12 @@ public class WorkOrderBoardMutationRulesTests
|
||||||
LifecycleStatus.Completed, WorkOrderMediaCategory.Before));
|
LifecycleStatus.Completed, WorkOrderMediaCategory.Before));
|
||||||
Assert.False(WorkOrderBoardMutationRules.CanMutateExtraMedia(
|
Assert.False(WorkOrderBoardMutationRules.CanMutateExtraMedia(
|
||||||
LifecycleStatus.Completed, WorkOrderMediaCategory.After));
|
LifecycleStatus.Completed, WorkOrderMediaCategory.After));
|
||||||
|
Assert.True(WorkOrderBoardMutationRules.CanMutateExtraMedia(
|
||||||
|
LifecycleStatus.Completed, WorkOrderMediaCategory.Aveta));
|
||||||
Assert.False(WorkOrderBoardMutationRules.CanMutateExtraMedia(
|
Assert.False(WorkOrderBoardMutationRules.CanMutateExtraMedia(
|
||||||
LifecycleStatus.Canceled, WorkOrderMediaCategory.Extra));
|
LifecycleStatus.Canceled, WorkOrderMediaCategory.Extra));
|
||||||
|
Assert.False(WorkOrderBoardMutationRules.CanMutateExtraMedia(
|
||||||
|
LifecycleStatus.Canceled, WorkOrderMediaCategory.Aveta));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -480,6 +480,77 @@ public class WorkOrderBoardServiceTests
|
||||||
Assert.True(response.Scheduled[0].AvetaRequired);
|
Assert.True(response.Scheduled[0].AvetaRequired);
|
||||||
Assert.Equal(new DateOnly(2026, 5, 8), response.Scheduled[0].OriginalDate);
|
Assert.Equal(new DateOnly(2026, 5, 8), response.Scheduled[0].OriginalDate);
|
||||||
Assert.Equal(new DateOnly(2026, 5, 4), response.Scheduled[0].OriginalWeek);
|
Assert.Equal(new DateOnly(2026, 5, 4), response.Scheduled[0].OriginalWeek);
|
||||||
|
Assert.False(response.Scheduled[0].HasAvetaDocument);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetBoardAsync_HasAvetaDocument_WhenAvetaAttachmentExists()
|
||||||
|
{
|
||||||
|
await using var context = CreateContext();
|
||||||
|
var weekStart = new DateOnly(2026, 6, 22);
|
||||||
|
|
||||||
|
context.workOrders.Add(new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
InternalWONumber = "10000000001",
|
||||||
|
AvetaRequired = true,
|
||||||
|
ScheduledDate = new DateTime(2026, 6, 24, 12, 0, 0, DateTimeKind.Utc),
|
||||||
|
LifecycleStatus = LifecycleStatus.Scheduled
|
||||||
|
});
|
||||||
|
context.workOrderAttachments.Add(new WorkOrderAttachments
|
||||||
|
{
|
||||||
|
WorkorderId = 1,
|
||||||
|
Attachments = "https://example.com/aveta.pdf",
|
||||||
|
Category = WorkOrderMediaCategory.Aveta
|
||||||
|
});
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var boardService = new WorkOrderBoardService(
|
||||||
|
new WorkOrderBoardDataService(context),
|
||||||
|
WorkOrderAccountTestHelpers.Resolver(context));
|
||||||
|
var response = await boardService.GetBoardAsync(new WorkOrderBoardQueryDto
|
||||||
|
{
|
||||||
|
WeekStart = weekStart
|
||||||
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
||||||
|
|
||||||
|
Assert.Single(response.Scheduled);
|
||||||
|
Assert.True(response.Scheduled[0].AvetaRequired);
|
||||||
|
Assert.True(response.Scheduled[0].HasAvetaDocument);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetBoardAsync_HasAvetaDocumentFalse_WhenOnlyExtraAttachment()
|
||||||
|
{
|
||||||
|
await using var context = CreateContext();
|
||||||
|
var weekStart = new DateOnly(2026, 6, 22);
|
||||||
|
|
||||||
|
context.workOrders.Add(new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
InternalWONumber = "10000000001",
|
||||||
|
AvetaRequired = true,
|
||||||
|
ScheduledDate = new DateTime(2026, 6, 24, 12, 0, 0, DateTimeKind.Utc),
|
||||||
|
LifecycleStatus = LifecycleStatus.Scheduled
|
||||||
|
});
|
||||||
|
context.workOrderAttachments.Add(new WorkOrderAttachments
|
||||||
|
{
|
||||||
|
WorkorderId = 1,
|
||||||
|
Attachments = "https://example.com/extra.pdf",
|
||||||
|
Category = WorkOrderMediaCategory.Extra
|
||||||
|
});
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var boardService = new WorkOrderBoardService(
|
||||||
|
new WorkOrderBoardDataService(context),
|
||||||
|
WorkOrderAccountTestHelpers.Resolver(context));
|
||||||
|
var response = await boardService.GetBoardAsync(new WorkOrderBoardQueryDto
|
||||||
|
{
|
||||||
|
WeekStart = weekStart
|
||||||
|
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
||||||
|
|
||||||
|
Assert.Single(response.Scheduled);
|
||||||
|
Assert.True(response.Scheduled[0].AvetaRequired);
|
||||||
|
Assert.False(response.Scheduled[0].HasAvetaDocument);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -242,6 +242,24 @@ public class WorkOrderCompletedSelectiveLockTests
|
||||||
Assert.Equal(WorkOrderMediaCategory.Extra, media.Category);
|
Assert.Equal(WorkOrderMediaCategory.Extra, media.Category);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AddMedia_Completed_AllowsAveta()
|
||||||
|
{
|
||||||
|
var (context, service) = CreateMediaSut();
|
||||||
|
context.workOrders.Add(CompletedWorkOrder());
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var media = await service.AddMediaAsync(
|
||||||
|
1,
|
||||||
|
WorkOrderMediaCategory.Aveta,
|
||||||
|
"https://example.com/aveta.pdf",
|
||||||
|
WorkOrderAccountTestHelpers.OrgWideAdmin(),
|
||||||
|
"actor-1");
|
||||||
|
|
||||||
|
Assert.True(media.Id > 0);
|
||||||
|
Assert.Equal(WorkOrderMediaCategory.Aveta, media.Category);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task AddMedia_Completed_BlocksBefore()
|
public async Task AddMedia_Completed_BlocksBefore()
|
||||||
{
|
{
|
||||||
|
|
@ -286,6 +304,32 @@ public class WorkOrderCompletedSelectiveLockTests
|
||||||
Assert.Equal(WorkOrderMediaCategory.Extra, media.Category);
|
Assert.Equal(WorkOrderMediaCategory.Extra, media.Category);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateMediaCategory_Completed_AllowsAveta()
|
||||||
|
{
|
||||||
|
var (context, service) = CreateMediaSut();
|
||||||
|
var wo = CompletedWorkOrder();
|
||||||
|
context.workOrders.Add(wo);
|
||||||
|
context.workOrderAttachments.Add(new WorkOrderAttachments
|
||||||
|
{
|
||||||
|
Id = 10,
|
||||||
|
WorkorderId = 1,
|
||||||
|
Attachments = "https://example.com/extra.pdf",
|
||||||
|
Category = WorkOrderMediaCategory.Extra
|
||||||
|
});
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var media = await service.UpdateMediaCategoryAsync(
|
||||||
|
1,
|
||||||
|
10,
|
||||||
|
WorkOrderMediaCategory.Aveta,
|
||||||
|
ToVersion(wo),
|
||||||
|
WorkOrderAccountTestHelpers.OrgWideAdmin(),
|
||||||
|
"actor-1");
|
||||||
|
|
||||||
|
Assert.Equal(WorkOrderMediaCategory.Aveta, media.Category);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task UpdateMediaCategory_Completed_BlocksMoveToBefore()
|
public async Task UpdateMediaCategory_Completed_BlocksMoveToBefore()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -866,6 +866,101 @@ public class WorkOrderMediaServiceTests
|
||||||
Assert.Contains(media, m => m.Category == WorkOrderMediaCategory.Extra);
|
Assert.Contains(media, m => m.Category == WorkOrderMediaCategory.Extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetMedia_IncludesAvetaCategory()
|
||||||
|
{
|
||||||
|
var (context, service) = CreateSut();
|
||||||
|
context.workOrders.Add(new WorkOrder { Id = 1 });
|
||||||
|
context.workOrderAttachments.Add(new WorkOrderAttachments
|
||||||
|
{
|
||||||
|
WorkorderId = 1,
|
||||||
|
Attachments = "https://example.com/aveta.pdf",
|
||||||
|
Category = WorkOrderMediaCategory.Aveta
|
||||||
|
});
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var media = await service.GetMediaAsync(1, AuthenticatedUser(), "actor-1");
|
||||||
|
|
||||||
|
Assert.NotNull(media);
|
||||||
|
Assert.Contains(media!, m => m.Category == WorkOrderMediaCategory.Aveta);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AddMedia_Aveta_PersistsCategory()
|
||||||
|
{
|
||||||
|
var (context, service) = CreateSut();
|
||||||
|
context.workOrders.Add(new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||||
|
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
|
||||||
|
});
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var media = await service.AddMediaAsync(
|
||||||
|
1,
|
||||||
|
WorkOrderMediaCategory.Aveta,
|
||||||
|
"https://example.com/aveta.pdf",
|
||||||
|
AuthenticatedUser(),
|
||||||
|
"actor-1");
|
||||||
|
|
||||||
|
Assert.True(media.Id > 0);
|
||||||
|
Assert.Equal(WorkOrderMediaCategory.Aveta, media.Category);
|
||||||
|
Assert.Equal(WorkOrderMediaCategory.Aveta, context.workOrderAttachments.Single().Category);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AddMedia_Extra_DoesNotPersistAsAveta()
|
||||||
|
{
|
||||||
|
var (context, service) = CreateSut();
|
||||||
|
context.workOrders.Add(new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||||
|
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
|
||||||
|
});
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var media = await service.AddMediaAsync(
|
||||||
|
1,
|
||||||
|
WorkOrderMediaCategory.Extra,
|
||||||
|
"https://example.com/extra.pdf",
|
||||||
|
AuthenticatedUser(),
|
||||||
|
"actor-1");
|
||||||
|
|
||||||
|
Assert.Equal(WorkOrderMediaCategory.Extra, media.Category);
|
||||||
|
Assert.Equal(WorkOrderMediaCategory.Extra, context.workOrderAttachments.Single().Category);
|
||||||
|
Assert.NotEqual(WorkOrderMediaCategory.Aveta, context.workOrderAttachments.Single().Category);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateMediaCategory_ExtraToAveta()
|
||||||
|
{
|
||||||
|
var (context, service) = CreateSut();
|
||||||
|
var wo = new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||||
|
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
|
||||||
|
};
|
||||||
|
context.workOrders.Add(wo);
|
||||||
|
context.workOrderAttachments.Add(new WorkOrderAttachments
|
||||||
|
{
|
||||||
|
Id = 10,
|
||||||
|
WorkorderId = 1,
|
||||||
|
Attachments = "https://example.com/extra.pdf",
|
||||||
|
Category = WorkOrderMediaCategory.Extra
|
||||||
|
});
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var media = await service.UpdateMediaCategoryAsync(
|
||||||
|
1, 10, WorkOrderMediaCategory.Aveta, ToVersion(wo), AuthenticatedUser(), "actor-1");
|
||||||
|
|
||||||
|
Assert.Equal(WorkOrderMediaCategory.Aveta, media.Category);
|
||||||
|
Assert.Equal(WorkOrderMediaCategory.Aveta, context.workOrderAttachments.Single().Category);
|
||||||
|
Assert.True(context.workOrderAttachments.Single().IsDeleted != true);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetMedia_TechnicianOnAssignedWorkOrder_Succeeds()
|
public async Task GetMedia_TechnicianOnAssignedWorkOrder_Succeeds()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue