mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-07 06:59:05 +00:00
Add dispatch checklist items with template support
- DispatchChecklistItem model (DispatchId, WorkOrderId, ItemText, IsCompleted, CompletedBy, CompletedAt) - DispatchToVendor copies template items when TaskListTemplateId provided - Supports custom checklist items alongside template items - UpdateChecklistItem endpoint to toggle completion with user name - AddChecklistItem endpoint for ad-hoc items - GetDispatchById includes checklist items in response - Migration for DispatchChecklistItems table
This commit is contained in:
parent
be190836a4
commit
3a25fa8559
8 changed files with 2989 additions and 0 deletions
|
|
@ -1090,6 +1090,26 @@ namespace Api.SeaHavenIndustries.Controllers
|
||||||
DispatchWorkOrders = workOrders.Select(wo => new DispatchWorkOrder { WorkOrderId = wo.Id }).ToList()
|
DispatchWorkOrders = workOrders.Select(wo => new DispatchWorkOrder { WorkOrderId = wo.Id }).ToList()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var checklistItems = new List<DispatchChecklistItem>();
|
||||||
|
if (model.TaskListTemplateId.HasValue)
|
||||||
|
{
|
||||||
|
var template = await _db.TaskListTemplates
|
||||||
|
.Include(t => t.Items)
|
||||||
|
.FirstOrDefaultAsync(t => t.Id == model.TaskListTemplateId.Value);
|
||||||
|
if (template?.Items != null)
|
||||||
|
{
|
||||||
|
checklistItems.AddRange(template.Items.OrderBy(i => i.SortOrder).Select((i, idx) =>
|
||||||
|
new DispatchChecklistItem { ItemText = i.ItemText, SortOrder = idx }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (model.CustomChecklistItems != null)
|
||||||
|
{
|
||||||
|
var startIdx = checklistItems.Count;
|
||||||
|
checklistItems.AddRange(model.CustomChecklistItems.Select((text, idx) =>
|
||||||
|
new DispatchChecklistItem { ItemText = text, SortOrder = startIdx + idx }));
|
||||||
|
}
|
||||||
|
dispatch.ChecklistItems = checklistItems;
|
||||||
|
|
||||||
var woListHtml = string.Join("", workOrders.Select(wo =>
|
var woListHtml = string.Join("", workOrders.Select(wo =>
|
||||||
$"<tr><td style='padding:6px 8px;border:1px solid #ddd;'>{wo.InternalWONumber}</td>" +
|
$"<tr><td style='padding:6px 8px;border:1px solid #ddd;'>{wo.InternalWONumber}</td>" +
|
||||||
$"<td style='padding:6px 8px;border:1px solid #ddd;'>{wo.WorkerOrderTitle}</td>" +
|
$"<td style='padding:6px 8px;border:1px solid #ddd;'>{wo.WorkerOrderTitle}</td>" +
|
||||||
|
|
@ -1223,6 +1243,18 @@ namespace Api.SeaHavenIndustries.Controllers
|
||||||
woNumber = dwo.WorkOrder != null ? dwo.WorkOrder.InternalWONumber : "",
|
woNumber = dwo.WorkOrder != null ? dwo.WorkOrder.InternalWONumber : "",
|
||||||
woTitle = dwo.WorkOrder != null ? dwo.WorkOrder.WorkerOrderTitle : ""
|
woTitle = dwo.WorkOrder != null ? dwo.WorkOrder.WorkerOrderTitle : ""
|
||||||
}).ToList(),
|
}).ToList(),
|
||||||
|
checklistItems = _db.DispatchChecklistItems
|
||||||
|
.Where(ci => ci.DispatchId == x.Id)
|
||||||
|
.OrderBy(ci => ci.SortOrder)
|
||||||
|
.Select(ci => new {
|
||||||
|
ci.Id,
|
||||||
|
ci.ItemText,
|
||||||
|
ci.IsCompleted,
|
||||||
|
ci.CompletedBy,
|
||||||
|
ci.CompletedAt,
|
||||||
|
ci.SortOrder,
|
||||||
|
ci.WorkOrderId
|
||||||
|
}).ToList(),
|
||||||
comments = _db.Comments
|
comments = _db.Comments
|
||||||
.Where(c => c.DispatchId == x.Id)
|
.Where(c => c.DispatchId == x.Id)
|
||||||
.OrderBy(c => c.CreatedDate)
|
.OrderBy(c => c.CreatedDate)
|
||||||
|
|
@ -1340,6 +1372,41 @@ namespace Api.SeaHavenIndustries.Controllers
|
||||||
return match.Success ? match.Groups[1].Value : null;
|
return match.Success ? match.Groups[1].Value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("UpdateChecklistItem")]
|
||||||
|
public async Task<IActionResult> UpdateChecklistItem([FromBody] ChecklistItemUpdate_DTO model)
|
||||||
|
{
|
||||||
|
var item = await _db.DispatchChecklistItems.FindAsync(model.Id);
|
||||||
|
if (item == null)
|
||||||
|
return NotFound(new Response { Status = "Error", Message = "Checklist item not found" });
|
||||||
|
|
||||||
|
item.IsCompleted = model.IsCompleted;
|
||||||
|
item.CompletedBy = model.IsCompleted ? model.CompletedBy : null;
|
||||||
|
item.CompletedAt = model.IsCompleted ? DateTime.UtcNow : null;
|
||||||
|
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
return Ok(new { message = "Updated", item.Id, item.IsCompleted, item.CompletedBy, item.CompletedAt });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("AddChecklistItem")]
|
||||||
|
public async Task<IActionResult> AddChecklistItem([FromBody] AddChecklistItem_DTO model)
|
||||||
|
{
|
||||||
|
var maxSort = await _db.DispatchChecklistItems
|
||||||
|
.Where(c => c.DispatchId == model.DispatchId)
|
||||||
|
.MaxAsync(c => (int?)c.SortOrder) ?? -1;
|
||||||
|
|
||||||
|
var item = new DispatchChecklistItem
|
||||||
|
{
|
||||||
|
DispatchId = model.DispatchId,
|
||||||
|
WorkOrderId = model.WorkOrderId,
|
||||||
|
ItemText = model.ItemText,
|
||||||
|
SortOrder = maxSort + 1,
|
||||||
|
IsCompleted = false
|
||||||
|
};
|
||||||
|
_db.DispatchChecklistItems.Add(item);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
return Ok(new { item.Id, item.ItemText, item.SortOrder, item.IsCompleted });
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<string> GenerateInternalWONumber()
|
private async Task<string> GenerateInternalWONumber()
|
||||||
{
|
{
|
||||||
var lastWO = await _db.workOrders
|
var lastWO = await _db.workOrders
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,21 @@ namespace Api.SeaHavenIndustries.DTOs
|
||||||
public decimal? NTEAmount { get; set; }
|
public decimal? NTEAmount { get; set; }
|
||||||
public DateTime? ScheduledDate { get; set; }
|
public DateTime? ScheduledDate { get; set; }
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
|
public int? TaskListTemplateId { get; set; }
|
||||||
|
public List<string>? CustomChecklistItems { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ChecklistItemUpdate_DTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public bool IsCompleted { get; set; }
|
||||||
|
public string? CompletedBy { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AddChecklistItem_DTO
|
||||||
|
{
|
||||||
|
public int DispatchId { get; set; }
|
||||||
|
public int? WorkOrderId { get; set; }
|
||||||
|
public string? ItemText { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ namespace Data.SeaHavenIndustries
|
||||||
public DbSet<Vendor> Vendors { get; set; }
|
public DbSet<Vendor> Vendors { get; set; }
|
||||||
public DbSet<Dispatch> Dispatches { get; set; }
|
public DbSet<Dispatch> Dispatches { get; set; }
|
||||||
public DbSet<DispatchWorkOrder> DispatchWorkOrders { get; set; }
|
public DbSet<DispatchWorkOrder> DispatchWorkOrders { get; set; }
|
||||||
|
public DbSet<DispatchChecklistItem> DispatchChecklistItems { get; set; }
|
||||||
public DbSet<TaskListTemplate> TaskListTemplates { get; set; }
|
public DbSet<TaskListTemplate> TaskListTemplates { get; set; }
|
||||||
public DbSet<TaskListTemplateItem> TaskListTemplateItems { get; set; }
|
public DbSet<TaskListTemplateItem> TaskListTemplateItems { get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
2736
Data.SeaHavenIndustries/Migrations/20260417192012_AddDispatchChecklist.Designer.cs
generated
Normal file
2736
Data.SeaHavenIndustries/Migrations/20260417192012_AddDispatchChecklist.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,70 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Data.SeaHavenIndustries.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddDispatchChecklist : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "DispatchChecklistItems",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
|
DispatchId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
WorkOrderId = table.Column<int>(type: "int", nullable: true),
|
||||||
|
ItemText = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||||
|
IsCompleted = table.Column<bool>(type: "bit", nullable: false),
|
||||||
|
CompletedBy = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||||
|
CompletedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||||
|
SortOrder = table.Column<int>(type: "int", nullable: false),
|
||||||
|
IsDeleted = table.Column<bool>(type: "bit", nullable: true),
|
||||||
|
createdby = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||||
|
DeleterUserId = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||||
|
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||||
|
CreatedDate = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||||
|
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||||
|
LastModifierUserId = table.Column<int>(type: "int", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_DispatchChecklistItems", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_DispatchChecklistItems_Dispatches_DispatchId",
|
||||||
|
column: x => x.DispatchId,
|
||||||
|
principalTable: "Dispatches",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_DispatchChecklistItems_workOrders_WorkOrderId",
|
||||||
|
column: x => x.WorkOrderId,
|
||||||
|
principalTable: "workOrders",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_DispatchChecklistItems_DispatchId",
|
||||||
|
table: "DispatchChecklistItems",
|
||||||
|
column: "DispatchId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_DispatchChecklistItems_WorkOrderId",
|
||||||
|
table: "DispatchChecklistItems",
|
||||||
|
column: "WorkOrderId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "DispatchChecklistItems");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -712,6 +712,65 @@ namespace Data.SeaHavenIndustries.Migrations
|
||||||
b.ToTable("Dispatches");
|
b.ToTable("Dispatches");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Data.SeaHavenIndustries.DispatchChecklistItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime?>("CompletedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("CompletedBy")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("CreatedDate")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("DeleterUserId")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DeletionTime")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<int>("DispatchId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<bool>("IsCompleted")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<bool?>("IsDeleted")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<string>("ItemText")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastModificationTime")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<int?>("LastModifierUserId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int?>("WorkOrderId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("createdby")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DispatchId");
|
||||||
|
|
||||||
|
b.HasIndex("WorkOrderId");
|
||||||
|
|
||||||
|
b.ToTable("DispatchChecklistItems");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Data.SeaHavenIndustries.DispatchWorkOrder", b =>
|
modelBuilder.Entity("Data.SeaHavenIndustries.DispatchWorkOrder", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
|
@ -2217,6 +2276,24 @@ namespace Data.SeaHavenIndustries.Migrations
|
||||||
b.Navigation("WorkOrder");
|
b.Navigation("WorkOrder");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Data.SeaHavenIndustries.DispatchChecklistItem", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Data.SeaHavenIndustries.Dispatch", "Dispatch")
|
||||||
|
.WithMany("ChecklistItems")
|
||||||
|
.HasForeignKey("DispatchId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Data.SeaHavenIndustries.WorkOrder", "WorkOrder")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("WorkOrderId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
b.Navigation("Dispatch");
|
||||||
|
|
||||||
|
b.Navigation("WorkOrder");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Data.SeaHavenIndustries.DispatchWorkOrder", b =>
|
modelBuilder.Entity("Data.SeaHavenIndustries.DispatchWorkOrder", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Data.SeaHavenIndustries.Dispatch", "Dispatch")
|
b.HasOne("Data.SeaHavenIndustries.Dispatch", "Dispatch")
|
||||||
|
|
@ -2588,6 +2665,8 @@ namespace Data.SeaHavenIndustries.Migrations
|
||||||
|
|
||||||
modelBuilder.Entity("Data.SeaHavenIndustries.Dispatch", b =>
|
modelBuilder.Entity("Data.SeaHavenIndustries.Dispatch", b =>
|
||||||
{
|
{
|
||||||
|
b.Navigation("ChecklistItems");
|
||||||
|
|
||||||
b.Navigation("Comments");
|
b.Navigation("Comments");
|
||||||
|
|
||||||
b.Navigation("DispatchWorkOrders");
|
b.Navigation("DispatchWorkOrders");
|
||||||
|
|
|
||||||
|
|
@ -26,5 +26,6 @@ namespace Data.SeaHavenIndustries
|
||||||
public string? AcceptToken { get; set; }
|
public string? AcceptToken { get; set; }
|
||||||
public ICollection<Comments>? Comments { get; set; }
|
public ICollection<Comments>? Comments { get; set; }
|
||||||
public ICollection<DispatchWorkOrder>? DispatchWorkOrders { get; set; }
|
public ICollection<DispatchWorkOrder>? DispatchWorkOrders { get; set; }
|
||||||
|
public ICollection<DispatchChecklistItem>? ChecklistItems { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
Data.SeaHavenIndustries/Models/DispatchChecklistItem.cs
Normal file
19
Data.SeaHavenIndustries/Models/DispatchChecklistItem.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace Data.SeaHavenIndustries
|
||||||
|
{
|
||||||
|
public class DispatchChecklistItem : FullAuditEntity
|
||||||
|
{
|
||||||
|
public int DispatchId { get; set; }
|
||||||
|
[ForeignKey(nameof(DispatchId))]
|
||||||
|
public virtual Dispatch? Dispatch { get; set; }
|
||||||
|
public int? WorkOrderId { get; set; }
|
||||||
|
[ForeignKey(nameof(WorkOrderId))]
|
||||||
|
public virtual WorkOrder? WorkOrder { get; set; }
|
||||||
|
public string? ItemText { get; set; }
|
||||||
|
public bool IsCompleted { get; set; }
|
||||||
|
public string? CompletedBy { get; set; }
|
||||||
|
public DateTime? CompletedAt { get; set; }
|
||||||
|
public int SortOrder { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue