Add InternalWONumber field with auto-generation

- Add InternalWONumber to WorkOrder model
- Auto-generate 8-digit sequential numbers starting at 10000001
- Generate on new work order creation and DynamoDB sync
- Include in GetWorkorderById and GetWorkOrderList responses
- Add BackfillInternalWONumbers endpoint for existing records
- Add migration for new column
This commit is contained in:
Adam Moussa 2026-04-16 18:42:58 -04:00
parent be70c84add
commit d07e24e419
6 changed files with 2365 additions and 1 deletions

View file

@ -30,6 +30,16 @@ namespace Api.SeaHavenIndustries.Controllers
var created = 0; var created = 0;
var updated = 0; var updated = 0;
var locationsCreated = 0; var locationsCreated = 0;
var lastInternalWO = await _db.workOrders
.Where(w => w.InternalWONumber != null && w.InternalWONumber != "")
.OrderByDescending(w => w.InternalWONumber)
.Select(w => w.InternalWONumber)
.FirstOrDefaultAsync();
int nextInternal = 10000001;
if (lastInternalWO != null && int.TryParse(lastInternalWO, out var parsed))
nextInternal = parsed + 1;
Dictionary<string, AttributeValue>? lastKey = null; Dictionary<string, AttributeValue>? lastKey = null;
do do
@ -63,6 +73,7 @@ namespace Api.SeaHavenIndustries.Controllers
{ {
var wo = new WorkOrder var wo = new WorkOrder
{ {
InternalWONumber = (nextInternal++).ToString("D8"),
ExternalWorkOrderId = externalId, ExternalWorkOrderId = externalId,
WorkerOrderNumber = externalId, WorkerOrderNumber = externalId,
WorkerOrderTitle = GetString(item, "description"), WorkerOrderTitle = GetString(item, "description"),
@ -197,6 +208,37 @@ namespace Api.SeaHavenIndustries.Controllers
return Ok(new { synced, created, skipped }); return Ok(new { synced, created, skipped });
} }
[HttpPost("BackfillInternalWONumbers")]
public async Task<IActionResult> BackfillInternalWONumbers()
{
var wosMissing = await _db.workOrders
.Where(w => w.InternalWONumber == null || w.InternalWONumber == "")
.OrderBy(w => w.Id)
.ToListAsync();
if (wosMissing.Count == 0)
return Ok(new { updated = 0 });
var lastWO = await _db.workOrders
.Where(w => w.InternalWONumber != null && w.InternalWONumber != "")
.OrderByDescending(w => w.InternalWONumber)
.Select(w => w.InternalWONumber)
.FirstOrDefaultAsync();
int next = 10000001;
if (lastWO != null && int.TryParse(lastWO, out var lastNum))
next = lastNum + 1;
foreach (var wo in wosMissing)
{
wo.InternalWONumber = next.ToString("D8");
next++;
}
await _db.SaveChangesAsync();
return Ok(new { updated = wosMissing.Count });
}
[HttpPost("BackfillCommentTypes")] [HttpPost("BackfillCommentTypes")]
public async Task<IActionResult> BackfillCommentTypes() public async Task<IActionResult> BackfillCommentTypes()
{ {

View file

@ -41,6 +41,7 @@ namespace Api.SeaHavenIndustries.Controllers
{ {
string userId = User.FindFirstValue(ClaimTypes.NameIdentifier); string userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
WorkOrder workOrder = new WorkOrder(); WorkOrder workOrder = new WorkOrder();
workOrder.InternalWONumber = await GenerateInternalWONumber();
workOrder.WorkerOrderNumber = model.WorkerOrderNumber; workOrder.WorkerOrderNumber = model.WorkerOrderNumber;
workOrder.WorkerOrderTitle = model.WorkerOrderTitle; workOrder.WorkerOrderTitle = model.WorkerOrderTitle;
workOrder.Description = model.Description; workOrder.Description = model.Description;
@ -381,8 +382,9 @@ namespace Api.SeaHavenIndustries.Controllers
.Select(w => new .Select(w => new
{ {
id = w.Id, id = w.Id,
internalWONumber = w.InternalWONumber,
workOrderNumber = w.WorkerOrderNumber, workOrderNumber = w.WorkerOrderNumber,
number = w.WorkerOrderNumber, number = w.InternalWONumber ?? w.WorkerOrderNumber,
title = w.WorkerOrderTitle, title = w.WorkerOrderTitle,
location = w.Locations != null ? w.Locations.Name : "", location = w.Locations != null ? w.Locations.Name : "",
locationName = w.Locations != null ? w.Locations.Name : "", locationName = w.Locations != null ? w.Locations.Name : "",
@ -580,6 +582,7 @@ namespace Api.SeaHavenIndustries.Controllers
var exist = await _db.workOrders.Include(w => w.Locations).Include(w => w.Comments).Include(w => w.Quotes).Where(w => w.Id == id).Select(s => new var exist = await _db.workOrders.Include(w => w.Locations).Include(w => w.Comments).Include(w => w.Quotes).Where(w => w.Id == id).Select(s => new
{ {
s.Id, s.Id,
s.InternalWONumber,
s.WorkerOrderNumber, s.WorkerOrderNumber,
title = s.WorkerOrderTitle, title = s.WorkerOrderTitle,
s.Description, s.Description,
@ -919,6 +922,21 @@ namespace Api.SeaHavenIndustries.Controllers
}).ToListAsync(); }).ToListAsync();
return Ok(data); return Ok(data);
} }
private async Task<string> GenerateInternalWONumber()
{
var lastWO = await _db.workOrders
.Where(w => w.InternalWONumber != null)
.OrderByDescending(w => w.InternalWONumber)
.Select(w => w.InternalWONumber)
.FirstOrDefaultAsync();
int next = 10000001;
if (lastWO != null && int.TryParse(lastWO, out var lastNum))
next = lastNum + 1;
return next.ToString("D8");
}
public class WorkorderFilterVM public class WorkorderFilterVM
{ {
public int? Id { get; set; } public int? Id { get; set; }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.SeaHavenIndustries.Migrations
{
/// <inheritdoc />
public partial class AddInternalWONumber : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "InternalWONumber",
table: "workOrders",
type: "nvarchar(max)",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "InternalWONumber",
table: "workOrders");
}
}
}

View file

@ -1432,6 +1432,9 @@ namespace Data.SeaHavenIndustries.Migrations
b.Property<string>("ExternalWorkOrderId") b.Property<string>("ExternalWorkOrderId")
.HasColumnType("nvarchar(max)"); .HasColumnType("nvarchar(max)");
b.Property<string>("InternalWONumber")
.HasColumnType("nvarchar(max)");
b.Property<bool?>("IsDeleted") b.Property<bool?>("IsDeleted")
.HasColumnType("bit"); .HasColumnType("bit");

View file

@ -6,6 +6,7 @@ namespace Data.SeaHavenIndustries
{ {
public class WorkOrder : FullAuditEntity public class WorkOrder : FullAuditEntity
{ {
public string? InternalWONumber { get; set; }
public string? ExternalWorkOrderId { get; set; } public string? ExternalWorkOrderId { get; set; }
public string? WorkerOrderNumber { get; set; } public string? WorkerOrderNumber { get; set; }
public string? WorkerOrderTitle { get; set; } public string? WorkerOrderTitle { get; set; }