mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-04 20:42:09 +00:00
merge: bring FlagColor branch onto structured-notes without FlagColor model
This commit is contained in:
commit
40d86c85a1
10 changed files with 1 additions and 191 deletions
|
|
@ -1975,10 +1975,6 @@ namespace Data.SeaHavenIndustries.Migrations
|
||||||
.HasMaxLength(2000)
|
.HasMaxLength(2000)
|
||||||
.HasColumnType("nvarchar(2000)");
|
.HasColumnType("nvarchar(2000)");
|
||||||
|
|
||||||
b.Property<string>("FlagColor")
|
|
||||||
.HasMaxLength(7)
|
|
||||||
.HasColumnType("nvarchar(7)");
|
|
||||||
|
|
||||||
b.Property<string>("InternalWONumber")
|
b.Property<string>("InternalWONumber")
|
||||||
.HasColumnType("nvarchar(max)");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,10 +70,6 @@ namespace Data.SeaHavenIndustries
|
||||||
// --- Completion slice ---
|
// --- Completion slice ---
|
||||||
public DocStatus? DocStatus { get; set; }
|
public DocStatus? DocStatus { get; set; }
|
||||||
|
|
||||||
/// <summary>Board flag color (#RRGGBB from fixed palette). Null = no flag.</summary>
|
|
||||||
[MaxLength(7)]
|
|
||||||
public string? FlagColor { get; set; }
|
|
||||||
|
|
||||||
// --- Legacy SQL columns (drift reconciliation) ---
|
// --- Legacy SQL columns (drift reconciliation) ---
|
||||||
public string? AvettaTask { get; set; }
|
public string? AvettaTask { get; set; }
|
||||||
public DateOnly? AssignDate { get; set; }
|
public DateOnly? AssignDate { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,6 @@ namespace SeaHaven.DataServices.Helpers
|
||||||
w.ServiceNotes,
|
w.ServiceNotes,
|
||||||
w.ExtraServices,
|
w.ExtraServices,
|
||||||
w.DocStatus,
|
w.DocStatus,
|
||||||
w.FlagColor,
|
|
||||||
w.PrimaryDispatchId,
|
w.PrimaryDispatchId,
|
||||||
w.RowVersion,
|
w.RowVersion,
|
||||||
DispatchRowVersion = w.PrimaryDispatch != null ? w.PrimaryDispatch.RowVersion : null
|
DispatchRowVersion = w.PrimaryDispatch != null ? w.PrimaryDispatch.RowVersion : null
|
||||||
|
|
@ -116,7 +115,6 @@ namespace SeaHaven.DataServices.Helpers
|
||||||
w.ServiceNotes,
|
w.ServiceNotes,
|
||||||
w.ExtraServices,
|
w.ExtraServices,
|
||||||
w.DocStatus,
|
w.DocStatus,
|
||||||
w.FlagColor,
|
|
||||||
w.PrimaryDispatchId,
|
w.PrimaryDispatchId,
|
||||||
w.RowVersion,
|
w.RowVersion,
|
||||||
w.DispatchRowVersion,
|
w.DispatchRowVersion,
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,6 @@ namespace SeaHaven.DataServices.Interfaces
|
||||||
string? ServiceNotes,
|
string? ServiceNotes,
|
||||||
string? ExtraServices,
|
string? ExtraServices,
|
||||||
DocStatus? DocStatus,
|
DocStatus? DocStatus,
|
||||||
string? FlagColor,
|
|
||||||
int? PrimaryDispatchId,
|
int? PrimaryDispatchId,
|
||||||
byte[]? RowVersion,
|
byte[]? RowVersion,
|
||||||
byte[]? DispatchRowVersion,
|
byte[]? DispatchRowVersion,
|
||||||
|
|
|
||||||
|
|
@ -54,8 +54,6 @@ namespace SeaHaven.Services.DTOs
|
||||||
public string? ServiceNotes { get; set; }
|
public string? ServiceNotes { get; set; }
|
||||||
public List<string>? ExtraServices { get; set; }
|
public List<string>? ExtraServices { get; set; }
|
||||||
public DocStatus? DocStatus { get; set; }
|
public DocStatus? DocStatus { get; set; }
|
||||||
/// <summary>Board flag color (#RRGGBB). Null = no flag. Distinct from Color (dispatcher avatar).</summary>
|
|
||||||
public string? FlagColor { get; set; }
|
|
||||||
public int? PrimaryDispatchId { get; set; }
|
public int? PrimaryDispatchId { get; set; }
|
||||||
public byte[]? RowVersion { get; set; }
|
public byte[]? RowVersion { get; set; }
|
||||||
public byte[]? DispatchRowVersion { get; set; }
|
public byte[]? DispatchRowVersion { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ namespace SeaHaven.Services.Helpers
|
||||||
public const string Pm = "pm";
|
public const string Pm = "pm";
|
||||||
public const string ServiceNotes = "serviceNotes";
|
public const string ServiceNotes = "serviceNotes";
|
||||||
public const string ExtraServices = "extraServices";
|
public const string ExtraServices = "extraServices";
|
||||||
public const string FlagColor = "flagColor";
|
|
||||||
public const string RescheduleCount = "RescheduleCount";
|
public const string RescheduleCount = "RescheduleCount";
|
||||||
|
|
||||||
private static readonly HashSet<string> DispatchFields = new(StringComparer.OrdinalIgnoreCase)
|
private static readonly HashSet<string> DispatchFields = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
|
@ -45,8 +44,7 @@ namespace SeaHaven.Services.Helpers
|
||||||
DocStatus,
|
DocStatus,
|
||||||
Pm,
|
Pm,
|
||||||
ServiceNotes,
|
ServiceNotes,
|
||||||
ExtraServices,
|
ExtraServices
|
||||||
FlagColor
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static bool IsEditable(string field) => Canonicalize(field) != null;
|
public static bool IsEditable(string field) => Canonicalize(field) != null;
|
||||||
|
|
@ -86,7 +84,6 @@ namespace SeaHaven.Services.Helpers
|
||||||
Pm => "Trade",
|
Pm => "Trade",
|
||||||
ServiceNotes => "ServiceNotes",
|
ServiceNotes => "ServiceNotes",
|
||||||
ExtraServices => "ExtraServices",
|
ExtraServices => "ExtraServices",
|
||||||
FlagColor => "FlagColor",
|
|
||||||
_ => canonical
|
_ => canonical
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,6 @@ namespace SeaHaven.Services.Implementation
|
||||||
ServiceNotes = row.ServiceNotes,
|
ServiceNotes = row.ServiceNotes,
|
||||||
ExtraServices = ParseExtraServices(row.ExtraServices),
|
ExtraServices = ParseExtraServices(row.ExtraServices),
|
||||||
DocStatus = row.DocStatus,
|
DocStatus = row.DocStatus,
|
||||||
FlagColor = row.FlagColor,
|
|
||||||
PrimaryDispatchId = row.PrimaryDispatchId,
|
PrimaryDispatchId = row.PrimaryDispatchId,
|
||||||
RowVersion = row.RowVersion,
|
RowVersion = row.RowVersion,
|
||||||
DispatchRowVersion = row.DispatchRowVersion
|
DispatchRowVersion = row.DispatchRowVersion
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,6 @@ namespace SeaHaven.Services.Implementation
|
||||||
WorkOrderBoardFieldNames.Pm => new List<FieldChange> { ApplyStringField(value, auditField, v => workOrder.Trade = v, () => workOrder.Trade) },
|
WorkOrderBoardFieldNames.Pm => new List<FieldChange> { ApplyStringField(value, auditField, v => workOrder.Trade = v, () => workOrder.Trade) },
|
||||||
WorkOrderBoardFieldNames.ServiceNotes => new List<FieldChange> { ApplyServiceNotes(workOrder, value, auditField) },
|
WorkOrderBoardFieldNames.ServiceNotes => new List<FieldChange> { ApplyServiceNotes(workOrder, value, auditField) },
|
||||||
WorkOrderBoardFieldNames.ExtraServices => new List<FieldChange> { ApplyExtraServices(workOrder, value, auditField) },
|
WorkOrderBoardFieldNames.ExtraServices => new List<FieldChange> { ApplyExtraServices(workOrder, value, auditField) },
|
||||||
WorkOrderBoardFieldNames.FlagColor => new List<FieldChange> { ApplyFlagColor(workOrder, value, auditField) },
|
|
||||||
_ => throw new WorkOrderBoardValidationException("InvalidField", $"Field '{field}' is not editable.")
|
_ => throw new WorkOrderBoardValidationException("InvalidField", $"Field '{field}' is not editable.")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -549,24 +548,6 @@ namespace SeaHaven.Services.Implementation
|
||||||
return FieldChange.ForField(auditField, old, parsed.ToString());
|
return FieldChange.ForField(auditField, old, parsed.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FieldChange ApplyFlagColor(WorkOrder workOrder, string? value, string auditField)
|
|
||||||
{
|
|
||||||
if (!WorkOrderFlagColors.TryNormalize(value, out var canonical))
|
|
||||||
throw new WorkOrderBoardValidationException("InvalidValue", $"Invalid flagColor: {value}");
|
|
||||||
|
|
||||||
if (canonical != null && !WorkOrderFlagColors.IsAllowed(canonical))
|
|
||||||
throw new WorkOrderBoardValidationException(
|
|
||||||
"InvalidValue",
|
|
||||||
$"flagColor must be one of the allowed palette values or empty to clear.");
|
|
||||||
|
|
||||||
var old = workOrder.FlagColor;
|
|
||||||
if (string.Equals(old, canonical, StringComparison.OrdinalIgnoreCase))
|
|
||||||
return FieldChange.Unchanged(auditField);
|
|
||||||
|
|
||||||
workOrder.FlagColor = canonical;
|
|
||||||
return FieldChange.ForField(auditField, old, canonical);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<FieldChange> ApplyAutoScheduleSideEffects(WorkOrder workOrder)
|
private static List<FieldChange> ApplyAutoScheduleSideEffects(WorkOrder workOrder)
|
||||||
{
|
{
|
||||||
var changes = new List<FieldChange>();
|
var changes = new List<FieldChange>();
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,6 @@ namespace SeaHaven.Services.Implementation
|
||||||
ServiceNotes = extended?.ServiceNotes ?? row.ServiceNotes,
|
ServiceNotes = extended?.ServiceNotes ?? row.ServiceNotes,
|
||||||
ExtraServices = ParseExtraServices(extended?.ExtraServices) ?? row.ExtraServices,
|
ExtraServices = ParseExtraServices(extended?.ExtraServices) ?? row.ExtraServices,
|
||||||
DocStatus = row.DocStatus,
|
DocStatus = row.DocStatus,
|
||||||
FlagColor = row.FlagColor,
|
|
||||||
PrimaryDispatchId = row.PrimaryDispatchId,
|
PrimaryDispatchId = row.PrimaryDispatchId,
|
||||||
RowVersion = row.RowVersion,
|
RowVersion = row.RowVersion,
|
||||||
DispatchRowVersion = row.DispatchRowVersion,
|
DispatchRowVersion = row.DispatchRowVersion,
|
||||||
|
|
|
||||||
|
|
@ -350,157 +350,4 @@ public class WorkOrderBoardUpdateServiceTests
|
||||||
|
|
||||||
Assert.Equal("InvalidValue", ex.Code);
|
Assert.Equal("InvalidValue", ex.Code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task PatchField_SetsFlagColor_CanonicalUppercase()
|
|
||||||
{
|
|
||||||
var (context, service) = CreateSut();
|
|
||||||
var wo = new WorkOrder
|
|
||||||
{
|
|
||||||
Id = 1,
|
|
||||||
LifecycleStatus = LifecycleStatus.Incomplete,
|
|
||||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
|
|
||||||
};
|
|
||||||
context.workOrders.Add(wo);
|
|
||||||
await context.SaveChangesAsync();
|
|
||||||
|
|
||||||
var result = await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto
|
|
||||||
{
|
|
||||||
Field = WorkOrderBoardFieldNames.FlagColor,
|
|
||||||
Value = "#ef4444",
|
|
||||||
WorkOrderVersion = ToVersion(wo)
|
|
||||||
}, "actor-1");
|
|
||||||
|
|
||||||
Assert.Equal("#EF4444", result.FlagColor);
|
|
||||||
Assert.Equal("#EF4444", (await context.workOrders.FindAsync(1))!.FlagColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task PatchField_ClearsFlagColor_WithNullOrEmpty()
|
|
||||||
{
|
|
||||||
var (context, service) = CreateSut();
|
|
||||||
var wo = new WorkOrder
|
|
||||||
{
|
|
||||||
Id = 1,
|
|
||||||
LifecycleStatus = LifecycleStatus.Incomplete,
|
|
||||||
FlagColor = "#EF4444",
|
|
||||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
|
|
||||||
};
|
|
||||||
context.workOrders.Add(wo);
|
|
||||||
await context.SaveChangesAsync();
|
|
||||||
|
|
||||||
var cleared = await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto
|
|
||||||
{
|
|
||||||
Field = WorkOrderBoardFieldNames.FlagColor,
|
|
||||||
Value = "",
|
|
||||||
WorkOrderVersion = ToVersion(wo)
|
|
||||||
}, "actor-1");
|
|
||||||
|
|
||||||
Assert.Null(cleared.FlagColor);
|
|
||||||
Assert.Null((await context.workOrders.FindAsync(1))!.FlagColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task PatchField_ClearsFlagColor_WithNullValue()
|
|
||||||
{
|
|
||||||
var (context, service) = CreateSut();
|
|
||||||
var wo = new WorkOrder
|
|
||||||
{
|
|
||||||
Id = 1,
|
|
||||||
LifecycleStatus = LifecycleStatus.Incomplete,
|
|
||||||
FlagColor = "#3B82F6",
|
|
||||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
|
|
||||||
};
|
|
||||||
context.workOrders.Add(wo);
|
|
||||||
await context.SaveChangesAsync();
|
|
||||||
|
|
||||||
var cleared = await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto
|
|
||||||
{
|
|
||||||
Field = WorkOrderBoardFieldNames.FlagColor,
|
|
||||||
Value = null,
|
|
||||||
WorkOrderVersion = ToVersion(wo)
|
|
||||||
}, "actor-1");
|
|
||||||
|
|
||||||
Assert.Null(cleared.FlagColor);
|
|
||||||
Assert.Null((await context.workOrders.FindAsync(1))!.FlagColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task PatchField_FlagColorOutsidePalette_Throws()
|
|
||||||
{
|
|
||||||
var (context, service) = CreateSut();
|
|
||||||
var wo = new WorkOrder
|
|
||||||
{
|
|
||||||
Id = 1,
|
|
||||||
LifecycleStatus = LifecycleStatus.Incomplete,
|
|
||||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
|
|
||||||
};
|
|
||||||
context.workOrders.Add(wo);
|
|
||||||
await context.SaveChangesAsync();
|
|
||||||
|
|
||||||
var ex = await Assert.ThrowsAsync<WorkOrderBoardValidationException>(() =>
|
|
||||||
service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto
|
|
||||||
{
|
|
||||||
Field = WorkOrderBoardFieldNames.FlagColor,
|
|
||||||
Value = "#000000",
|
|
||||||
WorkOrderVersion = ToVersion(wo)
|
|
||||||
}, "actor-1"));
|
|
||||||
|
|
||||||
Assert.Equal("InvalidValue", ex.Code);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task PatchField_FlagColorMalformed_Throws()
|
|
||||||
{
|
|
||||||
var (context, service) = CreateSut();
|
|
||||||
var wo = new WorkOrder
|
|
||||||
{
|
|
||||||
Id = 1,
|
|
||||||
LifecycleStatus = LifecycleStatus.Incomplete,
|
|
||||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
|
|
||||||
};
|
|
||||||
context.workOrders.Add(wo);
|
|
||||||
await context.SaveChangesAsync();
|
|
||||||
|
|
||||||
var ex = await Assert.ThrowsAsync<WorkOrderBoardValidationException>(() =>
|
|
||||||
service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto
|
|
||||||
{
|
|
||||||
Field = WorkOrderBoardFieldNames.FlagColor,
|
|
||||||
Value = "not-a-color",
|
|
||||||
WorkOrderVersion = ToVersion(wo)
|
|
||||||
}, "actor-1"));
|
|
||||||
|
|
||||||
Assert.Equal("InvalidValue", ex.Code);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task PatchField_FlagColor_DoesNotRequireDispatchVersion()
|
|
||||||
{
|
|
||||||
var (context, service) = CreateSut();
|
|
||||||
var wo = new WorkOrder
|
|
||||||
{
|
|
||||||
Id = 1,
|
|
||||||
LifecycleStatus = LifecycleStatus.Incomplete,
|
|
||||||
PrimaryDispatchId = 10,
|
|
||||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
|
|
||||||
};
|
|
||||||
context.Dispatches.Add(new Dispatch
|
|
||||||
{
|
|
||||||
Id = 10,
|
|
||||||
WorkOrderId = 1,
|
|
||||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 2 }
|
|
||||||
});
|
|
||||||
context.workOrders.Add(wo);
|
|
||||||
await context.SaveChangesAsync();
|
|
||||||
|
|
||||||
var result = await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto
|
|
||||||
{
|
|
||||||
Field = WorkOrderBoardFieldNames.FlagColor,
|
|
||||||
Value = "#8B5CF6",
|
|
||||||
WorkOrderVersion = ToVersion(wo)
|
|
||||||
// no DispatchVersion
|
|
||||||
}, "actor-1");
|
|
||||||
|
|
||||||
Assert.Equal("#8B5CF6", result.FlagColor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue