mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 07:13:12 +00:00
Add configurable dropdowns, new WO fields, and seed data
- Create DropdownOption model with Category, Value, ParentValue for Trade/SubTrade/Problem - Add DropdownOptionsController with CRUD + ByCategory endpoint with parent filtering - Add Problem, Trade, SubTrade, VendorNTE, ScheduledDate, CompletedDate, Source to WorkOrder - Update EditWorkorder_DTO and GetWorkorderById with new fields - Audit log tracks changes to all new fields - Seed default Trades (10), SubTrades (20), and Problems (11) on startup
This commit is contained in:
parent
78c7850fb2
commit
05b36ef7dd
10 changed files with 2642 additions and 1 deletions
|
|
@ -0,0 +1,87 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Api.SeaHavenIndustries.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class DropdownOptionsController : Controller
|
||||
{
|
||||
private readonly ApplicationDbContext _db;
|
||||
|
||||
public DropdownOptionsController(ApplicationDbContext db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll([FromQuery] string? category = null)
|
||||
{
|
||||
var query = _db.DropdownOptions.Where(d => d.IsActive);
|
||||
if (!string.IsNullOrWhiteSpace(category))
|
||||
query = query.Where(d => d.Category == category);
|
||||
|
||||
var data = await query.OrderBy(d => d.Category).ThenBy(d => d.SortOrder).ThenBy(d => d.Value).ToListAsync();
|
||||
return Ok(data);
|
||||
}
|
||||
|
||||
[HttpGet("ByCategory/{category}")]
|
||||
public async Task<IActionResult> GetByCategory(string category, [FromQuery] string? parent = null)
|
||||
{
|
||||
var query = _db.DropdownOptions.Where(d => d.IsActive && d.Category == category);
|
||||
if (!string.IsNullOrWhiteSpace(parent))
|
||||
query = query.Where(d => d.ParentValue == parent);
|
||||
|
||||
var data = await query.OrderBy(d => d.SortOrder).ThenBy(d => d.Value).ToListAsync();
|
||||
return Ok(data);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] DropdownOption model)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(model.Category) || string.IsNullOrWhiteSpace(model.Value))
|
||||
return BadRequest(new { message = "Category and Value are required" });
|
||||
|
||||
var existing = await _db.DropdownOptions
|
||||
.AnyAsync(d => d.Category == model.Category && d.Value == model.Value && d.ParentValue == model.ParentValue);
|
||||
if (existing)
|
||||
return BadRequest(new { message = "This option already exists" });
|
||||
|
||||
model.Id = 0;
|
||||
model.IsActive = true;
|
||||
_db.DropdownOptions.Add(model);
|
||||
await _db.SaveChangesAsync();
|
||||
return Ok(model);
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> Update(int id, [FromBody] DropdownOption model)
|
||||
{
|
||||
var item = await _db.DropdownOptions.FindAsync(id);
|
||||
if (item == null)
|
||||
return NotFound(new { message = "Option not found" });
|
||||
|
||||
item.Value = model.Value;
|
||||
item.ParentValue = model.ParentValue;
|
||||
item.SortOrder = model.SortOrder;
|
||||
item.IsActive = model.IsActive;
|
||||
await _db.SaveChangesAsync();
|
||||
return Ok(item);
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(int id)
|
||||
{
|
||||
var item = await _db.DropdownOptions.FindAsync(id);
|
||||
if (item == null)
|
||||
return NotFound(new { message = "Option not found" });
|
||||
|
||||
_db.DropdownOptions.Remove(item);
|
||||
await _db.SaveChangesAsync();
|
||||
return Ok(new { message = "Deleted" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -213,6 +213,13 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
LogChange("Priority", workOrder.Priority, model.Priority);
|
||||
LogChange("LocationId", workOrder.LocationId?.ToString(), model.LocationId?.ToString());
|
||||
LogChange("Status", workOrder.Status, model.Status);
|
||||
LogChange("Problem", workOrder.Problem, model.Problem);
|
||||
LogChange("Trade", workOrder.Trade, model.Trade);
|
||||
LogChange("SubTrade", workOrder.SubTrade, model.SubTrade);
|
||||
LogChange("VendorNTE", workOrder.VendorNTE?.ToString("F2"), model.VendorNTE?.ToString("F2"));
|
||||
LogChange("ScheduledDate", workOrder.ScheduledDate?.ToString("yyyy-MM-dd"), model.ScheduledDate?.ToString("yyyy-MM-dd"));
|
||||
LogChange("CompletedDate", workOrder.CompletedDate?.ToString("yyyy-MM-dd"), model.CompletedDate?.ToString("yyyy-MM-dd"));
|
||||
LogChange("Source", workOrder.Source, model.Source);
|
||||
|
||||
workOrder.WorkerOrderNumber = model.WorkerOrderNumber;
|
||||
workOrder.WorkerOrderTitle = model.WorkerOrderTitle;
|
||||
|
|
@ -224,6 +231,13 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
workOrder.Priority = model.Priority;
|
||||
workOrder.LocationId = model.LocationId;
|
||||
workOrder.Status = model.Status;
|
||||
workOrder.Problem = model.Problem;
|
||||
workOrder.Trade = model.Trade;
|
||||
workOrder.SubTrade = model.SubTrade;
|
||||
workOrder.VendorNTE = model.VendorNTE;
|
||||
workOrder.ScheduledDate = model.ScheduledDate;
|
||||
workOrder.CompletedDate = model.CompletedDate;
|
||||
workOrder.Source = model.Source;
|
||||
workOrder.BeforPhotoIssue = model.BeforPhotoIssue;
|
||||
workOrder.AfterPhotoIssue = model.AfterPhotoIssue;
|
||||
workOrder.SignOffName = model.SignOffName;
|
||||
|
|
@ -588,11 +602,18 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
s.Description,
|
||||
s.CreatedDate,
|
||||
s.DueDate,
|
||||
s.ScheduledDate,
|
||||
s.CompletedDate,
|
||||
s.Status,
|
||||
assignee = s.AssignToUser != null ? s.AssignToUser.FirstName : "",
|
||||
location = s.Locations != null ? s.Locations.Name : "",
|
||||
locationId = s.LocationId,
|
||||
priority = s.Priority,
|
||||
s.Problem,
|
||||
s.Trade,
|
||||
s.SubTrade,
|
||||
s.VendorNTE,
|
||||
s.Source,
|
||||
s.PO,
|
||||
s.TT,
|
||||
poc = s.WorkOrderContacts.Select(c => new { c.Id, name = c.POC != null ? (c.POC.FirstName + " " + c.POC.LastName).Trim() : "", email = c.POC != null ? c.POC.Email : "", phone = c.POC != null ? c.POC.PhoneNumber : "" }),
|
||||
|
|
|
|||
|
|
@ -49,7 +49,13 @@ namespace Api.SeaHavenIndustries.DTOs
|
|||
public string? Priority { get; set; }
|
||||
public string? Status { get; set; }
|
||||
public int? LocationId { get; set; }
|
||||
//public int? CategoryId { get; set; }
|
||||
public string? Problem { get; set; }
|
||||
public string? Trade { get; set; }
|
||||
public string? SubTrade { get; set; }
|
||||
public decimal? VendorNTE { get; set; }
|
||||
public DateTime? ScheduledDate { get; set; }
|
||||
public DateTime? CompletedDate { get; set; }
|
||||
public string? Source { get; set; }
|
||||
|
||||
public List<IFormFile>? Attachments { get; set; }
|
||||
public string? BeforPhotoIssue { get; set; }
|
||||
|
|
|
|||
|
|
@ -128,6 +128,30 @@ if (app.Environment.IsDevelopment())
|
|||
if (result.Succeeded)
|
||||
await userManager.AddToRoleAsync(admin, "Admin");
|
||||
}
|
||||
|
||||
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
if (!db.DropdownOptions.Any())
|
||||
{
|
||||
var trades = new[] { "Plumbing", "Electrical", "HVAC", "Fire/Life Safety", "General", "Roofing", "Janitorial", "Painting", "Locksmith", "Carpentry" };
|
||||
for (int i = 0; i < trades.Length; i++)
|
||||
db.DropdownOptions.Add(new DropdownOption { Category = "Trade", Value = trades[i], SortOrder = i });
|
||||
|
||||
var subTrades = new (string trade, string sub)[] {
|
||||
("Plumbing", "Drain Cleaning"), ("Plumbing", "Pipe Repair"), ("Plumbing", "Fixture Install"), ("Plumbing", "Backflow Testing"),
|
||||
("Electrical", "Lighting"), ("Electrical", "Panel/Breaker"), ("Electrical", "Wiring"), ("Electrical", "Generator"),
|
||||
("HVAC", "AC Repair"), ("HVAC", "Heating"), ("HVAC", "Ductwork"), ("HVAC", "Controls/BMS"),
|
||||
("Fire/Life Safety", "Sprinkler"), ("Fire/Life Safety", "Fire Alarm"), ("Fire/Life Safety", "Extinguisher"), ("Fire/Life Safety", "Fire Pump"),
|
||||
("General", "Doors"), ("General", "Flooring"), ("General", "Drywall"), ("General", "Concrete"),
|
||||
};
|
||||
for (int i = 0; i < subTrades.Length; i++)
|
||||
db.DropdownOptions.Add(new DropdownOption { Category = "SubTrade", Value = subTrades[i].sub, ParentValue = subTrades[i].trade, SortOrder = i });
|
||||
|
||||
var problems = new[] { "Leak", "No Power", "Not Cooling", "Not Heating", "Clogged", "Broken/Damaged", "Alarm Fault", "Preventive Maintenance", "Inspection", "Installation", "Other" };
|
||||
for (int i = 0; i < problems.Length; i++)
|
||||
db.DropdownOptions.Add(new DropdownOption { Category = "Problem", Value = problems[i], SortOrder = i });
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
app.Run();
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ namespace Data.SeaHavenIndustries
|
|||
public DbSet<WorkOrderCategories> workOrderCategories { get; set; }
|
||||
public DbSet<WorkOrderAttachments> workOrderAttachments { get; set; }
|
||||
public DbSet<WorkOrderAuditLog> WorkOrderAuditLogs { get; set; }
|
||||
public DbSet<DropdownOption> DropdownOptions { get; set; }
|
||||
|
||||
// New DbSets for added entities
|
||||
public DbSet<Accounts> Accounts { get; set; }
|
||||
|
|
|
|||
2323
Data.SeaHavenIndustries/Migrations/20260417142453_AddDropdownOptionsAndWOFields.Designer.cs
generated
Normal file
2323
Data.SeaHavenIndustries/Migrations/20260417142453_AddDropdownOptionsAndWOFields.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,109 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Data.SeaHavenIndustries.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddDropdownOptionsAndWOFields : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "CompletedDate",
|
||||
table: "workOrders",
|
||||
type: "datetime2",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Problem",
|
||||
table: "workOrders",
|
||||
type: "nvarchar(max)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "ScheduledDate",
|
||||
table: "workOrders",
|
||||
type: "datetime2",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Source",
|
||||
table: "workOrders",
|
||||
type: "nvarchar(max)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "SubTrade",
|
||||
table: "workOrders",
|
||||
type: "nvarchar(max)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Trade",
|
||||
table: "workOrders",
|
||||
type: "nvarchar(max)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "VendorNTE",
|
||||
table: "workOrders",
|
||||
type: "decimal(18,2)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DropdownOptions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
Category = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
Value = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
ParentValue = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
SortOrder = table.Column<int>(type: "int", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "bit", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DropdownOptions", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "DropdownOptions");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CompletedDate",
|
||||
table: "workOrders");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Problem",
|
||||
table: "workOrders");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ScheduledDate",
|
||||
table: "workOrders");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Source",
|
||||
table: "workOrders");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SubTrade",
|
||||
table: "workOrders");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Trade",
|
||||
table: "workOrders");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "VendorNTE",
|
||||
table: "workOrders");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -627,6 +627,36 @@ namespace Data.SeaHavenIndustries.Migrations
|
|||
b.ToTable("Departments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.SeaHavenIndustries.DropdownOption", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Category")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("ParentValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("DropdownOptions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.SeaHavenIndustries.Employee", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
|
@ -1408,6 +1438,9 @@ namespace Data.SeaHavenIndustries.Migrations
|
|||
b.Property<string>("Building")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime?>("CompletedDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("CreatedDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
|
|
@ -1453,6 +1486,12 @@ namespace Data.SeaHavenIndustries.Migrations
|
|||
b.Property<string>("Priority")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Problem")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime?>("ScheduledDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("ScheduledStart")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
|
|
@ -1471,15 +1510,27 @@ namespace Data.SeaHavenIndustries.Migrations
|
|||
b.Property<string>("SiteCode")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Source")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("SourceEmailS3Key")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("SubTrade")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("TT")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Trade")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<decimal?>("VendorNTE")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<string>("WorkerOrderNumber")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
|
|
|
|||
12
Data.SeaHavenIndustries/Models/DropdownOption.cs
Normal file
12
Data.SeaHavenIndustries/Models/DropdownOption.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
namespace Data.SeaHavenIndustries
|
||||
{
|
||||
public class DropdownOption
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Category { get; set; } = "";
|
||||
public string Value { get; set; } = "";
|
||||
public string? ParentValue { get; set; }
|
||||
public int SortOrder { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,13 @@ namespace Data.SeaHavenIndustries
|
|||
public DateTime? DateReported { get; set; }
|
||||
public DateTime? ScheduledStart { get; set; }
|
||||
public string? SourceEmailS3Key { get; set; }
|
||||
public string? Problem { get; set; }
|
||||
public string? Trade { get; set; }
|
||||
public string? SubTrade { get; set; }
|
||||
public decimal? VendorNTE { get; set; }
|
||||
public DateTime? ScheduledDate { get; set; }
|
||||
public DateTime? CompletedDate { get; set; }
|
||||
public string? Source { get; set; }
|
||||
public bool? istemplate { get; set; }
|
||||
public string? PO { get; set; }
|
||||
public string? TT { get; set; }
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue