mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 04:53:11 +00:00
Add vendor dispatch workflow backend
- Create Vendor model with company info, trade specialties, active flag - Create Dispatch model (doubles as Vendor PO) with PO number, NTE, status, reply-to address - VendorController: CRUD, paginated list, dropdown endpoint with trade filtering - DispatchToVendor endpoint: multi-vendor dispatch, auto-generated PO numbers (VPO-00001), HTML email with full WO details via SendGrid, reply-to wo-{number}@int.seahaven.com - GetDispatches endpoint for listing dispatches by WO - Include dispatches in GetWorkorderById response - SendMessage.SendDispatchEmail with reply-to support - Audit log entry for each dispatch
This commit is contained in:
parent
05b36ef7dd
commit
d24c4643f5
12 changed files with 3144 additions and 8 deletions
176
Api.SeaHavenIndustries/Controllers/VendorController.cs
Normal file
176
Api.SeaHavenIndustries/Controllers/VendorController.cs
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
using Api.SeaHavenIndustries.DTOs;
|
||||
using Data.SeaHavenIndustries;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Api.SeaHavenIndustries.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Route("api/vendors")]
|
||||
public class VendorController : Controller
|
||||
{
|
||||
private readonly ApplicationDbContext _db;
|
||||
|
||||
public VendorController(ApplicationDbContext db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[HttpGet("GetVendorList")]
|
||||
public IActionResult GetVendorList(string? search = "", int page = 1, int pageSize = 10)
|
||||
{
|
||||
var query = _db.Vendors.Where(v => v.IsActive);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
var s = search.ToLower();
|
||||
query = query.Where(v =>
|
||||
(v.CompanyName ?? "").ToLower().Contains(s) ||
|
||||
(v.ContactName ?? "").ToLower().Contains(s) ||
|
||||
(v.Email ?? "").ToLower().Contains(s) ||
|
||||
(v.TradeSpecialties ?? "").ToLower().Contains(s));
|
||||
}
|
||||
|
||||
var totalCount = query.Count();
|
||||
|
||||
var data = query.OrderByDescending(v => v.Id)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(v => new
|
||||
{
|
||||
v.Id,
|
||||
v.CompanyName,
|
||||
v.ContactName,
|
||||
v.Email,
|
||||
v.Phone,
|
||||
v.TradeSpecialties,
|
||||
v.IsActive
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return Ok(new Pagination_DTO
|
||||
{
|
||||
Data = data,
|
||||
PageNumber = page,
|
||||
PageSize = pageSize,
|
||||
TotalCount = totalCount,
|
||||
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetVendorById(int id)
|
||||
{
|
||||
var vendor = await _db.Vendors.Where(v => v.Id == id).Select(v => new
|
||||
{
|
||||
v.Id,
|
||||
v.CompanyName,
|
||||
v.ContactName,
|
||||
v.Email,
|
||||
v.Phone,
|
||||
v.Address,
|
||||
v.City,
|
||||
v.State,
|
||||
v.Zip,
|
||||
v.TradeSpecialties,
|
||||
v.IsActive
|
||||
}).FirstOrDefaultAsync();
|
||||
|
||||
if (vendor == null)
|
||||
return NotFound(new Response { Status = "Error", Message = "Vendor not found" });
|
||||
|
||||
return Ok(vendor);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] Vendor_DTO model)
|
||||
{
|
||||
try
|
||||
{
|
||||
var vendor = new Vendor
|
||||
{
|
||||
CompanyName = model.CompanyName,
|
||||
ContactName = model.ContactName,
|
||||
Email = model.Email,
|
||||
Phone = model.Phone,
|
||||
Address = model.Address,
|
||||
City = model.City,
|
||||
State = model.State,
|
||||
Zip = model.Zip,
|
||||
TradeSpecialties = model.TradeSpecialties,
|
||||
IsActive = model.IsActive
|
||||
};
|
||||
_db.Vendors.Add(vendor);
|
||||
await _db.SaveChangesAsync();
|
||||
return Ok(new DataResponse { Message = "Vendor Created", Status = "200" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> Update(int id, [FromBody] EditVendor_DTO model)
|
||||
{
|
||||
try
|
||||
{
|
||||
var exist = await _db.Vendors.FindAsync(id);
|
||||
if (exist == null)
|
||||
return BadRequest(new Response { Status = "Error", Message = "Vendor not found" });
|
||||
|
||||
exist.CompanyName = model.CompanyName;
|
||||
exist.ContactName = model.ContactName;
|
||||
exist.Email = model.Email;
|
||||
exist.Phone = model.Phone;
|
||||
exist.Address = model.Address;
|
||||
exist.City = model.City;
|
||||
exist.State = model.State;
|
||||
exist.Zip = model.Zip;
|
||||
exist.TradeSpecialties = model.TradeSpecialties;
|
||||
exist.IsActive = model.IsActive;
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
return Ok(new DataResponse { Message = "Vendor Updated", Status = "200" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(int id)
|
||||
{
|
||||
var exist = await _db.Vendors.FindAsync(id);
|
||||
if (exist == null)
|
||||
return BadRequest(new Response { Status = "Error", Message = "Vendor not found" });
|
||||
|
||||
_db.Vendors.Remove(exist);
|
||||
await _db.SaveChangesAsync();
|
||||
return Ok(new DataResponse { Message = "Vendor Deleted", Status = "200" });
|
||||
}
|
||||
|
||||
[HttpGet("Dropdown")]
|
||||
public async Task<IActionResult> GetDropdown([FromQuery] string? trade = null)
|
||||
{
|
||||
var query = _db.Vendors.Where(v => v.IsActive);
|
||||
|
||||
var vendors = await query.OrderBy(v => v.CompanyName)
|
||||
.Select(v => new { v.Id, v.CompanyName, v.TradeSpecialties })
|
||||
.ToListAsync();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(trade))
|
||||
{
|
||||
var tradeMatched = vendors.Where(v => (v.TradeSpecialties ?? "").Contains(trade)).ToList();
|
||||
var rest = vendors.Where(v => !(v.TradeSpecialties ?? "").Contains(trade)).ToList();
|
||||
vendors = tradeMatched.Concat(rest).ToList();
|
||||
}
|
||||
|
||||
return Ok(vendors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,12 +26,14 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
private readonly ApplicationDbContext _db;
|
||||
private readonly IWebHostEnvironment _webHostEnvironment;
|
||||
IHttpContextAccessor _httpContext;
|
||||
public WorkOrderController(UserManager<ApplicationUser> userManager, ApplicationDbContext db, IWebHostEnvironment webHostEnvironment, IHttpContextAccessor httpContext)
|
||||
private readonly Helper.SendMessage _sendMessage;
|
||||
public WorkOrderController(UserManager<ApplicationUser> userManager, ApplicationDbContext db, IWebHostEnvironment webHostEnvironment, IHttpContextAccessor httpContext, Helper.SendMessage sendMessage)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_db = db;
|
||||
_webHostEnvironment = webHostEnvironment;
|
||||
_httpContext = httpContext;
|
||||
_sendMessage = sendMessage;
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("AddWorkorder")]
|
||||
|
|
@ -593,7 +595,7 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
[HttpGet("GetWorkorderById")]
|
||||
public async Task<IActionResult> GetWorkorderById(int id)
|
||||
{
|
||||
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).Include(w => w.Dispatches).ThenInclude(d => d.Vendor).Where(w => w.Id == id).Select(s => new
|
||||
{
|
||||
s.Id,
|
||||
s.InternalWONumber,
|
||||
|
|
@ -639,7 +641,18 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
a.Action,
|
||||
a.CreatedAt,
|
||||
userName = a.User != null ? a.User.FirstName + " " + a.User.LastName : ""
|
||||
}).ToList()
|
||||
}).ToList(),
|
||||
dispatches = s.Dispatches.Select(d => new {
|
||||
d.Id,
|
||||
d.PONumber,
|
||||
d.NTEAmount,
|
||||
d.Description,
|
||||
d.Status,
|
||||
d.DispatchedAt,
|
||||
d.EmailSent,
|
||||
vendorName = d.Vendor != null ? d.Vendor.CompanyName : "",
|
||||
vendorEmail = d.Vendor != null ? d.Vendor.Email : ""
|
||||
}).ToList()
|
||||
}).FirstOrDefaultAsync();
|
||||
if (exist == null)
|
||||
{
|
||||
|
|
@ -980,6 +993,125 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
}).ToListAsync();
|
||||
return Ok(data);
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("DispatchToVendor")]
|
||||
public async Task<IActionResult> DispatchToVendor([FromBody] Dispatch_DTO model)
|
||||
{
|
||||
try
|
||||
{
|
||||
var workOrder = await _db.workOrders.Include(w => w.Locations)
|
||||
.FirstOrDefaultAsync(w => w.Id == model.WorkOrderId);
|
||||
if (workOrder == null)
|
||||
return BadRequest(new Response { Status = "Error", Message = "Work order not found" });
|
||||
|
||||
var lastPO = await _db.Dispatches
|
||||
.Where(d => d.PONumber != null)
|
||||
.OrderByDescending(d => d.PONumber)
|
||||
.Select(d => d.PONumber)
|
||||
.FirstOrDefaultAsync();
|
||||
int nextPO = 1;
|
||||
if (lastPO != null && lastPO.StartsWith("VPO-") && int.TryParse(lastPO.Substring(4), out var lastNum))
|
||||
nextPO = lastNum + 1;
|
||||
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
var dispatches = new List<object>();
|
||||
|
||||
foreach (var vendorId in model.VendorIds)
|
||||
{
|
||||
var vendor = await _db.Vendors.FindAsync(vendorId);
|
||||
if (vendor == null) continue;
|
||||
|
||||
var poNumber = $"VPO-{nextPO:D5}";
|
||||
nextPO++;
|
||||
|
||||
var replyTo = $"wo-{workOrder.InternalWONumber}@int.seahaven.com";
|
||||
|
||||
var dispatch = new Dispatch
|
||||
{
|
||||
WorkOrderId = model.WorkOrderId,
|
||||
VendorId = vendorId,
|
||||
PONumber = poNumber,
|
||||
NTEAmount = model.NTEAmount,
|
||||
Description = model.Description,
|
||||
Status = "Sent",
|
||||
DispatchedAt = DateTime.UtcNow,
|
||||
ReplyToAddress = replyTo
|
||||
};
|
||||
|
||||
var subject = $"[WO-{workOrder.InternalWONumber}] {workOrder.WorkerOrderTitle}";
|
||||
var locationName = workOrder.Locations?.Name ?? "";
|
||||
var locationAddr = workOrder.Locations?.Address ?? "";
|
||||
|
||||
var html = $@"
|
||||
<h2>Work Order Dispatch</h2>
|
||||
<table style='border-collapse:collapse;width:100%;font-family:Arial,sans-serif;'>
|
||||
<tr><td style='padding:8px;font-weight:bold;border:1px solid #ddd;'>WO Number</td><td style='padding:8px;border:1px solid #ddd;'>{workOrder.InternalWONumber}</td></tr>
|
||||
<tr><td style='padding:8px;font-weight:bold;border:1px solid #ddd;'>Title</td><td style='padding:8px;border:1px solid #ddd;'>{workOrder.WorkerOrderTitle}</td></tr>
|
||||
<tr><td style='padding:8px;font-weight:bold;border:1px solid #ddd;'>Location</td><td style='padding:8px;border:1px solid #ddd;'>{locationName} {locationAddr}</td></tr>
|
||||
<tr><td style='padding:8px;font-weight:bold;border:1px solid #ddd;'>Priority</td><td style='padding:8px;border:1px solid #ddd;'>{workOrder.Priority}</td></tr>
|
||||
<tr><td style='padding:8px;font-weight:bold;border:1px solid #ddd;'>Due Date</td><td style='padding:8px;border:1px solid #ddd;'>{workOrder.DueDate?.ToString("yyyy-MM-dd") ?? "N/A"}</td></tr>
|
||||
<tr><td style='padding:8px;font-weight:bold;border:1px solid #ddd;'>Trade</td><td style='padding:8px;border:1px solid #ddd;'>{workOrder.Trade}</td></tr>
|
||||
<tr><td style='padding:8px;font-weight:bold;border:1px solid #ddd;'>Sub-Trade</td><td style='padding:8px;border:1px solid #ddd;'>{workOrder.SubTrade}</td></tr>
|
||||
<tr><td style='padding:8px;font-weight:bold;border:1px solid #ddd;'>Problem</td><td style='padding:8px;border:1px solid #ddd;'>{workOrder.Problem}</td></tr>
|
||||
<tr><td style='padding:8px;font-weight:bold;border:1px solid #ddd;'>NTE</td><td style='padding:8px;border:1px solid #ddd;'>${model.NTEAmount?.ToString("F2") ?? "N/A"}</td></tr>
|
||||
<tr><td style='padding:8px;font-weight:bold;border:1px solid #ddd;'>Vendor PO</td><td style='padding:8px;border:1px solid #ddd;'>{poNumber}</td></tr>
|
||||
</table>
|
||||
<h3>Description</h3>
|
||||
<p>{model.Description ?? workOrder.Description}</p>
|
||||
<hr/>
|
||||
<p style='color:#999;font-size:12px;'>Reply to this email to communicate about this work order.</p>";
|
||||
|
||||
var emailSent = await _sendMessage.SendDispatchEmail(vendor.Email, subject, html, replyTo);
|
||||
dispatch.EmailSent = emailSent;
|
||||
|
||||
_db.Dispatches.Add(dispatch);
|
||||
|
||||
_db.WorkOrderAuditLogs.Add(new WorkOrderAuditLog
|
||||
{
|
||||
WorkOrderId = model.WorkOrderId,
|
||||
UserId = userId,
|
||||
FieldName = "Dispatch",
|
||||
OldValue = "",
|
||||
NewValue = $"{vendor.CompanyName} ({poNumber})",
|
||||
Action = "dispatch",
|
||||
CreatedAt = DateTime.UtcNow
|
||||
});
|
||||
|
||||
dispatches.Add(new { dispatch.Id, poNumber, vendorName = vendor.CompanyName, dispatch.Status });
|
||||
}
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
return Ok(new { message = "Dispatched", dispatches });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("GetDispatches/{workOrderId}")]
|
||||
public async Task<IActionResult> GetDispatches(int workOrderId)
|
||||
{
|
||||
var data = await _db.Dispatches
|
||||
.Where(d => d.WorkOrderId == workOrderId)
|
||||
.Include(d => d.Vendor)
|
||||
.OrderByDescending(d => d.DispatchedAt)
|
||||
.Select(d => new
|
||||
{
|
||||
d.Id,
|
||||
d.PONumber,
|
||||
d.NTEAmount,
|
||||
d.Description,
|
||||
d.Status,
|
||||
d.DispatchedAt,
|
||||
d.EmailSent,
|
||||
vendorName = d.Vendor != null ? d.Vendor.CompanyName : "",
|
||||
vendorEmail = d.Vendor != null ? d.Vendor.Email : ""
|
||||
})
|
||||
.ToListAsync();
|
||||
return Ok(data);
|
||||
}
|
||||
|
||||
private async Task<string> GenerateInternalWONumber()
|
||||
{
|
||||
var lastWO = await _db.workOrders
|
||||
|
|
|
|||
10
Api.SeaHavenIndustries/DTOs/Dispatch_DTO.cs
Normal file
10
Api.SeaHavenIndustries/DTOs/Dispatch_DTO.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
namespace Api.SeaHavenIndustries.DTOs
|
||||
{
|
||||
public class Dispatch_DTO
|
||||
{
|
||||
public int WorkOrderId { get; set; }
|
||||
public List<int> VendorIds { get; set; } = new();
|
||||
public decimal? NTEAmount { get; set; }
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
}
|
||||
21
Api.SeaHavenIndustries/DTOs/Vendor_DTO.cs
Normal file
21
Api.SeaHavenIndustries/DTOs/Vendor_DTO.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
namespace Api.SeaHavenIndustries.DTOs
|
||||
{
|
||||
public class Vendor_DTO
|
||||
{
|
||||
public string? CompanyName { get; set; }
|
||||
public string? ContactName { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public string? Address { get; set; }
|
||||
public string? City { get; set; }
|
||||
public string? State { get; set; }
|
||||
public string? Zip { get; set; }
|
||||
public string? TradeSpecialties { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
}
|
||||
|
||||
public class EditVendor_DTO : Vendor_DTO
|
||||
{
|
||||
public int Id { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -98,8 +98,27 @@ namespace Api.SeaHavenIndustries.Helper
|
|||
return false;
|
||||
}
|
||||
}
|
||||
public async Task<bool> SendDispatchEmail(string emailTo, string subject, string htmlBody, string? replyTo)
|
||||
{
|
||||
try
|
||||
{
|
||||
var apiKey = _configuration.GetValue<string>("SendGrid:ApiKey");
|
||||
var client = new SendGridClient(apiKey);
|
||||
var from = new EmailAddress("tech@seahavenind.com", "Sea Haven Industries");
|
||||
var to = new EmailAddress(emailTo, "");
|
||||
|
||||
var msg = MailHelper.CreateSingleEmail(from, to, subject, "", htmlBody);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(replyTo))
|
||||
msg.SetReplyTo(new EmailAddress(replyTo));
|
||||
|
||||
var response = await client.SendEmailAsync(msg);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ namespace Data.SeaHavenIndustries
|
|||
public DbSet<WorkOrderAttachments> workOrderAttachments { get; set; }
|
||||
public DbSet<WorkOrderAuditLog> WorkOrderAuditLogs { get; set; }
|
||||
public DbSet<DropdownOption> DropdownOptions { get; set; }
|
||||
public DbSet<Vendor> Vendors { get; set; }
|
||||
public DbSet<Dispatch> Dispatches { get; set; }
|
||||
|
||||
// New DbSets for added entities
|
||||
public DbSet<Accounts> Accounts { get; set; }
|
||||
|
|
|
|||
2478
Data.SeaHavenIndustries/Migrations/20260417153405_AddVendorAndDispatch.Designer.cs
generated
Normal file
2478
Data.SeaHavenIndustries/Migrations/20260417153405_AddVendorAndDispatch.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,104 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Data.SeaHavenIndustries.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddVendorAndDispatch : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Vendors",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
CompanyName = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
ContactName = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
Email = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
Phone = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
Address = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
City = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
State = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
Zip = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
TradeSpecialties = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
IsActive = table.Column<bool>(type: "bit", 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_Vendors", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Dispatches",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
WorkOrderId = table.Column<int>(type: "int", nullable: false),
|
||||
VendorId = table.Column<int>(type: "int", nullable: false),
|
||||
PONumber = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
NTEAmount = table.Column<decimal>(type: "decimal(18,2)", nullable: true),
|
||||
Description = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
Status = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
DispatchedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
EmailSent = table.Column<bool>(type: "bit", nullable: false),
|
||||
ReplyToAddress = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
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_Dispatches", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Dispatches_Vendors_VendorId",
|
||||
column: x => x.VendorId,
|
||||
principalTable: "Vendors",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_Dispatches_workOrders_WorkOrderId",
|
||||
column: x => x.WorkOrderId,
|
||||
principalTable: "workOrders",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Dispatches_VendorId",
|
||||
table: "Dispatches",
|
||||
column: "VendorId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Dispatches_WorkOrderId",
|
||||
table: "Dispatches",
|
||||
column: "WorkOrderId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Dispatches");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Vendors");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -627,6 +627,71 @@ namespace Data.SeaHavenIndustries.Migrations
|
|||
b.ToTable("Departments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.SeaHavenIndustries.Dispatch", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime?>("CreatedDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("DeleterUserId")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime?>("DeletionTime")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime?>("DispatchedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<bool>("EmailSent")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool?>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int?>("LastModifierUserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal?>("NTEAmount")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<string>("PONumber")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ReplyToAddress")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("VendorId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("WorkOrderId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("createdby")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("VendorId");
|
||||
|
||||
b.HasIndex("WorkOrderId");
|
||||
|
||||
b.ToTable("Dispatches");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.SeaHavenIndustries.DropdownOption", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
|
@ -1409,6 +1474,70 @@ namespace Data.SeaHavenIndustries.Migrations
|
|||
b.ToTable("Templates");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.SeaHavenIndustries.Vendor", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Address")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("City")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("CompanyName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ContactName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime?>("CreatedDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("DeleterUserId")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime?>("DeletionTime")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool?>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int?>("LastModifierUserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Phone")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("State")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("TradeSpecialties")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Zip")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("createdby")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Vendors");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.SeaHavenIndustries.WorkOrder", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
|
@ -1932,6 +2061,25 @@ namespace Data.SeaHavenIndustries.Migrations
|
|||
b.Navigation("Location");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.SeaHavenIndustries.Dispatch", b =>
|
||||
{
|
||||
b.HasOne("Data.SeaHavenIndustries.Vendor", "Vendor")
|
||||
.WithMany("Dispatches")
|
||||
.HasForeignKey("VendorId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Data.SeaHavenIndustries.WorkOrder", "WorkOrder")
|
||||
.WithMany("Dispatches")
|
||||
.HasForeignKey("WorkOrderId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Vendor");
|
||||
|
||||
b.Navigation("WorkOrder");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.SeaHavenIndustries.Employee", b =>
|
||||
{
|
||||
b.HasOne("Data.SeaHavenIndustries.Department", "Department")
|
||||
|
|
@ -2302,10 +2450,17 @@ namespace Data.SeaHavenIndustries.Migrations
|
|||
b.Navigation("Employees");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.SeaHavenIndustries.Vendor", b =>
|
||||
{
|
||||
b.Navigation("Dispatches");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.SeaHavenIndustries.WorkOrder", b =>
|
||||
{
|
||||
b.Navigation("Comments");
|
||||
|
||||
b.Navigation("Dispatches");
|
||||
|
||||
b.Navigation("Quotes");
|
||||
|
||||
b.Navigation("WorkOrderCategories");
|
||||
|
|
|
|||
23
Data.SeaHavenIndustries/Models/Dispatch.cs
Normal file
23
Data.SeaHavenIndustries/Models/Dispatch.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Data.SeaHavenIndustries
|
||||
{
|
||||
public class Dispatch : FullAuditEntity
|
||||
{
|
||||
public int WorkOrderId { get; set; }
|
||||
[ForeignKey(nameof(WorkOrderId))]
|
||||
public virtual WorkOrder? WorkOrder { get; set; }
|
||||
|
||||
public int VendorId { get; set; }
|
||||
[ForeignKey(nameof(VendorId))]
|
||||
public virtual Vendor? Vendor { get; set; }
|
||||
|
||||
public string? PONumber { get; set; }
|
||||
public decimal? NTEAmount { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public string? Status { get; set; }
|
||||
public DateTime? DispatchedAt { get; set; }
|
||||
public bool EmailSent { get; set; }
|
||||
public string? ReplyToAddress { get; set; }
|
||||
}
|
||||
}
|
||||
17
Data.SeaHavenIndustries/Models/Vendor.cs
Normal file
17
Data.SeaHavenIndustries/Models/Vendor.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
namespace Data.SeaHavenIndustries
|
||||
{
|
||||
public class Vendor : FullAuditEntity
|
||||
{
|
||||
public string? CompanyName { get; set; }
|
||||
public string? ContactName { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public string? Address { get; set; }
|
||||
public string? City { get; set; }
|
||||
public string? State { get; set; }
|
||||
public string? Zip { get; set; }
|
||||
public string? TradeSpecialties { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public ICollection<Dispatch>? Dispatches { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -56,7 +56,6 @@ namespace Data.SeaHavenIndustries
|
|||
public ICollection<WorkOrderContacts>? WorkOrderContacts { get; set; }
|
||||
public ICollection<WorkOrderAttachments>? workOrderAttachments { get; set; }
|
||||
public ICollection<WorkOrderCategories>? WorkOrderCategories { get; set; }
|
||||
|
||||
|
||||
public ICollection<Dispatch>? Dispatches { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue