Compare commits

..

1 commit

Author SHA1 Message Date
npalOmega
19994ef725 calander 2026-04-21 07:59:21 -05:00
168 changed files with 1747 additions and 86050 deletions

3
.gitignore vendored
View file

@ -362,6 +362,3 @@ MigrationBackup/
# Fody - auto-generated XML schema
FodyWeavers.xsd
/SeaHavenIndustries/wwwroot/ChromeEx/Win_x64_1000083_chrome-win/chrome-win
appsettings.Development.json
.DS_Store

View file

@ -7,12 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AWSSDK.DynamoDBv2" Version="4.0.17.9" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="SendGrid" Version="9.29.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="PasswordGenerator" Version="2.1.0" />
@ -20,8 +15,6 @@
<ItemGroup>
<ProjectReference Include="..\Data.SeaHavenIndustries\Data.SeaHavenIndustries.csproj" />
<ProjectReference Include="..\SeaHaven.Services\SeaHaven.Services.csproj" />
<ProjectReference Include="..\SeaHaven.DataServices\SeaHaven.DataServices.csproj" />
</ItemGroup>
</Project>

View file

@ -2,19 +2,16 @@
using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SeaHaven.Services.Interfaces;
using SeaHaven.Services.DTOs;
[ApiController]
[Route("api/Account")]
[Route("api/accounts")]
public class AccountController : ControllerBase
{
private readonly IAccountService _accountService;
private readonly ApplicationDbContext _db;
public AccountController(IAccountService accountService)
public AccountController(ApplicationDbContext db)
{
_accountService = accountService;
_db = db;
}
// -----------------------------------
@ -25,16 +22,34 @@ public class AccountController : ControllerBase
{
try
{
var createDto = new CreateAccountDTO
var model = new Accounts
{
Name = dto.Name ?? throw new ArgumentException("Name is required"),
Name = dto.Name,
Type = dto.Type,
Importance = dto.Importance,
TerritoryId = dto.TerritoryId,
OwnerEmployeeId = dto.OwnerEmployeeId,
PrivacySetting = dto.PrivacySetting,
About = dto.About,
Tags = dto.Tags,
Phone = dto.Phone,
Email = dto.Email,
Website = dto.Website
IndustryId = dto.IndustryId,
TimeZone = dto.TimeZone,
Website = dto.Website,
CurrencyCode = dto.CurrencyCode,
ServiceInstructions = dto.ServiceInstructions,
FacebookUrl = dto.FacebookUrl,
LinkedInUrl = dto.LinkedInUrl,
TwitterUrl = dto.TwitterUrl,
CreatedDate = DateTime.UtcNow
};
var userId = User.Identity?.Name ?? "system";
await _accountService.CreateAccountAsync(createDto, userId);
_db.Accounts.Add(model);
await _db.SaveChangesAsync();
return Ok(new DataResponse
{
@ -60,16 +75,34 @@ public class AccountController : ControllerBase
{
try
{
var updateDto = new UpdateAccountDTO
{
Name = dto.Name,
Phone = dto.Phone,
Email = dto.Email,
Website = dto.Website
};
var account = await _db.Accounts.FirstOrDefaultAsync(a => a.Id == dto.Id);
if (account == null)
return BadRequest(new Response { Status = "Error", Message = "Account not found." });
var userId = User.Identity?.Name ?? "system";
await _accountService.UpdateAccountAsync(dto.Id, updateDto, userId);
account.Name = dto.Name;
account.Type = dto.Type;
account.Importance = dto.Importance;
account.TerritoryId = dto.TerritoryId;
account.OwnerEmployeeId = dto.OwnerEmployeeId;
account.PrivacySetting = dto.PrivacySetting;
account.About = dto.About;
account.Tags = dto.Tags;
account.Phone = dto.Phone;
account.Email = dto.Email;
account.IndustryId = dto.IndustryId;
account.TimeZone = dto.TimeZone;
account.Website = dto.Website;
account.CurrencyCode = dto.CurrencyCode;
account.ServiceInstructions = dto.ServiceInstructions;
account.FacebookUrl = dto.FacebookUrl;
account.LinkedInUrl = dto.LinkedInUrl;
account.TwitterUrl = dto.TwitterUrl;
account.LastModificationTime = DateTime.UtcNow;
await _db.SaveChangesAsync();
return Ok(new DataResponse
{
@ -95,8 +128,12 @@ public class AccountController : ControllerBase
{
try
{
var userId = User.Identity?.Name ?? "system";
await _accountService.DeleteAccountAsync(id, userId);
var account = await _db.Accounts.FirstOrDefaultAsync(a => a.Id == id);
if (account == null)
return BadRequest(new Response { Status = "Error", Message = "Account not found." });
_db.Accounts.Remove(account);
await _db.SaveChangesAsync();
return Ok(new DataResponse
{
@ -118,28 +155,49 @@ public class AccountController : ControllerBase
// GET: api/Account/GetAccountList?search=&page=&pageSize=
// -----------------------------------
[HttpGet("GetAccountList")]
public async Task<IActionResult> GetAccountList(string? search = "", int page = 1, int pageSize = 12)
public IActionResult GetAccountList(string? search = "", int page = 1, int pageSize = 12)
{
var pagedResult = await _accountService.GetAccountsPagedAsync(page, pageSize, search);
search ??= "";
var data = pagedResult.Items.Select(a => new
var query = _db.Accounts
.Where(a => a.IsDeleted == false || a.IsDeleted == null);
if (!string.IsNullOrWhiteSpace(search))
{
Id = a.Id,
Name = a.Name,
Type = a.Type ?? "",
Phone = a.Phone,
Email = a.Email,
Industry = (int?)null, // Map if needed
Website = a.Website
});
var s = search.ToLower();
query = query.Where(a =>
(a.Name ?? "").ToLower().Contains(s) ||
(a.Email ?? "").ToLower().Contains(s) ||
(a.Phone ?? "").Contains(s) ||
(a.Website ?? "").ToLower().Contains(s)
);
}
var totalCount = query.Count();
var data = query
.OrderByDescending(a => a.Id)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(a => new
{
a.Id,
a.Name,
a.Type,
a.Phone,
a.Email,
Industry = a.IndustryId,
a.Website
})
.ToList();
var viewModel = new Pagination_DTO
{
Data = data,
PageNumber = page,
PageSize = pageSize,
TotalCount = pagedResult.TotalCount,
TotalPages = (int)Math.Ceiling(pagedResult.TotalCount / (double)pageSize)
TotalCount = totalCount,
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
};
return Ok(viewModel);

View file

@ -4,8 +4,6 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SeaHaven.Services.Interfaces;
using SeaHaven.Services.DTOs;
namespace Api.SeaHavenIndustries.Controllers
{
@ -15,12 +13,12 @@ namespace Api.SeaHavenIndustries.Controllers
public class AssetController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly IAssetService _assetService;
public AssetController(UserManager<ApplicationUser> userManager, IAssetService assetService)
private readonly ApplicationDbContext _db;
public AssetController(UserManager<ApplicationUser> userManager, ApplicationDbContext db)
{
_userManager = userManager;
_assetService = assetService;
_db = db;
}
[HttpPost]
@ -29,15 +27,17 @@ namespace Api.SeaHavenIndustries.Controllers
{
try
{
var createDto = new CreateAssetDTO
{
Name = asset.Name ?? throw new ArgumentException("Name is required"),
LocationId = asset.LocationId
};
var userId = User.Identity?.Name ?? "system";
await _assetService.CreateAssetAsync(createDto, userId);
Assets model = new Assets();
model.AssetCode = asset.AssetCode;
model.Name = asset.Name;
model.Description = asset.Description;
model.AccountId = asset.AccountId;
model.LocationId = asset.LocationId;
model.IsActive = asset.IsActive;
_db.Assets.Add(model);
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Asset Created Successfully", Status = "200" });
}
catch (Exception ex)
@ -52,15 +52,23 @@ namespace Api.SeaHavenIndustries.Controllers
{
try
{
var updateDto = new UpdateAssetDTO
var exist = _db.Assets.Where(w => w.Id == asset.Id).FirstOrDefault();
if (exist == null)
{
Name = asset.Name,
LocationId = asset.LocationId
};
var userId = User.Identity?.Name ?? "system";
await _assetService.UpdateAssetAsync(asset.Id, updateDto, userId);
return BadRequest(new Response { Status = "Error", Message = "Asset not found!" });
}
exist.AssetCode = asset.AssetCode;
exist.Name = asset.Name;
exist.Description = asset.Description;
exist.AccountId = asset.AccountId;
exist.LocationId = asset.LocationId;
exist.IsActive = asset.IsActive;
_db.Assets.Update(exist);
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Asset Updated Successfully", Status = "200" });
}
catch (Exception ex)
@ -75,8 +83,18 @@ namespace Api.SeaHavenIndustries.Controllers
{
try
{
var userId = User.Identity?.Name ?? "system";
await _assetService.DeleteAssetAsync(id, userId);
var exist = _db.Assets.Where(w => w.Id == id).FirstOrDefault();
if (exist != null)
{
_db.Remove(exist);
}
else
{
return BadRequest(new Response { Status = "Error", Message = "Asset not found!" });
}
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Asset Deleted Successfully", Status = "200" });
}
catch (Exception ex)
@ -91,13 +109,17 @@ namespace Api.SeaHavenIndustries.Controllers
{
try
{
var asset = await _assetService.GetAssetByIdWithDetailsAsync(id);
var asset = await _db.Assets
.Include(a => a.Account)
.Include(a => a.Location)
.Where(w => w.Id == id)
.FirstOrDefaultAsync();
if (asset == null)
{
return NotFound(new Response { Status = "Error", Message = "Asset not found" });
}
return Ok(asset);
}
catch (Exception ex)
@ -108,25 +130,48 @@ namespace Api.SeaHavenIndustries.Controllers
[HttpGet]
[Route("GetAssetList")]
public async Task<IActionResult> GetAssetList(string? search = "", int page = 1, int pageSize = 10)
public IActionResult GetAssetList(string? search = "", int page = 1, int pageSize = 10)
{
var pagedResult = await _assetService.GetAssetsPagedAsync(page, pageSize, search);
// Build the query with search filter
var query = _db.Assets
.Include(a => a.Account)
.Include(a => a.Location)
.Where(s =>
string.IsNullOrEmpty(search) ||
(s.Name != null && s.Name.ToLower().Contains(search.ToLower())) ||
(s.AssetCode != null && s.AssetCode.ToLower().Contains(search.ToLower())) ||
(s.Description != null && s.Description.ToLower().Contains(search.ToLower()))
);
var data = pagedResult.Items.Select(a => new
{
a.Id,
a.Name,
LocationName = a.LocationName ?? "",
a.CreatedDate
});
// Get total count AFTER applying search filter
var totalCount = query.Count();
// Get paginated data
var data = query
.Select(n => new
{
n.Id,
n.AssetCode,
n.Name,
n.Description,
n.IsActive,
AccountName = n.Account != null ? n.Account.Name : "",
LocationName = n.Location != null ? n.Location.Name : ""
})
.OrderByDescending(d => d.Id)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToList();
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
var viewModel = new Pagination_DTO
{
Data = data,
PageNumber = page,
PageSize = pageSize,
TotalCount = pagedResult.TotalCount,
TotalPages = (int)Math.Ceiling(pagedResult.TotalCount / (double)pageSize)
TotalCount = totalCount,
TotalPages = totalPages
};
return Ok(viewModel);

View file

@ -4,11 +4,13 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Mail;
using System.Net;
using System.Security.Claims;
using System.Text;
using Api.SeaHavenIndustries.DTOs;
using Api.SeaHavenIndustries.Helper;
using Microsoft.EntityFrameworkCore;
using System.Diagnostics;
namespace Api.SeaHavenIndustries.Controllers
{
@ -19,14 +21,12 @@ namespace Api.SeaHavenIndustries.Controllers
private readonly UserManager<ApplicationUser> _userManager;
private readonly IConfiguration _configuration;
private readonly ApplicationDbContext _db;
private readonly SendMessage _sendMessage;
public AuthenticationController(UserManager<ApplicationUser> userManager, IConfiguration configuration, ApplicationDbContext db, SendMessage sendMessage)
public AuthenticationController(UserManager<ApplicationUser> userManager, IConfiguration configuration, ApplicationDbContext db, IWebHostEnvironment webHostEnvironment, IHttpContextAccessor httpContext)
{
_userManager = userManager;
_configuration = configuration;
_db = db;
_sendMessage = sendMessage;
}
[AllowAnonymous]
@ -34,45 +34,36 @@ namespace Api.SeaHavenIndustries.Controllers
[Route("login")]
public async Task<IActionResult> Login([FromBody] LoginModel model)
{
try
var user = await _userManager.FindByNameAsync(model.Username ?? "");
if (user.IsDeleted != true && user != null && await _userManager.CheckPasswordAsync(user, model.Password ?? ""))
{
var user = await _userManager.FindByNameAsync(model.Username ?? "");
if (user != null && user.IsDeleted != true && await _userManager.CheckPasswordAsync(user, model.Password ?? ""))
{
// Standard login without 2FA
var userRoles = await _userManager.GetRolesAsync(user);
var authClaims = new List<Claim>
// Standard login without 2FA
var userRoles = await _userManager.GetRolesAsync(user);
var authClaims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName ?? ""),
new Claim(ClaimTypes.NameIdentifier, user.Id),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
foreach (var userRole in userRoles)
{
authClaims.Add(new Claim(ClaimTypes.Role, userRole));
}
var token = GetToken(authClaims);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo,
email = user.Email,
userRoles = userRoles.FirstOrDefault(),
phoneNumber = user.PhoneNumber,
fullname = user.FirstName + " " + user.LastName,
id = user.Id
});
foreach (var userRole in userRoles)
{
authClaims.Add(new Claim(ClaimTypes.Role, userRole));
}
// Invalid credentials
return Unauthorized();
var token = GetToken(authClaims);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo,
email = user.Email,
userRoles = userRoles.FirstOrDefault(),
phoneNumber = user.PhoneNumber,
fullname = user.FirstName + " " + user.LastName,
id = user.Id
});
}
catch (Exception ex)
{
return StatusCode(500, ex.Message);
}
// Invalid credentials
return Unauthorized();
}
[Route("ChangePassword")]
@ -159,7 +150,7 @@ namespace Api.SeaHavenIndustries.Controllers
_db.ForgetPasswordCodes.Add(forgetPasswordCode);
_db.SaveChanges();
string body = $"Your Password Reset Code is: " + forgetPasswordCode.Code;
await _sendMessage.SendEMail(user.Email, "Forget Password Request.", body);
SendEMail(user.Email, "Forget Password Request.", body);
return Ok(new Response { Status = "Success ", Message = "Please check your email for code" });
}
else
@ -220,10 +211,42 @@ namespace Api.SeaHavenIndustries.Controllers
}
}
[HttpGet()]
public bool SendEMail(string emailid, string subject, string body)
{
try
{
MailMessage mail = new MailMessage();
mail.From = new MailAddress("infoesquall@codevoir.com"); //IMPORTANT: This must be same as your smtp authentication address.
mail.To.Add(emailid);
//set the content
mail.Subject = subject;
mail.Body = body;
mail.IsBodyHtml = true;
//send the message
SmtpClient smtp = new SmtpClient("mail.codevoir.com");
//IMPORANT: Your smtp login email MUST be same as your FROM address.
NetworkCredential Credentials = new NetworkCredential("infoesquall@codevoir.com", "Abc123!@#");
smtp.UseDefaultCredentials = false;
smtp.Credentials = Credentials;
smtp.Port = 8889; //25 alternative port number is 8889
smtp.EnableSsl = false;
smtp.Send(mail);
return true;
}
catch (Exception ex)
{
return false;
}
}
private Random _random = new Random();
private string GenerateRandomNo()
{
return _random.Next(0, 999999).ToString("D6");
return _random.Next(0, 9999).ToString("D4");
}
#endregion
}

View file

@ -0,0 +1,306 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Api.SeaHavenIndustries.DTOs;
using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace Api.SeaHavenIndustries.Controllers
{
//[Authorize] // Uncomment when authentication is needed
[ApiController]
[Route("api/Calendar")]
public class CalendarController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly ApplicationDbContext _db;
public CalendarController(UserManager<ApplicationUser> userManager, ApplicationDbContext db)
{
_userManager = userManager;
_db = db;
}
// ===================================================================================
// CREATE EVENT
// ===================================================================================
[HttpPost]
[Route("AddEvent")]
public async Task<IActionResult> AddEvent(EditEvent_DTO eventDto)
{
try
{
var model = new Events
{
Title = eventDto.Title,
Description = eventDto.Description,
Location = eventDto.Location,
RecordID = eventDto.RecordID,
RecordName = eventDto.RecordName,
RecordTargetType = eventDto.RecordTargetType,
ReminderMinutes = eventDto.ReminderMinutes,
EventColor = eventDto.EventColor,
StartDate = eventDto.StartDate,
StartTime = eventDto.StartTime,
EndDate = eventDto.EndDate,
EndTime = eventDto.EndTime,
AllDay = eventDto.AllDay,
CreatedDate = DateTime.Now,
IsDeleted = false
};
_db.Events.Add(model);
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Event Created Successfully", Status = "200" });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
// ===================================================================================
// UPDATE EVENT
// ===================================================================================
[HttpPost]
[Route("EditEvent")]
public async Task<IActionResult> EditEvent(EditEvent_DTO eventDto)
{
try
{
if (!eventDto.Id.HasValue)
{
return BadRequest(new Response { Status = "Error", Message = "Event ID is required for editing" });
}
var model = await _db.Events.FindAsync(eventDto.Id.Value);
if (model == null || model.IsDeleted == true)
{
return NotFound(new Response { Status = "Error", Message = "Event not found" });
}
model.Title = eventDto.Title;
model.Description = eventDto.Description;
model.Location = eventDto.Location;
model.RecordID = eventDto.RecordID;
model.RecordName = eventDto.RecordName;
model.RecordTargetType = eventDto.RecordTargetType;
model.ReminderMinutes = eventDto.ReminderMinutes;
model.EventColor = eventDto.EventColor;
model.StartDate = eventDto.StartDate;
model.StartTime = eventDto.StartTime;
model.EndDate = eventDto.EndDate;
model.EndTime = eventDto.EndTime;
model.AllDay = eventDto.AllDay;
model.LastModificationTime = DateTime.Now;
_db.Events.Update(model);
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Event Updated Successfully", Status = "200" });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
// ===================================================================================
// GET EVENT BY ID
// ===================================================================================
[HttpGet]
[Route("GetEventById/{id}")]
public async Task<IActionResult> GetEventById(int id)
{
try
{
var model = await _db.Events
.Where(e => e.Id == id && e.IsDeleted != true)
.FirstOrDefaultAsync();
if (model == null)
{
return NotFound(new Response { Status = "Error", Message = "Event not found" });
}
var dto = new Event_DTO
{
Id = model.Id,
Title = model.Title,
Description = model.Description,
Location = model.Location,
RecordID = model.RecordID,
RecordName = model.RecordName,
RecordTargetType = model.RecordTargetType,
ReminderMinutes = model.ReminderMinutes,
EventColor = model.EventColor,
StartDate = model.StartDate,
StartTime = model.StartTime,
EndDate = model.EndDate,
EndTime = model.EndTime,
AllDay = model.AllDay,
CreatedDate = model.CreatedDate,
createdby = model.createdby
};
return Ok(new DataResponse { Message = "Success", Status = "200", Data = dto });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
// ===================================================================================
// DELETE EVENT
// ===================================================================================
[HttpDelete]
[Route("DeleteEvent/{id}")]
public async Task<IActionResult> DeleteEvent(int id)
{
try
{
var model = await _db.Events.FindAsync(id);
if (model == null)
{
return NotFound(new Response { Status = "Error", Message = "Event not found" });
}
// Soft delete
model.IsDeleted = true;
model.DeletionTime = DateTime.Now;
_db.Events.Update(model);
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Event Deleted Successfully", Status = "200" });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
// ===================================================================================
// GET EVENTS LIST (WITH PAGINATION AND FILTERS)
// ===================================================================================
[HttpGet]
[Route("GetEventList")]
public IActionResult GetEventList(string? search = "", int page = 1, int pageSize = 10)
{
try
{
var query = _db.Events.Where(e => e.IsDeleted != true);
// Apply search filter if provided
if (!string.IsNullOrWhiteSpace(search))
{
var searchLower = search.ToLower();
query = query.Where(e =>
e.Title.ToLower().Contains(searchLower) ||
(e.Description != null && e.Description.ToLower().Contains(searchLower)) ||
(e.Location != null && e.Location.ToLower().Contains(searchLower))
);
}
// IMPORTANT: Calculate totalCount AFTER applying filters
var totalCount = query.Count();
// Apply pagination
var events = query
.OrderByDescending(e => e.StartDate)
.ThenByDescending(e => e.CreatedDate)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(e => new Event_DTO
{
Id = e.Id,
Title = e.Title,
Description = e.Description,
Location = e.Location,
RecordID = e.RecordID,
RecordName = e.RecordName,
RecordTargetType = e.RecordTargetType,
ReminderMinutes = e.ReminderMinutes,
EventColor = e.EventColor,
StartDate = e.StartDate,
StartTime = e.StartTime,
EndDate = e.EndDate,
EndTime = e.EndTime,
AllDay = e.AllDay,
CreatedDate = e.CreatedDate,
createdby = e.createdby
})
.ToList();
return Ok(new DataResponse
{
Message = "Success",
Status = "200",
Data = new { rows = events, totalCount }
});
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
// ===================================================================================
// GET EVENTS FOR CALENDAR (Date Range Filter)
// ===================================================================================
[HttpGet]
[Route("GetEventsForCalendar")]
public IActionResult GetEventsForCalendar([FromQuery] DateTime? start, [FromQuery] DateTime? end)
{
try
{
var query = _db.Events.Where(e => e.IsDeleted != true);
// Filter by date range if provided
if (start.HasValue && end.HasValue)
{
query = query.Where(e =>
(e.StartDate >= start.Value && e.StartDate <= end.Value) ||
(e.EndDate >= start.Value && e.EndDate <= end.Value) ||
(e.StartDate <= start.Value && e.EndDate >= end.Value)
);
}
var events = query
.Select(e => new Event_DTO
{
Id = e.Id,
Title = e.Title,
Description = e.Description,
Location = e.Location,
RecordID = e.RecordID,
RecordName = e.RecordName,
RecordTargetType = e.RecordTargetType,
ReminderMinutes = e.ReminderMinutes,
EventColor = e.EventColor,
StartDate = e.StartDate,
StartTime = e.StartTime,
EndDate = e.EndDate,
EndTime = e.EndTime,
AllDay = e.AllDay,
CreatedDate = e.CreatedDate,
createdby = e.createdby
})
.ToList();
return Ok(new DataResponse { Message = "Success", Status = "200", Data = events });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
}
}

View file

@ -30,7 +30,7 @@ namespace Api.SeaHavenIndustries.Controllers
try
{
var allUsersQuery = _db.Users
.Where(w => (w.FirstName ?? "").ToLower().Contains((search ?? "").ToLower()))
.Where(w => w.FirstName.ToLower().Contains(search.ToLower()))
.OrderByDescending(d => d.Id);
var totalCount = allUsersQuery.Count();

View file

@ -5,28 +5,20 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SendGrid.Helpers.Mail;
using SeaHaven.Services.Interfaces;
namespace Api.SeaHavenIndustries.Controllers
{
//[Authorize] // Removed for testing
[ApiController]
[Route("api/Contact")]
[Route("api/contacts")]
public class ContactController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly IContactService _contactService;
private readonly ILocationService _locationService;
public ContactController(
UserManager<ApplicationUser> userManager,
IContactService contactService,
ILocationService locationService)
private readonly ApplicationDbContext _db;
public ContactController(UserManager<ApplicationUser> userManager, ApplicationDbContext db)
{
_userManager = userManager;
_contactService = contactService;
_locationService = locationService;
_db = db;
}
[HttpPost]
[Route("AddContacts")]
@ -34,10 +26,27 @@ namespace Api.SeaHavenIndustries.Controllers
{
try
{
// ✅ Use DTO mapping method instead of manual property assignment
var userId = _userManager.GetUserId(User) ?? "system";
await _contactService.CreateContactAsync(contacts.ToServiceCreateDTO(), userId);
Contacts model = new Contacts();
model.Title = contacts.Title;
model.Owner = contacts.Owner;
model.FirstName = contacts.FirstName;
model.MiddleName = contacts.MiddleName;
model.LastName = contacts.LastName;
model.ContactType = contacts.ContactType;
model.PhoneNumber = contacts.PhoneNumber;
model.Email = contacts.Email;
model.Address1 = contacts.Address1;
model.Address2 = contacts.Address2;
model.City = contacts.City;
model.State = contacts.State;
model.Zip = contacts.Zip;
model.FacebookUrl = contacts.FacebookUrl;
model.LinkedInUrl = contacts.LinkedInUrl;
model.TwitterUrl = contacts.TwitterUrl;
model.LocationId = contacts.LocationId;
model.AccountId = contacts.AccountId;
_db.Contacts.Add(model);
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200" });
}
catch (Exception ex)
@ -52,39 +61,53 @@ namespace Api.SeaHavenIndustries.Controllers
{
try
{
// ✅ Use DTO mapping method instead of manual property assignment
var userId = _userManager.GetUserId(User) ?? "system";
await _contactService.UpdateContactAsync(contacts.Id, contacts.ToServiceUpdateDTO(), userId);
var exist = _db.Contacts.Where(w => w.Id == contacts.Id).FirstOrDefault();
exist.Title = contacts.Title;
exist.Owner = contacts.Owner;
exist.FirstName = contacts.FirstName;
exist.MiddleName = contacts.MiddleName;
exist.LastName = contacts.LastName;
exist.ContactType = contacts.ContactType;
exist.PhoneNumber = contacts.PhoneNumber;
exist.Email = contacts.Email;
exist.Address1 = contacts.Address1;
exist.Address2 = contacts.Address2;
exist.City = contacts.City;
exist.State = contacts.State;
exist.Zip = contacts.Zip;
exist.FacebookUrl = contacts.FacebookUrl;
exist.LinkedInUrl = contacts.LinkedInUrl;
exist.TwitterUrl = contacts.TwitterUrl;
exist.LocationId = contacts.LocationId;
exist.AccountId = contacts.AccountId;
_db.Contacts.Update(exist);
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200" });
}
catch (KeyNotFoundException)
{
return BadRequest(new Response { Status = "Error", Message = "Contact not found" });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
[HttpGet]
[Route("DeleteContacts")]
public async Task<IActionResult> DeleteContacts(int id)
{
try
{
var userId = _userManager.GetUserId(User) ?? "system";
// ✅ Call Service layer (not DataService directly)
await _contactService.DeleteContactAsync(id, userId);
var exist = _db.Contacts.Where(w => w.Id == id).FirstOrDefault();
if (exist != null)
{
_db.Remove(exist);
}
else
{
return BadRequest(new Response { Status = "Error", Message = "ID not matched!" });
}
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Removed Successfully", Status = "200" });
}
catch (KeyNotFoundException)
{
return BadRequest(new Response { Status = "Error", Message = "ID not matched!" });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
@ -92,21 +115,32 @@ namespace Api.SeaHavenIndustries.Controllers
}
[HttpGet]
[Route("GetContactList")]
public async Task<IActionResult> GetContactList(string? search ="", int page = 1, int pageSize = 10)
public IActionResult GetContactList(string? search ="", int page = 1, int pageSize = 10)
{
// ✅ Call Service layer
var pagedResult = await _contactService.GetContactsPagedAsync(page, pageSize, search);
// ✅ Use DTO mapping method (NO manual transformation in controller)
var data = ContactListItem_DTO.FromServiceDTOs(pagedResult.Items);
var totalPages = (int)Math.Ceiling(pagedResult.TotalCount / (double)pageSize);
var totalCount = _db.Contacts.Count();
var data = _db.Contacts.Where(s=>
(s.FirstName != null && s.FirstName.ToLower().Contains(search.ToLower())) ||
(s.LastName != null && s.LastName.ToLower().Contains(search.ToLower())) ||
(s.Email != null && s.Email.ToLower().Contains(search.ToLower())) ||
(s.PhoneNumber != null && s.PhoneNumber.Contains(search.ToLower()))
).Select(n => new
{
n.Id,
Name = (n.FirstName + " " + n.LastName).Trim(),
email = n.Email,
phone = n.PhoneNumber
}).OrderByDescending(d => d.Id).ToList().Skip((page - 1) * pageSize)
.Take(pageSize);
// Calculate the number of pages
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
// Create a view model containing the paged items and pagination metadata
var viewModel = new Pagination_DTO
{
Data = data,
PageNumber = page,
PageSize = pageSize,
TotalCount = pagedResult.TotalCount,
TotalCount = totalCount,
TotalPages = totalPages
};
return Ok(viewModel);
@ -116,54 +150,52 @@ namespace Api.SeaHavenIndustries.Controllers
[Route("GetContacts")]
public async Task<IActionResult> GetContacts()
{
// ✅ Call Service layer
var data = await _contactService.GetAllContactsAsync();
var data = await _db.Contacts.ToListAsync();
return Ok(data);
}
[HttpGet]
[Route("GetLocations")]
public async Task<IActionResult> GetLocations()
{
// ✅ Call Service layer
var locations = await _locationService.GetAllLocationsAsync();
// ✅ Use DTO mapping method (NO manual transformation in controller)
var data = LocationViewModel_DTO.FromServiceDTOs(locations);
var data = await _db.Locations.Select(s => new LocationViewModel
{
Id = s.Id,
Name = s.Title + " - " + s.Name
}).ToListAsync();
return Ok(data);
}
// TODO: ARCHITECTURAL VIOLATION - These methods should NOT be in ContactController
// GetCategories should be in a CategoryController with ICategoryService
// GetUsers should be in a UserController with IUserService
// For now, commenting out to prevent direct database access
[HttpGet]
[Route("GetCategories")]
public async Task<IActionResult> GetCategories()
{
// ❌ TEMPORARY: Needs ICategoryService
return StatusCode(501, new Response { Status = "Error", Message = "Not Implemented - Needs CategoryService" });
var data = await _db.Categories.ToListAsync();
return Ok(data);
}
[HttpGet]
[Route("GetUsers")]
public async Task<IActionResult> GetUsers()
{
// ❌ TEMPORARY: Needs IUserService
return StatusCode(501, new Response { Status = "Error", Message = "Not Implemented - Needs UserService" });
var filteredUsers = await (from user in _db.Users
where !(from userRole in _db.UserRoles
join role in _db.Roles on userRole.RoleId equals role.Id
where role.Name == "Admin" || role.Name == "Manager"
select userRole.UserId).Contains(user.Id)
select user).ToListAsync();
return Ok(filteredUsers);
}
// TODO: ARCHITECTURAL VIOLATION - Location CRUD methods should NOT be in ContactController
// These should be in LocationController with ILocationService
[HttpPost]
[Route("AddLocation")]
public async Task<IActionResult> AddLocation(Location_DTO model)
{
try
{
// ✅ Use Service layer with DTO mapping
var userId = _userManager.GetUserId(User) ?? "system";
await _locationService.CreateLocationAsync(model.ToServiceCreateDTO(), userId);
Locations locations = new Locations();
locations.Title = model.Title;
locations.Name = model.Name;
_db.Locations.Add(locations);
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200" });
}
catch (Exception ex)
@ -171,22 +203,26 @@ namespace Api.SeaHavenIndustries.Controllers
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
[HttpPost]
[Route("EditLocation")]
public async Task<IActionResult> EditLocation(EditLocation_DTO model)
{
try
{
// ✅ Use Service layer with DTO mapping
var userId = _userManager.GetUserId(User) ?? "system";
await _locationService.UpdateLocationAsync(model.Id, model.ToServiceUpdateDTO(), userId);
var exist = _db.Locations.Where(w => w.Id == model.Id).FirstOrDefault();
if (exist != null)
{
exist.Title = model.Title;
exist.Name = model.Name;
}
else
{
return BadRequest(new Response { Status = "Error", Message = "ID not matched!" });
}
_db.Locations.Update(exist);
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200" });
}
catch (KeyNotFoundException)
{
return BadRequest(new Response { Status = "Error", Message = "ID not matched!" });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
@ -199,15 +235,20 @@ namespace Api.SeaHavenIndustries.Controllers
{
try
{
// ✅ Use Service layer
var userId = _userManager.GetUserId(User) ?? "system";
await _locationService.DeleteLocationAsync(Id, userId);
var data1 = await _db.Templates.ToListAsync();
_db.Templates.RemoveRange(data1);
var data = await _db.Locations.Where(w => w.Id == Id).FirstOrDefaultAsync();
if (data != null)
{
_db.Locations.Remove(data ?? new Locations());
}
else
{
return BadRequest(new Response { Status = "Error", Message = "ID not matched!" });
}
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200" });
}
catch (KeyNotFoundException)
{
return BadRequest(new Response { Status = "Error", Message = "ID not matched!" });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
@ -216,25 +257,36 @@ namespace Api.SeaHavenIndustries.Controllers
[HttpGet]
[Route("GetAddressbook")]
public async Task<IActionResult> GetAddressbook(string? search = "", int page = 1, int pageSize = 10)
public IActionResult GetAddressbook(string? search = "", int page = 1, int pageSize = 10)
{
// ✅ Call Service layer
var pagedResult = await _locationService.GetLocationsPagedAsync(page, pageSize, search);
// ✅ Use DTO mapping method (NO manual transformation in controller)
var data = LocationListItem_DTO.FromServiceDTOs(pagedResult.Items);
var totalPages = (int)Math.Ceiling(pagedResult.TotalCount / (double)pageSize);
var totalCount = _db.Locations.Count();
var data = _db.Locations.Where(w=> w.Name.ToLower().Contains(search.ToLower()) || w.Title.ToLower().Contains(search.ToLower())).Select(n => new
{
n.Id,
n.Name,
n.Title
}).OrderByDescending(d => d.Id).ToList().Skip((page - 1) * pageSize)
.Take(pageSize);
// Calculate the number of pages
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
// Create a view model containing the paged items and pagination metadata
var viewModel = new Pagination_DTO
{
Data = data,
PageNumber = page,
PageSize = pageSize,
TotalCount = pagedResult.TotalCount,
TotalCount = totalCount,
TotalPages = totalPages
};
return Ok(viewModel);
}
public class LocationViewModel
{
public int? Id { get; set; }
public string? Name { get; set; }
}
}
}

View file

@ -1,39 +0,0 @@
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 DashboardController : Controller
{
private readonly ApplicationDbContext _db;
public DashboardController(ApplicationDbContext db)
{
_db = db;
}
[HttpGet("Stats")]
public async Task<IActionResult> GetStats()
{
var query = _db.workOrders.Where(w => w.istemplate != true);
var total = await query.CountAsync();
var open = await query.CountAsync(w => w.Status == "Open" || w.Status == "In Progress" || w.Status == "On Hold");
var notDispatched = await query.CountAsync(w => (w.Status == "Open") && (w.AssignTo == null || w.AssignTo == ""));
var completed = await query.CountAsync(w => w.Status == "Done");
return Ok(new
{
total,
open,
notDispatched,
completed
});
}
}
}

View file

@ -1,349 +0,0 @@
using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SeaHaven.Services.Interfaces;
using SeaHaven.Services.DTOs;
namespace Api.SeaHavenIndustries.Controllers
{
[Authorize]
[ApiController]
[Route("api/[controller]")]
[Route("api/dispatches")]
public class DispatchController : Controller
{
private readonly IDispatchService _dispatchService;
private readonly ApplicationDbContext _db; // Keep for complex queries
public DispatchController(IDispatchService dispatchService, ApplicationDbContext db)
{
_dispatchService = dispatchService;
_db = db;
}
[HttpGet]
public async Task<IActionResult> List(
[FromQuery] string? search = null,
[FromQuery] string? status = null,
[FromQuery] int? vendorId = null,
[FromQuery] DateTime? dateFrom = null,
[FromQuery] DateTime? dateTo = null,
[FromQuery] string? sortBy = "dispatchedAt",
[FromQuery] string? sortDir = "desc",
[FromQuery] int page = 1,
[FromQuery] int pageSize = 25)
{
var q = _db.Dispatches
.Include(d => d.Vendor)
.Include(d => d.WorkOrder).ThenInclude(w => w!.Locations)
.Where(d => d.IsDeleted == null || d.IsDeleted == false);
if (!string.IsNullOrWhiteSpace(search))
{
var s = search.ToLower();
q = q.Where(d =>
(d.DispatchNumber ?? "").ToLower().Contains(s) ||
(d.PONumber ?? "").ToLower().Contains(s) ||
(d.Vendor != null && (d.Vendor.CompanyName ?? "").ToLower().Contains(s)) ||
(d.WorkOrder != null && (d.WorkOrder.InternalWONumber ?? "").ToLower().Contains(s)) ||
(d.WorkOrder != null && (d.WorkOrder.WorkerOrderTitle ?? "").ToLower().Contains(s)) ||
(d.WorkOrder != null && d.WorkOrder.Locations != null && (d.WorkOrder.Locations.Name ?? "").ToLower().Contains(s))
);
}
if (!string.IsNullOrWhiteSpace(status))
q = q.Where(d => d.Status == status);
if (vendorId.HasValue)
q = q.Where(d => d.VendorId == vendorId.Value);
if (dateFrom.HasValue)
q = q.Where(d => d.DispatchedAt >= dateFrom.Value);
if (dateTo.HasValue)
{
var end = dateTo.Value.Date.AddDays(1);
q = q.Where(d => d.DispatchedAt < end);
}
var total = await q.CountAsync();
var dir = (sortDir ?? "desc").ToLowerInvariant() == "asc";
switch ((sortBy ?? "").ToLowerInvariant())
{
case "ponumber":
q = dir ? q.OrderBy(d => d.PONumber) : q.OrderByDescending(d => d.PONumber);
break;
case "dispatchnumber":
q = dir ? q.OrderBy(d => d.DispatchNumber) : q.OrderByDescending(d => d.DispatchNumber);
break;
case "vendor":
q = dir ? q.OrderBy(d => d.Vendor!.CompanyName) : q.OrderByDescending(d => d.Vendor!.CompanyName);
break;
case "nte":
case "nteamount":
q = dir ? q.OrderBy(d => d.NTEAmount) : q.OrderByDescending(d => d.NTEAmount);
break;
case "status":
q = dir ? q.OrderBy(d => d.Status) : q.OrderByDescending(d => d.Status);
break;
case "scheduleddate":
q = dir ? q.OrderBy(d => d.ScheduledDate) : q.OrderByDescending(d => d.ScheduledDate);
break;
case "completeddate":
q = dir ? q.OrderBy(d => d.CompletedDate) : q.OrderByDescending(d => d.CompletedDate);
break;
default:
q = dir ? q.OrderBy(d => d.DispatchedAt) : q.OrderByDescending(d => d.DispatchedAt);
break;
}
var rows = await q
.Skip((Math.Max(page, 1) - 1) * pageSize)
.Take(pageSize)
.Select(d => new
{
d.Id,
d.DispatchNumber,
d.PONumber,
d.NTEAmount,
d.Status,
d.DispatchedAt,
d.ScheduledDate,
d.CompletedDate,
d.AcknowledgedAt,
d.VerifiedAt,
VendorId = d.VendorId,
VendorName = d.Vendor != null ? d.Vendor.CompanyName : null,
WorkOrderId = d.WorkOrderId,
PrimaryWOInternalNumber = d.WorkOrder != null ? d.WorkOrder.InternalWONumber : null,
PrimaryWOTitle = d.WorkOrder != null ? d.WorkOrder.WorkerOrderTitle : null,
LocationName = d.WorkOrder != null && d.WorkOrder.Locations != null ? d.WorkOrder.Locations.Name : null,
WorkOrderCount = _db.DispatchWorkOrders.Count(dwo => dwo.DispatchId == d.Id),
PendingUpliftCount = _db.DispatchUpliftRequests.Count(u => u.DispatchId == d.Id && u.Status == "Pending" && (u.IsDeleted == null || u.IsDeleted == false))
})
.ToListAsync();
return Ok(new DataResponse
{
Status = "Success",
Data = new { total, page, pageSize, items = rows }
});
}
[HttpGet("{id:int}")]
public async Task<IActionResult> GetById(int id)
{
var dispatch = await _db.Dispatches
.Include(d => d.Vendor)
.Include(d => d.WorkOrder).ThenInclude(w => w!.Locations)
.FirstOrDefaultAsync(d => d.Id == id);
if (dispatch == null) return NotFound(new Response { Status = "Error", Message = "Dispatch not found" });
var linkedWOs = await _db.DispatchWorkOrders
.Where(dwo => dwo.DispatchId == id)
.Select(dwo => new
{
dwo.WorkOrderId,
InternalWONumber = dwo.WorkOrder != null ? dwo.WorkOrder.InternalWONumber : null,
Title = dwo.WorkOrder != null ? dwo.WorkOrder.WorkerOrderTitle : null,
Priority = dwo.WorkOrder != null ? dwo.WorkOrder.Priority : null,
Status = dwo.WorkOrder != null ? dwo.WorkOrder.Status : null,
LocationName = dwo.WorkOrder != null && dwo.WorkOrder.Locations != null ? dwo.WorkOrder.Locations.Name : null,
Trade = dwo.WorkOrder != null ? dwo.WorkOrder.Trade : null,
SubTrade = dwo.WorkOrder != null ? dwo.WorkOrder.SubTrade : null,
Problem = dwo.WorkOrder != null ? dwo.WorkOrder.Problem : null
}).ToListAsync();
if (linkedWOs.Count == 0 && dispatch.WorkOrderId.HasValue)
{
var wo = await _db.workOrders
.Include(w => w.Locations)
.FirstOrDefaultAsync(w => w.Id == dispatch.WorkOrderId.Value);
if (wo != null)
{
linkedWOs = new[] { new
{
WorkOrderId = wo.Id,
InternalWONumber = wo.InternalWONumber,
Title = wo.WorkerOrderTitle,
Priority = wo.Priority,
Status = wo.Status,
LocationName = wo.Locations != null ? wo.Locations.Name : null,
Trade = wo.Trade,
SubTrade = wo.SubTrade,
Problem = wo.Problem
}}.ToList();
}
}
var checklist = await _db.DispatchChecklistItems
.Where(c => c.DispatchId == id && (c.IsDeleted == null || c.IsDeleted == false))
.OrderBy(c => c.SortOrder)
.Select(c => new { c.Id, c.ItemText, c.IsCompleted, c.CompletedBy, c.CompletedAt, c.SortOrder, c.WorkOrderId })
.ToListAsync();
var signoffs = await _db.DispatchSignoffs
.Where(s => s.DispatchId == id && (s.IsDeleted == null || s.IsDeleted == false))
.Select(s => new { s.Id, s.SignoffType, s.Name, s.Signature, s.SignatureMethod, s.SignedAt })
.ToListAsync();
var rawComments = await (from c in _db.Comments
where c.DispatchId == id && (c.IsDeleted == null || c.IsDeleted == false)
join u in _db.Users on c.UserId equals u.Id into users
from u in users.DefaultIfEmpty()
orderby c.CreatedDate
select new
{
c.Id,
c.Commenttext,
c.Commenter,
c.CommentType,
c.RecordType,
c.CreatedDate,
c.UserId,
UserFirstName = u != null ? u.FirstName : null,
UserLastName = u != null ? u.LastName : null
})
.ToListAsync();
var comments = rawComments.Select(c => new
{
c.Id,
c.Commenttext,
c.Commenter,
c.CommentType,
c.RecordType,
c.CreatedDate,
UserName = string.Join(" ", new[] { c.UserFirstName, c.UserLastName }
.Where(s => !string.IsNullOrWhiteSpace(s))).Trim()
}).ToList();
var rawUplifts = await (from u in _db.DispatchUpliftRequests
where u.DispatchId == id && (u.IsDeleted == null || u.IsDeleted == false)
join dec in _db.Users on u.DecidedByUserId equals dec.Id into decs
from dec in decs.DefaultIfEmpty()
orderby u.CreatedDate descending
select new
{
u.Id,
u.CurrentNTE,
u.RequestedNTE,
u.VendorReason,
u.Status,
u.RequiredTier,
u.RequestedByVendorName,
RequestedAt = u.CreatedDate,
u.DecidedAt,
u.DecisionNote,
DecidedByFirstName = dec != null ? dec.FirstName : null,
DecidedByLastName = dec != null ? dec.LastName : null
})
.ToListAsync();
var upliftRequests = rawUplifts.Select(u => new
{
u.Id,
u.CurrentNTE,
u.RequestedNTE,
Delta = u.RequestedNTE - (u.CurrentNTE ?? 0m),
u.VendorReason,
u.Status,
u.RequiredTier,
u.RequestedByVendorName,
u.RequestedAt,
u.DecidedAt,
u.DecisionNote,
DecidedByName = string.Join(" ", new[] { u.DecidedByFirstName, u.DecidedByLastName }
.Where(s => !string.IsNullOrWhiteSpace(s))).Trim()
}).ToList();
var dispatchNumber = dispatch.DispatchNumber ?? "";
var workOrderIds = linkedWOs.Select(l => l.WorkOrderId).Where(wid => wid > 0).ToList();
var rawAudit = await (from a in _db.WorkOrderAuditLogs
where workOrderIds.Contains(a.WorkOrderId)
&& a.FieldName != null
&& a.FieldName.Contains(dispatchNumber)
join u in _db.Users on a.UserId equals u.Id into users
from u in users.DefaultIfEmpty()
orderby a.CreatedAt descending
select new
{
a.Id,
a.Action,
a.FieldName,
a.OldValue,
a.NewValue,
a.CreatedAt,
a.UserId,
UserFirstName = u != null ? u.FirstName : null,
UserLastName = u != null ? u.LastName : null
})
.ToListAsync();
var auditLog = rawAudit.Select(a => new
{
Id = a.Id,
Action = a.Action,
FieldName = a.FieldName,
OldValue = a.OldValue,
NewValue = a.NewValue,
CreatedAt = a.CreatedAt,
UserName = string.Join(" ", new[] { a.UserFirstName, a.UserLastName }
.Where(s => !string.IsNullOrWhiteSpace(s))).Trim()
}).ToList();
return Ok(new DataResponse
{
Status = "Success",
Data = new
{
dispatch.Id,
dispatch.DispatchNumber,
dispatch.PONumber,
dispatch.NTEAmount,
dispatch.Description,
dispatch.Status,
dispatch.ScheduledDate,
dispatch.CompletedDate,
dispatch.DispatchedAt,
dispatch.AcknowledgedAt,
dispatch.VerifiedAt,
dispatch.VerifiedBy,
dispatch.EmailSent,
dispatch.WorkOrderId,
Vendor = dispatch.Vendor == null ? null : new
{
dispatch.Vendor.Id,
dispatch.Vendor.CompanyName,
dispatch.Vendor.ContactName,
dispatch.Vendor.Email,
dispatch.Vendor.Phone,
dispatch.Vendor.Address,
dispatch.Vendor.City,
dispatch.Vendor.State,
dispatch.Vendor.Zip,
dispatch.Vendor.TradeSpecialties
},
PrimaryLocation = dispatch.WorkOrder?.Locations == null ? null : new
{
dispatch.WorkOrder.Locations.Id,
dispatch.WorkOrder.Locations.Name,
dispatch.WorkOrder.Locations.Address1,
dispatch.WorkOrder.Locations.City,
dispatch.WorkOrder.Locations.State,
dispatch.WorkOrder.Locations.Zip
},
WorkOrders = linkedWOs,
Checklist = checklist,
Signoffs = signoffs,
Comments = comments,
UpliftRequests = upliftRequests,
AuditLog = auditLog
}
});
}
}
}

View file

@ -1,43 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace Api.SeaHavenIndustries.Controllers
{
[ApiController]
[Route("api/dispatch")]
public class DispatchPublicController : Controller
{
[HttpGet("accept/{token}")]
public IActionResult Accept(string token)
{
return Content(BuildHtml(), "text/html");
}
private static string BuildHtml()
{
return @"<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'/>
<meta name='viewport' content='width=device-width, initial-scale=1'/>
<title>Link Expired - Sea Haven Industries</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; background: #f5f5f5; }
.card { background: white; border-radius: 12px; padding: 40px; max-width: 480px; text-align: center; box-shadow: 0 4px 20px rgba(0,0,0,0.1); }
.icon { width: 60px; height: 60px; border-radius: 50%; background: #ffc107; color: white; font-size: 28px; display: flex; align-items: center; justify-content: center; margin: 0 auto 20px; }
h1 { font-size: 22px; color: #333; margin: 0 0 12px; }
p { font-size: 15px; color: #666; line-height: 1.6; margin: 0; }
.footer { margin-top: 30px; font-size: 12px; color: #999; }
</style>
</head>
<body>
<div class='card'>
<div class='icon'>!</div>
<h1>Link No Longer Active</h1>
<p>This dispatch accept link has been retired. Please use the vendor portal link in your most recent dispatch email, or contact your dispatcher for a new link.</p>
<div class='footer'>Sea Haven Industries</div>
</div>
</body>
</html>";
}
}
}

View file

@ -1,87 +0,0 @@
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" });
}
}
}

View file

@ -2,19 +2,16 @@
using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SeaHaven.Services.Interfaces;
using SeaHaven.Services.DTOs;
[ApiController]
[Route("api/Employee")]
[Route("api/employees")]
public class EmployeeController : Controller
{
private readonly IEmployeeService _employeeService;
private readonly ApplicationDbContext _db;
public EmployeeController(IEmployeeService employeeService)
public EmployeeController(ApplicationDbContext db)
{
_employeeService = employeeService;
_db = db;
}
// -----------------------------------
@ -23,27 +20,42 @@ public class EmployeeController : Controller
[HttpPost("AddEmployees")]
public async Task<IActionResult> AddEmployees(AddEmployee_DTO dto)
{
try
var employee = new Employee
{
var createDto = new CreateEmployeeDTO
FirstName = dto.FirstName,
LastName = dto.LastName,
JobTitleId = dto.JobTitleId,
PermissionGroupId = dto.PermissionGroupId,
Status = dto.Status,
LastLogin = dto.LastLogin,
CreatedDate = DateTime.UtcNow
};
if (!string.IsNullOrWhiteSpace(dto.Email))
{
employee.Emails.Add(new EmployeeEmail
{
FirstName = dto.FirstName,
LastName = dto.LastName,
JobTitleId = dto.JobTitleId,
DepartmentId = null, // Map from PermissionGroupId if needed
Email = dto.Email,
Phone = dto.Phone
};
var userId = User.Identity?.Name ?? "system";
await _employeeService.CreateEmployeeAsync(createDto, userId);
return Ok(new { Message = "Employee added successfully" });
Address = dto.Email,
IsPrimary = true,
EmailTypeId = 1
});
}
catch (Exception ex)
if (!string.IsNullOrWhiteSpace(dto.Phone))
{
return BadRequest(new { Message = ex.Message });
employee.Phones.Add(new EmployeePhone
{
Number = dto.Phone,
Extension = dto.PhoneExtension,
IsPrimary = true,
PhoneTypeId = 1
});
}
_db.Employees.Add(employee);
await _db.SaveChangesAsync();
return Ok(new { Message = "Employee added successfully" });
}
// -----------------------------------
@ -52,54 +64,87 @@ public class EmployeeController : Controller
[HttpPost("EditEmployees")]
public async Task<IActionResult> EditEmployees(EditEmployee_DTO dto)
{
try
{
var updateDto = new UpdateEmployeeDTO
{
FirstName = dto.FirstName,
LastName = dto.LastName,
JobTitleId = dto.JobTitleId
};
var employee = await _db.Employees
.Include(e => e.Emails)
.Include(e => e.Phones)
.FirstOrDefaultAsync(e => e.Id == dto.Id);
var userId = User.Identity?.Name ?? "system";
await _employeeService.UpdateEmployeeAsync(dto.Id, updateDto, userId);
if (employee == null)
return BadRequest("Employee not found");
return Ok(new { Message = "Employee updated successfully" });
}
catch (Exception ex)
{
return BadRequest(new { Message = ex.Message });
}
employee.FirstName = dto.FirstName;
employee.LastName = dto.LastName;
employee.JobTitleId = dto.JobTitleId;
employee.PermissionGroupId = dto.PermissionGroupId;
employee.Status = dto.Status;
employee.LastLogin = dto.LastLogin;
employee.LastModificationTime = DateTime.UtcNow;
await _db.SaveChangesAsync();
return Ok(new { Message = "Employee updated successfully" });
}
// -----------------------------------
// GET: api/Employee/GetEmployeeList
// -----------------------------------
[HttpGet("GetEmployeeList")]
public async Task<IActionResult> GetEmployeeList(string? search = "", int page = 1, int pageSize = 12)
public IActionResult GetEmployeeList(string? search = "", int page = 1, int pageSize = 12)
{
var pagedResult = await _employeeService.GetEmployeesPagedAsync(page, pageSize, search);
search ??= "";
var data = pagedResult.Items.Select(e => new
{
id = e.Id,
firstName = e.FirstName,
lastName = e.LastName,
jobTitle = e.JobTitleName ?? "",
email = e.Email ?? "",
phone = e.Phone ?? "",
department = e.DepartmentName ?? "",
status = "Active" // Map from employee status if available
});
var totalCount = _db.Employees.Where(e => !e.IsDeleted.HasValue || e.IsDeleted == false).Count();
var totalPages = (int)Math.Ceiling(pagedResult.TotalCount / (double)pageSize);
var data = _db.Employees
.Include(e => e.JobTitle)
.Include(e => e.Department)
.Include(e => e.Emails)
.Include(e => e.Phones)
.Where(e => !e.IsDeleted.HasValue || e.IsDeleted == false)
.Where(e =>
string.IsNullOrWhiteSpace(search) ||
(e.FirstName != null && e.FirstName.ToLower().Contains(search.ToLower())) ||
(e.LastName != null && e.LastName.ToLower().Contains(search.ToLower())) ||
e.Emails.Any(x => x.Address != null && x.Address.ToLower().Contains(search.ToLower())) ||
e.Phones.Any(x => x.Number != null && x.Number.Contains(search))
)
.Select(e => new
{
e.Id,
e.FirstName,
e.LastName,
JobTitleName = e.JobTitle != null ? e.JobTitle.Name : "",
DepartmentName = e.Department != null ? e.Department.Name : "",
e.Status,
PrimaryEmail = e.Emails.Where(x => x.IsPrimary == true).Select(x => x.Address).FirstOrDefault(),
PrimaryPhone = e.Phones.Where(x => x.IsPrimary == true).Select(x => x.Number).FirstOrDefault(),
PhoneExtension = e.Phones.Where(x => x.IsPrimary == true).Select(x => x.Extension).FirstOrDefault()
})
.OrderByDescending(e => e.Id)
.ToList()
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(e => new
{
id = e.Id,
firstName = e.FirstName,
lastName = e.LastName,
jobTitle = e.JobTitleName ?? "",
email = e.PrimaryEmail ?? "",
phone = e.PrimaryPhone != null
? e.PrimaryPhone + (e.PhoneExtension != null ? " ext. " + e.PhoneExtension : "")
: "",
department = e.DepartmentName ?? "",
status = e.Status
});
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
var viewModel = new Pagination_DTO
{
Data = data,
PageNumber = page,
PageSize = pageSize,
TotalCount = pagedResult.TotalCount,
TotalCount = totalCount,
TotalPages = totalPages
};
@ -113,8 +158,18 @@ public class EmployeeController : Controller
{
try
{
var userId = User.Identity?.Name ?? "system";
await _employeeService.DeleteEmployeeAsync(id, userId);
var employee = await _db.Employees
.FirstOrDefaultAsync(e => e.Id == id);
if (employee == null)
return BadRequest(new Response
{
Status = "Error",
Message = "Employee not found."
});
_db.Employees.Remove(employee);
await _db.SaveChangesAsync();
return Ok(new DataResponse
{
@ -122,14 +177,6 @@ public class EmployeeController : Controller
Status = "200"
});
}
catch (InvalidOperationException ex)
{
return BadRequest(new Response
{
Status = "Error",
Message = ex.Message
});
}
catch (Exception ex)
{
return BadRequest(new Response

View file

@ -1,164 +1,87 @@
using System.Security.Claims;
using Api.SeaHavenIndustries.DTOs;
using Api.SeaHavenIndustries.DTOs;
using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SeaHaven.Services.Interfaces;
using SeaHaven.Services.DTOs;
using SeaHaven.DataServices.Interfaces;
[ApiController]
[Authorize]
[Route("api/FollowUp")]
[Route("api/followups")]
public class FollowUpController : ControllerBase
{
private readonly IFollowUpService _followUpService;
private readonly IFollowUpDataService _followUpDataService; // Keep for direct entity operations
private readonly ApplicationDbContext _db; // Keep for User lookups
private readonly UserManager<ApplicationUser> _users;
private readonly ApplicationDbContext _db;
public FollowUpController(IFollowUpService followUpService, IFollowUpDataService followUpDataService, ApplicationDbContext db, UserManager<ApplicationUser> users)
public FollowUpController(ApplicationDbContext db)
{
_followUpService = followUpService;
_followUpDataService = followUpDataService;
_db = db;
_users = users;
}
[HttpGet("GetFollowUpList")]
public async Task<IActionResult> GetFollowUps(
public IActionResult GetFollowUps(
string? search = "",
string? status = "Open",
bool completed = false,
int page = 1,
int pageSize = 12)
{
status = string.IsNullOrWhiteSpace(status) ? "Open" : status;
var status = completed ? "Completed" : "Open";
var pagedResult = await _followUpService.GetFollowUpsPagedAsync(page, pageSize, search, status);
var items = pagedResult.Items;
var query = _db.FollowUps
.Include(x => x.Employee)
.Include(x => x.Account)
.Include(x => x.Location)
.Include(x => x.WorkOrder)
.Where(x => x.Status == status);
var rows = items.Select(x => new
if (!string.IsNullOrWhiteSpace(search))
{
Id = x.Id,
Reason = x.Reason,
ScheduleDate = x.ScheduleDate,
ScheduleStartTime = x.ScheduleStartTime,
EmployeeId = x.EmployeeId,
WorkOrderId = x.WorkOrderId,
AccountId = x.AccountId,
LocationId = x.LocationId,
AccountName = x.AccountName ?? "",
AssignedTo = x.EmployeeName ?? "",
LocationName = x.LocationName ?? "",
WorkOrderNumber = "",
InternalWONumber = "",
Status = x.Status,
CreatedDate = x.CreatedDate,
CreatedById = (string?)null
}).ToList();
search = search.ToLower();
var creatorIds = rows
.Select(r => r.CreatedById)
.Where(id => !string.IsNullOrEmpty(id))
.Distinct()
.ToList();
var creators = await _db.Users
.Where(u => creatorIds.Contains(u.Id))
.Select(u => new { u.Id, u.FirstName, u.LastName, u.UserName })
.ToDictionaryAsync(u => u.Id, u => u);
string NameFor(string? userId)
{
if (string.IsNullOrEmpty(userId) || !creators.TryGetValue(userId, out var u)) return "";
var full = ((u.FirstName ?? "") + " " + (u.LastName ?? "")).Trim();
return string.IsNullOrWhiteSpace(full) ? (u.UserName ?? "") : full;
query = query.Where(x =>
(x.Reason ?? "").ToLower().Contains(search) ||
(x.Account != null && (x.Account.Name ?? "").ToLower().Contains(search)) ||
(x.Employee != null &&
(
((x.Employee.FirstName ?? "") + " " + (x.Employee.LastName ?? ""))
.ToLower()
.Contains(search)
)
)
);
}
var data = rows.Select(r => new
{
r.Id,
r.Reason,
r.ScheduleDate,
r.ScheduleStartTime,
r.EmployeeId,
r.WorkOrderId,
r.AccountId,
r.LocationId,
r.AccountName,
r.AssignedTo,
r.LocationName,
r.WorkOrderNumber,
r.InternalWONumber,
r.Status,
r.CreatedDate,
r.CreatedById,
CreatedByName = NameFor(r.CreatedById)
}).ToList();
var totalCount = query.Count();
var data = query
.OrderByDescending(x => x.Id)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(x => new
{
x.Id,
x.Reason,
x.ScheduleDate,
Account = x.Account != null ? x.Account.Name : "",
AssignedTo = x.Employee != null
? (x.Employee.FirstName + " " + x.Employee.LastName)
: "",
LocationName = x.Location != null ? x.Location.Name : "",
WorkOrderNumber = x.WorkOrder != null ? x.WorkOrder.WorkerOrderNumber : "",
x.Status
})
.ToList();
return Ok(new Pagination_DTO
{
Data = data,
PageNumber = page,
PageSize = pageSize,
TotalCount = pagedResult.TotalCount,
TotalPages = (int)Math.Ceiling(pagedResult.TotalCount / (double)pageSize)
TotalCount = totalCount,
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
});
}
[HttpGet("GetById")]
public async Task<IActionResult> GetById(int id)
{
var model = await _followUpDataService.GetByIdWithDetailsAsync(id);
if (model == null) return NotFound();
string createdByName = "";
if (!string.IsNullOrEmpty(model.createdby))
{
var u = await _db.Users
.Where(x => x.Id == model.createdby)
.Select(x => new { x.FirstName, x.LastName, x.UserName })
.FirstOrDefaultAsync();
if (u != null)
{
var n = ((u.FirstName ?? "") + " " + (u.LastName ?? "")).Trim();
createdByName = string.IsNullOrWhiteSpace(n) ? (u.UserName ?? "") : n;
}
}
return Ok(new
{
model.Id,
model.Reason,
model.ScheduleDate,
model.ScheduleStartTime,
model.EmployeeId,
model.WorkOrderId,
model.AccountId,
model.LocationId,
AccountName = model.Account?.Name ?? "",
AssignedTo = model.Employee != null
? (model.Employee.FirstName + " " + model.Employee.LastName)
: "",
LocationName = model.Location?.Name ?? "",
WorkOrderNumber = model.WorkOrder?.WorkerOrderNumber ?? "",
InternalWONumber = model.WorkOrder?.InternalWONumber ?? "",
model.Status,
model.CreatedDate,
CreatedById = model.createdby,
CreatedByName = createdByName
});
}
[HttpPost("Create")]
public async Task<IActionResult> Create(FollowUp_DTO dto)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var model = new FollowUps
{
Reason = dto.Reason,
@ -169,19 +92,19 @@ public class FollowUpController : ControllerBase
AccountId = dto.AccountId,
LocationId = dto.LocationId,
Status = "Open",
CreatedDate = DateTime.UtcNow,
createdby = userId
CreatedDate = DateTime.UtcNow
};
await _followUpDataService.AddAsync(model);
_db.FollowUps.Add(model);
await _db.SaveChangesAsync();
return Ok(new DataResponse { Status = "200", Message = "Follow Up created", Data = model.Id });
return Ok(new DataResponse { Status = "200", Message = "Follow Up created" });
}
[HttpPost("Update")]
public async Task<IActionResult> Update(FollowUp_DTO dto)
{
var model = await _followUpDataService.GetByIdAsync(dto.Id);
var model = await _db.FollowUps.FindAsync(dto.Id);
if (model == null) return NotFound();
model.Reason = dto.Reason;
@ -193,19 +116,19 @@ public class FollowUpController : ControllerBase
model.LocationId = dto.LocationId;
model.LastModificationTime = DateTime.UtcNow;
await _followUpDataService.UpdateAsync(model);
await _db.SaveChangesAsync();
return Ok(new DataResponse { Status = "200", Message = "Follow Up updated" });
}
[HttpPost("Complete")]
public async Task<IActionResult> Complete(int id)
{
var model = await _followUpDataService.GetByIdAsync(id);
var model = await _db.FollowUps.FindAsync(id);
if (model == null) return NotFound();
model.Status = "Completed";
model.LastModificationTime = DateTime.UtcNow;
await _followUpDataService.UpdateAsync(model);
await _db.SaveChangesAsync();
return Ok();
}
@ -213,24 +136,13 @@ public class FollowUpController : ControllerBase
[HttpPost("Cancel")]
public async Task<IActionResult> Cancel(int id)
{
var model = await _followUpDataService.GetByIdAsync(id);
var model = await _db.FollowUps.FindAsync(id);
if (model == null) return NotFound();
model.Status = "Cancelled";
model.LastModificationTime = DateTime.UtcNow;
await _followUpDataService.UpdateAsync(model);
await _db.SaveChangesAsync();
return Ok();
}
[HttpPost("Delete")]
public async Task<IActionResult> Delete(int id)
{
var model = await _followUpDataService.GetByIdAsync(id);
if (model == null) return NotFound();
await _followUpDataService.DeleteAsync(id);
return Ok(new DataResponse { Status = "200", Message = "Follow Up deleted" });
}
}

View file

@ -1,170 +0,0 @@
using Api.SeaHavenIndustries.DTOs;
using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SeaHaven.Services.Interfaces;
using SeaHaven.Services.DTOs;
using SeaHaven.DataServices.Interfaces;
namespace Api.SeaHavenIndustries.Controllers
{
[Authorize]
[ApiController]
[Route("api/[controller]")]
[Route("api/locations")]
public class LocationController : Controller
{
private readonly ILocationService _locationService;
private readonly ILocationDataService _locationDataService; // Keep for direct entity operations
public LocationController(ILocationService locationService, ILocationDataService locationDataService)
{
_locationService = locationService;
_locationDataService = locationDataService;
}
[HttpGet("GetLocationList")]
public async Task<IActionResult> GetLocationList(string? search = "", int page = 1, int pageSize = 10)
{
var pagedResult = await _locationService.GetLocationsPagedAsync(page, pageSize, search);
var data = pagedResult.Items.Select(l => new
{
Id = l.Id,
Name = l.Name,
Title = l.Title,
Address = l.Address1,
Address1 = l.Address1,
Address2 = l.Address2,
City = l.City,
State = l.State,
ZipCode = l.Zip,
Phone = l.PhoneNumber,
Contact = (string?)null,
ContactEmail = l.Email,
Status = l.Status
});
var viewModel = new Pagination_DTO
{
Data = data,
PageNumber = page,
PageSize = pageSize,
TotalCount = pagedResult.TotalCount,
TotalPages = (int)Math.Ceiling(pagedResult.TotalCount / (double)pageSize)
};
return Ok(viewModel);
}
[HttpGet("{id}")]
public async Task<IActionResult> GetLocationById(int id)
{
var location = await _locationDataService.GetByIdAsync(id);
if (location == null)
return NotFound(new Response { Status = "Error", Message = "Location not found" });
var result = new
{
location.Id,
location.Name,
location.Title,
Address = location.Address1,
location.Address1,
location.Address2,
location.City,
location.State,
ZipCode = location.Zip,
Phone = location.PhoneNumber,
Contact = (string?)null,
ContactEmail = location.Email,
location.Status
};
return Ok(result);
}
[HttpPost]
public async Task<IActionResult> AddLocation([FromBody] Location_DTO model)
{
try
{
var location = new Locations
{
Name = model.Name,
Title = model.Title,
Address1 = model.Address,
City = model.City,
State = model.State,
Zip = model.ZipCode,
PhoneNumber = model.Phone,
Email = model.ContactEmail,
Status = model.Status,
CreatedDate = DateTime.Now
};
await _locationDataService.AddAsync(location);
return Ok(new DataResponse { Message = "Location Created Successfully", Status = "200" });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
[HttpPut("{id}")]
public async Task<IActionResult> EditLocation(int id, [FromBody] EditLocation_DTO model)
{
try
{
var exist = await _locationDataService.GetByIdAsync(id);
if (exist == null)
return BadRequest(new Response { Status = "Error", Message = "Location not found" });
exist.Name = model.Name;
exist.Title = model.Title;
exist.Address1 = model.Address;
exist.City = model.City;
exist.State = model.State;
exist.Zip = model.ZipCode; // This is a property, not a method
exist.PhoneNumber = model.Phone;
exist.Email = model.ContactEmail;
exist.Status = model.Status;
exist.LastModificationTime = DateTime.Now;
await _locationDataService.UpdateAsync(exist);
return Ok(new DataResponse { Message = "Location Updated Successfully", Status = "200" });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteLocation(int id)
{
try
{
var exist = await _locationDataService.GetByIdAsync(id);
if (exist == null)
return BadRequest(new Response { Status = "Error", Message = "Location not found" });
await _locationDataService.DeleteAsync(id);
return Ok(new DataResponse { Message = "Location Deleted Successfully", Status = "200" });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
[HttpGet("DeleteLocation")]
public async Task<IActionResult> DeleteLocationByQuery(int id)
{
return await DeleteLocation(id);
}
}
}

View file

@ -2,36 +2,36 @@
using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SeaHaven.Services.Interfaces;
using SeaHaven.Services.DTOs;
[ApiController]
[Route("api/PMSchedule")]
[Route("api/pmschedules")]
public class PMScheduleController : ControllerBase
{
private readonly IPMScheduleService _pmScheduleService;
private readonly ApplicationDbContext _db;
public PMScheduleController(IPMScheduleService pmScheduleService)
public PMScheduleController(ApplicationDbContext db)
{
_pmScheduleService = pmScheduleService;
_db = db;
}
[HttpGet("GetList")]
public async Task<IActionResult> GetList()
public IActionResult GetList()
{
var items = await _pmScheduleService.GetAllPMSchedulesAsync();
var data = items.Select(x => new
{
Id = x.Id,
Name = x.Name,
Frequency = x.Frequency,
StartDate = x.StartDate,
IsActive = x.IsActive,
Asset = x.AssetName,
Location = x.LocationName
});
var data = _db.PMSchedules
.Include(x => x.Asset)
.Include(x => x.Location)
.OrderByDescending(x => x.Id)
.Select(x => new
{
x.Id,
x.Name,
x.Frequency,
x.StartDate,
x.IsActive,
Asset = x.Asset != null ? x.Asset.Name : null,
Location = x.Location != null ? x.Location.Name : null
})
.ToList();
return Ok(data);
}
@ -39,42 +39,31 @@ public class PMScheduleController : ControllerBase
[HttpPost("Save")]
public async Task<IActionResult> Save(PMSchedule_DTO dto)
{
try
{
var userId = User.Identity?.Name ?? "system";
PMSchedules model;
if (dto.Id == 0)
{
var createDto = new CreatePMScheduleDTO
{
Name = dto.Name,
Frequency = dto.Frequency,
StartDate = dto.StartDate,
IsActive = dto.IsActive,
AssetId = dto.AssetId,
LocationId = dto.LocationId
};
await _pmScheduleService.CreatePMScheduleAsync(createDto, userId);
}
else
{
var updateDto = new UpdatePMScheduleDTO
{
Name = dto.Name,
Frequency = dto.Frequency,
StartDate = dto.StartDate,
IsActive = dto.IsActive,
AssetId = dto.AssetId,
LocationId = dto.LocationId
};
await _pmScheduleService.UpdatePMScheduleAsync(dto.Id, updateDto, userId);
}
return Ok();
}
catch (Exception ex)
if (dto.Id == 0)
{
return BadRequest(new { Message = ex.Message });
model = new PMSchedules
{
CreatedDate = DateTime.UtcNow
};
_db.PMSchedules.Add(model);
}
else
{
model = await _db.PMSchedules.FindAsync(dto.Id);
if (model == null) return NotFound();
model.LastModificationTime = DateTime.UtcNow;
}
model.Name = dto.Name;
model.Frequency = dto.Frequency;
model.StartDate = dto.StartDate;
model.IsActive = dto.IsActive;
model.AssetId = dto.AssetId;
model.LocationId = dto.LocationId;
await _db.SaveChangesAsync();
return Ok();
}
}

View file

@ -158,13 +158,13 @@ namespace Api.SeaHavenIndustries.Controllers
public IActionResult GetQuotes(string search = "", int page = 1, int pageSize = 10)
{
var totalCount = _db.Quotes.Count();
var data = _db.Quotes.Include(s => s.WorkOrder).AsNoTracking().Where(w => (w.QuotId ?? "").ToLower().Contains(search.ToLower()) || (w.SiteName ?? "").ToLower().Contains(search.ToLower()) || (w.WorkOrder != null && (w.WorkOrder.WorkerOrderTitle ?? "").ToLower().Contains(search.ToLower()))).Select(s => new
var data = _db.Quotes.Include(s => s.WorkOrder).AsNoTracking().Where(w => w.QuotId.ToLower().Contains(search.ToLower()) || w.SiteName.ToLower().Contains(search.ToLower()) || w.WorkOrder.WorkerOrderTitle.ToLower().Contains(search.ToLower())).Select(s => new
{
s.Id,
QuoteId = "QO-" + s.QuotId,
WorkOrder = s.WorkOrder != null ? s.WorkOrder.WorkerOrderNumber : "",
s.ProjectName,
TotalAmount = "$" + (s.LineItems != null ? s.LineItems.Sum(l => l.Lineitem ?? 0) : 0).ToString(),
TotalAmount = "$" + s.LineItems.Sum(s => s.Lineitem.Value).ToString(),
s.SiteName,
s.SiteId,
s.PO,
@ -174,8 +174,8 @@ namespace Api.SeaHavenIndustries.Controllers
s.ContactId,
s.Date,
lineitems = s.LineItems.Select(q => new { q.Descriptions, q.Lineitem, q.LineitemNumber}),
}).OrderByDescending(d => d.Id).Skip((page - 1) * pageSize)
.Take(pageSize).ToList();
}).OrderByDescending(d => d.Id).ToList().Skip((page - 1) * pageSize)
.Take(pageSize);
// Calculate the number of pages
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
@ -201,8 +201,8 @@ namespace Api.SeaHavenIndustries.Controllers
s.Id,
s.PO,
s.TT,
Title = s.Locations != null ? s.Locations.Title : "",
Name = s.Locations != null ? s.Locations.Name : ""
s.Locations.Title,
s.Locations.Name
}).ToListAsync();
return Ok(data);
}
@ -216,7 +216,7 @@ namespace Api.SeaHavenIndustries.Controllers
s.Id,
s.QuotId,
s.Date,
Name = s.Contacts != null ? (s.Contacts.FirstName + " " + s.Contacts.LastName).Trim() : "",
Name = (s.Contacts.FirstName + " " + s.Contacts.LastName).Trim(),
s.PO,
s.TT,
s.SiteName,
@ -225,7 +225,7 @@ namespace Api.SeaHavenIndustries.Controllers
s.BillingAddress,
s.ShippingAddress,
lineitems = s.LineItems.Select(q => new { q.Descriptions, q.Lineitem }),
TotalAmount = "$" + (s.LineItems != null ? s.LineItems.Sum(l => l.Lineitem ?? 0) : 0).ToString()
TotalAmount = "$" + s.LineItems.Sum(s => s.Lineitem.Value).ToString()
}).FirstOrDefaultAsync();
return Ok(data);
}

View file

@ -47,8 +47,6 @@ namespace Api.SeaHavenIndustries.Controllers
try
{
var exist = _db.Categories.Where(w => w.Id == model.Id).FirstOrDefault();
if (exist == null)
return BadRequest(new Response { Status = "Error", Message = "Category not found" });
exist.Name = model.Name;
_db.Categories.Update(exist);
await _db.SaveChangesAsync();
@ -104,11 +102,13 @@ namespace Api.SeaHavenIndustries.Controllers
{
s.Id,
s.Name
}).OrderByDescending(d => d.Id).Skip((page - 1) * pageSize)
.Take(pageSize).ToList();
}).OrderByDescending(d => d.Id).ToList().Skip((page - 1) * pageSize)
.Take(pageSize);
// Calculate the number of pages
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
// Create a view model containing the paged items and pagination metadata
var viewModel = new Pagination_DTO
{
Data = data,
@ -127,8 +127,8 @@ namespace Api.SeaHavenIndustries.Controllers
try
{
var totalCount = _db.workOrders.Where(w=> w.istemplate == true).Count();
var data = _db.workOrders.Where(w => w.istemplate == true).OrderByDescending(d => d.Id).Skip((page - 1) * pageSize)
.Take(pageSize).ToList();
var data = _db.workOrders.Where(w => w.istemplate == true).OrderByDescending(d => d.Id).ToList().Skip((page - 1) * pageSize)
.Take(pageSize);
// Calculate the number of pages
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
@ -157,8 +157,8 @@ namespace Api.SeaHavenIndustries.Controllers
try
{
var totalCount = _db.Users.Count();
var allUsers = _db.Users.Where(w => (w.FirstName ?? "").ToLower().Contains((search ?? "").ToLower())).OrderByDescending(d => d.Id).Skip((page - 1) * pageSize)
.Take(pageSize).ToList();
var allUsers = _db.Users.Where(w => w.FirstName.ToLower().Contains(search.ToLower())).OrderByDescending(d => d.Id).ToList().Skip((page - 1) * pageSize)
.Take(pageSize);
// Calculate the number of pages
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);

View file

@ -1,423 +0,0 @@
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
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 SyncController : Controller
{
private readonly ApplicationDbContext _db;
private readonly AmazonDynamoDBClient _dynamo;
private const string WO_TABLE = "WorkOrders";
private const string COMMENTS_TABLE = "WorkOrderComments";
private const string VENDOR_REPLIES_TABLE = "VendorReplies";
public SyncController(ApplicationDbContext db)
{
_db = db;
_dynamo = new AmazonDynamoDBClient(Amazon.RegionEndpoint.USEast1);
}
[HttpPost("WorkOrders")]
public async Task<IActionResult> SyncWorkOrders()
{
var synced = 0;
var created = 0;
var updated = 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;
do
{
var request = new ScanRequest
{
TableName = WO_TABLE,
Limit = 100,
ExclusiveStartKey = lastKey
};
var response = await _dynamo.ScanAsync(request);
foreach (var item in response.Items)
{
var externalId = GetString(item, "work_order_id");
if (string.IsNullOrEmpty(externalId)) continue;
var existing = await _db.workOrders
.FirstOrDefaultAsync(w => w.ExternalWorkOrderId == externalId);
var locationId = await ResolveLocationId(
GetString(item, "site_code"),
GetString(item, "building"),
GetString(item, "address"));
if (locationId.HasValue && !locationsCreated.Equals(0))
locationsCreated++;
if (existing == null)
{
var wo = new WorkOrder
{
InternalWONumber = (nextInternal++).ToString("D8"),
ExternalWorkOrderId = externalId,
WorkerOrderNumber = externalId,
WorkerOrderTitle = GetString(item, "description"),
Description = GetString(item, "description"),
Status = MapStatus(GetString(item, "wo_status")),
Priority = MapSeverityToPriority(GetString(item, "severity")),
Severity = GetString(item, "severity"),
Customer = GetString(item, "customer"),
SiteCode = GetString(item, "site_code"),
Building = GetString(item, "building"),
LocationId = locationId,
DueDate = ParseDate(GetString(item, "due_date")),
DateReported = ParseDate(GetString(item, "date_reported")),
ScheduledStart = ParseDate(GetString(item, "scheduled_start")),
AssignTo = null,
SourceEmailS3Key = GetString(item, "source_email_s3_key"),
CreatedDate = ParseDate(GetString(item, "created_at")) ?? DateTime.UtcNow,
istemplate = false
};
_db.workOrders.Add(wo);
created++;
}
else
{
existing.WorkerOrderTitle = GetString(item, "description") ?? existing.WorkerOrderTitle;
existing.Description = GetString(item, "description") ?? existing.Description;
existing.Status = MapStatus(GetString(item, "wo_status")) ?? existing.Status;
existing.Priority = MapSeverityToPriority(GetString(item, "severity")) ?? existing.Priority;
existing.Severity = GetString(item, "severity") ?? existing.Severity;
existing.SiteCode = GetString(item, "site_code") ?? existing.SiteCode;
existing.Building = GetString(item, "building") ?? existing.Building;
existing.LocationId = locationId ?? existing.LocationId;
existing.DueDate = ParseDate(GetString(item, "due_date")) ?? existing.DueDate;
existing.DateReported = ParseDate(GetString(item, "date_reported")) ?? existing.DateReported;
existing.ScheduledStart = ParseDate(GetString(item, "scheduled_start")) ?? existing.ScheduledStart;
existing.SourceEmailS3Key = GetString(item, "source_email_s3_key") ?? existing.SourceEmailS3Key;
updated++;
}
synced++;
if (synced % 100 == 0)
await _db.SaveChangesAsync();
}
lastKey = response.LastEvaluatedKey;
} while (lastKey != null && lastKey.Count > 0);
await _db.SaveChangesAsync();
return Ok(new
{
synced,
created,
updated,
locationsCreated = await _db.Locations.CountAsync()
});
}
[HttpPost("Comments")]
public async Task<IActionResult> SyncComments()
{
var synced = 0;
var created = 0;
var skipped = 0;
Dictionary<string, AttributeValue>? lastKey = null;
do
{
var request = new ScanRequest
{
TableName = COMMENTS_TABLE,
Limit = 100,
ExclusiveStartKey = lastKey
};
var response = await _dynamo.ScanAsync(request);
foreach (var item in response.Items)
{
var externalCommentId = GetString(item, "comment_id");
var externalWoId = GetString(item, "work_order_id");
if (string.IsNullOrEmpty(externalCommentId) || string.IsNullOrEmpty(externalWoId))
continue;
var alreadyExists = await _db.Comments
.AnyAsync(c => c.ExternalCommentId == externalCommentId);
if (alreadyExists)
{
skipped++;
synced++;
continue;
}
var workOrder = await _db.workOrders
.FirstOrDefaultAsync(w => w.ExternalWorkOrderId == externalWoId);
if (workOrder == null)
{
skipped++;
continue;
}
var comment = new Comments
{
ExternalCommentId = externalCommentId,
WorkerOrderId = workOrder.Id,
Commenttext = GetString(item, "text"),
Commenter = GetString(item, "commenter"),
RecordType = GetString(item, "record_type"),
CommentType = "customer",
CreatedDate = ParseDate(GetString(item, "created_at")) ?? DateTime.UtcNow,
};
_db.Comments.Add(comment);
created++;
synced++;
if (synced % 100 == 0)
await _db.SaveChangesAsync();
}
lastKey = response.LastEvaluatedKey;
} while (lastKey != null && lastKey.Count > 0);
await _db.SaveChangesAsync();
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("BackfillDispatchNumbers")]
public async Task<IActionResult> BackfillDispatchNumbers()
{
var missing = await _db.Dispatches
.Where(d => d.DispatchNumber == null || d.DispatchNumber == "")
.OrderBy(d => d.Id)
.ToListAsync();
if (missing.Count == 0) return Ok(new { updated = 0 });
int next = 1;
var last = await _db.Dispatches
.Where(d => d.DispatchNumber != null && d.DispatchNumber != "")
.OrderByDescending(d => d.DispatchNumber)
.Select(d => d.DispatchNumber)
.FirstOrDefaultAsync();
if (last != null && last.StartsWith("DSP-") && int.TryParse(last.Substring(4), out var n))
next = n + 1;
foreach (var d in missing)
{
d.DispatchNumber = $"DSP-{next:D5}";
next++;
}
await _db.SaveChangesAsync();
return Ok(new { updated = missing.Count });
}
[HttpPost("VendorReplies")]
public async Task<IActionResult> SyncVendorReplies()
{
var synced = 0;
var created = 0;
var skipped = 0;
Dictionary<string, AttributeValue>? lastKey = null;
do
{
var request = new ScanRequest
{
TableName = VENDOR_REPLIES_TABLE,
Limit = 100,
ExclusiveStartKey = lastKey
};
var response = await _dynamo.ScanAsync(request);
foreach (var item in response.Items)
{
var replyId = GetString(item, "reply_id");
var woNumber = GetString(item, "internal_wo_number");
var dispatchNumber = GetString(item, "dispatch_number");
if (string.IsNullOrEmpty(replyId) || string.IsNullOrEmpty(woNumber))
continue;
var alreadyExists = await _db.Comments
.AnyAsync(c => c.ExternalCommentId == replyId);
if (alreadyExists) { skipped++; synced++; continue; }
var workOrder = await _db.workOrders
.FirstOrDefaultAsync(w => w.InternalWONumber == woNumber);
if (workOrder == null) { skipped++; continue; }
int? dispatchId = null;
if (!string.IsNullOrWhiteSpace(dispatchNumber))
{
var dispatch = await _db.Dispatches
.FirstOrDefaultAsync(d => d.DispatchNumber == dispatchNumber && d.WorkOrderId == workOrder.Id);
dispatchId = dispatch?.Id;
}
var comment = new Comments
{
ExternalCommentId = replyId,
WorkerOrderId = workOrder.Id,
DispatchId = dispatchId,
Commenttext = GetString(item, "reply_text"),
Commenter = GetString(item, "sender_email"),
CommentType = "vendor",
RecordType = "vendor_reply",
CreatedDate = ParseDate(GetString(item, "received_at")) ?? DateTime.UtcNow,
};
_db.Comments.Add(comment);
created++;
synced++;
if (synced % 50 == 0)
await _db.SaveChangesAsync();
}
lastKey = response.LastEvaluatedKey;
} while (lastKey != null && lastKey.Count > 0);
await _db.SaveChangesAsync();
return Ok(new { synced, created, skipped });
}
[HttpPost("BackfillCommentTypes")]
public async Task<IActionResult> BackfillCommentTypes()
{
var updated = await _db.Database.ExecuteSqlRawAsync(
"UPDATE Comments SET CommentType = 'customer' WHERE ExternalCommentId IS NOT NULL AND (CommentType IS NULL OR CommentType = '')");
return Ok(new { updated });
}
[HttpPost("All")]
public async Task<IActionResult> SyncAll()
{
var woResult = await SyncWorkOrders() as OkObjectResult;
var commentResult = await SyncComments() as OkObjectResult;
return Ok(new
{
workOrders = woResult?.Value,
comments = commentResult?.Value
});
}
private async Task<int?> ResolveLocationId(string? siteCode, string? building, string? address)
{
if (string.IsNullOrWhiteSpace(siteCode) && string.IsNullOrWhiteSpace(building))
return null;
var matchCode = siteCode ?? building;
var existing = await _db.Locations
.FirstOrDefaultAsync(l => l.Name == matchCode || l.Title == matchCode);
if (existing != null)
return existing.Id;
var location = new Locations
{
Name = matchCode,
Title = building,
Address1 = address,
Status = "Active"
};
_db.Locations.Add(location);
await _db.SaveChangesAsync();
return location.Id;
}
private static string? GetString(Dictionary<string, AttributeValue> item, string key)
{
if (item.TryGetValue(key, out var val) && val.S != null)
return val.S;
return null;
}
private static DateTime? ParseDate(string? value)
{
if (string.IsNullOrWhiteSpace(value)) return null;
if (DateTime.TryParse(value, out var dt)) return dt;
return null;
}
private static string? MapStatus(string? dynamoStatus)
{
return dynamoStatus switch
{
"new" => "Open",
"assigned" => "Open",
"in_progress" => "In Progress",
"on_hold" => "On Hold",
"completed" => "Done",
"cancelled" => "Cancelled",
"unknown" => "Open",
_ => "Open"
};
}
private static string? MapSeverityToPriority(string? severity)
{
if (string.IsNullOrWhiteSpace(severity)) return null;
return $"Sev {severity}";
}
}
}

View file

@ -1,146 +0,0 @@
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/tasklisttemplates")]
public class TaskListTemplateController : Controller
{
private readonly ApplicationDbContext _db;
public TaskListTemplateController(ApplicationDbContext db)
{
_db = db;
}
[HttpGet]
public async Task<IActionResult> GetAll()
{
var data = await _db.TaskListTemplates
.Where(t => t.IsActive)
.Include(t => t.Items.OrderBy(i => i.SortOrder))
.OrderBy(t => t.Name)
.Select(t => new
{
t.Id,
t.Name,
t.Description,
t.IsActive,
items = t.Items.OrderBy(i => i.SortOrder).Select(i => new
{
i.Id,
i.ItemText,
i.SortOrder
}).ToList()
})
.ToListAsync();
return Ok(data);
}
[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
var template = await _db.TaskListTemplates
.Include(t => t.Items.OrderBy(i => i.SortOrder))
.Where(t => t.Id == id)
.Select(t => new
{
t.Id,
t.Name,
t.Description,
t.IsActive,
items = t.Items.OrderBy(i => i.SortOrder).Select(i => new
{
i.Id,
i.ItemText,
i.SortOrder
}).ToList()
})
.FirstOrDefaultAsync();
if (template == null)
return NotFound(new Response { Status = "Error", Message = "Template not found" });
return Ok(template);
}
[HttpPost]
public async Task<IActionResult> Create([FromBody] TaskListTemplate_DTO model)
{
try
{
var template = new TaskListTemplate
{
Name = model.Name,
Description = model.Description,
IsActive = model.IsActive,
Items = model.Items.Select((item, idx) => new TaskListTemplateItem
{
ItemText = item.ItemText,
SortOrder = item.SortOrder > 0 ? item.SortOrder : idx
}).ToList()
};
_db.TaskListTemplates.Add(template);
await _db.SaveChangesAsync();
return Ok(new { message = "Template created", id = template.Id });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
[HttpPut("{id}")]
public async Task<IActionResult> Update(int id, [FromBody] TaskListTemplate_DTO model)
{
try
{
var template = await _db.TaskListTemplates
.Include(t => t.Items)
.FirstOrDefaultAsync(t => t.Id == id);
if (template == null)
return NotFound(new Response { Status = "Error", Message = "Template not found" });
template.Name = model.Name;
template.Description = model.Description;
template.IsActive = model.IsActive;
_db.TaskListTemplateItems.RemoveRange(template.Items);
template.Items = model.Items.Select((item, idx) => new TaskListTemplateItem
{
TaskListTemplateId = id,
ItemText = item.ItemText,
SortOrder = item.SortOrder > 0 ? item.SortOrder : idx
}).ToList();
await _db.SaveChangesAsync();
return Ok(new { message = "Template updated" });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
var template = await _db.TaskListTemplates.FindAsync(id);
if (template == null)
return NotFound(new Response { Status = "Error", Message = "Template not found" });
template.IsActive = false;
await _db.SaveChangesAsync();
return Ok(new { message = "Template deleted" });
}
}
}

View file

@ -1,241 +0,0 @@
using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Security.Claims;
namespace Api.SeaHavenIndustries.Controllers
{
[Authorize]
[ApiController]
[Route("api/[controller]")]
[Route("api/uplifts")]
public class UpliftController : Controller
{
private readonly ApplicationDbContext _db;
private readonly IConfiguration _config;
public UpliftController(ApplicationDbContext db, IConfiguration config)
{
_db = db;
_config = config;
}
[HttpGet]
public async Task<IActionResult> List([FromQuery] string? status = "Pending", [FromQuery] int? tier = null, [FromQuery] int page = 1, [FromQuery] int pageSize = 25)
{
var query = from u in _db.DispatchUpliftRequests
join d in _db.Dispatches on u.DispatchId equals d.Id
join v in _db.Vendors on d.VendorId equals v.Id into vendors
from v in vendors.DefaultIfEmpty()
where (u.IsDeleted == null || u.IsDeleted == false)
select new { u, d, v };
if (!string.IsNullOrWhiteSpace(status))
query = query.Where(x => x.u.Status == status);
if (tier.HasValue)
query = query.Where(x => x.u.RequiredTier == tier.Value);
var total = await query.CountAsync();
var raw = await query
.OrderByDescending(x => x.u.CreatedDate)
.Skip((Math.Max(page, 1) - 1) * pageSize)
.Take(pageSize)
.Select(x => new
{
x.u.Id,
DispatchId = x.d.Id,
x.d.DispatchNumber,
x.d.PONumber,
WorkOrderId = x.d.WorkOrderId,
VendorCompanyName = x.v != null ? x.v.CompanyName : x.u.RequestedByVendorName,
x.u.CurrentNTE,
x.u.RequestedNTE,
x.u.VendorReason,
x.u.RequiredTier,
x.u.Status,
RequestedAt = x.u.CreatedDate,
x.u.DecidedAt,
x.u.DecisionNote
})
.ToListAsync();
var items = raw.Select(r => new
{
r.Id,
r.DispatchId,
r.DispatchNumber,
r.PONumber,
r.WorkOrderId,
r.VendorCompanyName,
r.CurrentNTE,
r.RequestedNTE,
Delta = r.RequestedNTE - (r.CurrentNTE ?? 0m),
r.VendorReason,
r.RequiredTier,
r.Status,
r.RequestedAt,
r.DecidedAt,
r.DecisionNote,
CanDecide = UserCanApprove(r.RequiredTier)
}).ToList();
return Ok(new DataResponse
{
Status = "Success",
Data = new { total, page, pageSize, items }
});
}
[HttpGet("dispatch/{dispatchId:int}")]
public async Task<IActionResult> ListForDispatch(int dispatchId)
{
var rows = await (from u in _db.DispatchUpliftRequests
where u.DispatchId == dispatchId && (u.IsDeleted == null || u.IsDeleted == false)
join dec in _db.Users on u.DecidedByUserId equals dec.Id into decs
from dec in decs.DefaultIfEmpty()
orderby u.CreatedDate descending
select new
{
u.Id,
u.DispatchId,
u.CurrentNTE,
u.RequestedNTE,
Delta = u.RequestedNTE - (u.CurrentNTE ?? 0m),
u.VendorReason,
u.Status,
u.RequiredTier,
u.RequestedByVendorName,
RequestedAt = u.CreatedDate,
u.DecidedAt,
u.DecisionNote,
DecidedByFirstName = dec != null ? dec.FirstName : null,
DecidedByLastName = dec != null ? dec.LastName : null
}).ToListAsync();
var items = rows.Select(r => new
{
r.Id,
r.DispatchId,
r.CurrentNTE,
r.RequestedNTE,
r.Delta,
r.VendorReason,
r.Status,
r.RequiredTier,
r.RequestedByVendorName,
r.RequestedAt,
r.DecidedAt,
r.DecisionNote,
DecidedByName = string.Join(" ", new[] { r.DecidedByFirstName, r.DecidedByLastName }
.Where(s => !string.IsNullOrWhiteSpace(s))).Trim(),
CanDecide = UserCanApprove(r.RequiredTier)
}).ToList();
return Ok(new DataResponse { Status = "Success", Data = items });
}
[HttpPost("{id:int}/approve")]
public async Task<IActionResult> Approve(int id, [FromBody] DecisionRequest? body)
{
var req = await _db.DispatchUpliftRequests.FirstOrDefaultAsync(u => u.Id == id);
if (req == null) return NotFound(new Response { Status = "Error", Message = "Uplift request not found" });
if (req.Status != "Pending") return BadRequest(new Response { Status = "Error", Message = $"Cannot approve a '{req.Status}' request" });
if (!UserCanApprove(req.RequiredTier))
return StatusCode(403, new Response { Status = "Error", Message = $"Approval requires a Tier {req.RequiredTier} role" });
var dispatch = await _db.Dispatches.FirstOrDefaultAsync(d => d.Id == req.DispatchId);
if (dispatch == null) return NotFound(new Response { Status = "Error", Message = "Dispatch not found" });
if (dispatch.Status == "Verified" || dispatch.Status == "Cancelled")
return BadRequest(new Response { Status = "Error", Message = $"Cannot approve uplift on a '{dispatch.Status}' dispatch" });
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var now = DateTime.UtcNow;
var oldNTE = dispatch.NTEAmount ?? 0m;
dispatch.NTEAmount = req.RequestedNTE;
dispatch.LastModificationTime = now;
req.Status = "Approved";
req.DecidedAt = now;
req.DecidedByUserId = userId;
req.DecisionNote = string.IsNullOrWhiteSpace(body?.Note) ? null : body!.Note!.Trim();
req.LastModificationTime = now;
_db.WorkOrderAuditLogs.Add(new WorkOrderAuditLog
{
WorkOrderId = dispatch.WorkOrderId ?? 0,
UserId = userId,
FieldName = $"Dispatch {dispatch.DispatchNumber} NTE",
OldValue = $"${oldNTE:F2}",
NewValue = $"${req.RequestedNTE:F2}",
Action = "uplift_approved",
CreatedAt = now
});
await _db.SaveChangesAsync();
return Ok(new DataResponse { Status = "Success", Data = new { req.Id, req.Status, dispatch.NTEAmount } });
}
[HttpPost("{id:int}/deny")]
public async Task<IActionResult> Deny(int id, [FromBody] DecisionRequest body)
{
if (body == null || string.IsNullOrWhiteSpace(body.Note))
return BadRequest(new Response { Status = "Error", Message = "A note is required when denying" });
var req = await _db.DispatchUpliftRequests.FirstOrDefaultAsync(u => u.Id == id);
if (req == null) return NotFound(new Response { Status = "Error", Message = "Uplift request not found" });
if (req.Status != "Pending") return BadRequest(new Response { Status = "Error", Message = $"Cannot deny a '{req.Status}' request" });
if (!UserCanApprove(req.RequiredTier))
return StatusCode(403, new Response { Status = "Error", Message = $"Decision requires a Tier {req.RequiredTier} role" });
var dispatch = await _db.Dispatches.FirstOrDefaultAsync(d => d.Id == req.DispatchId);
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var now = DateTime.UtcNow;
req.Status = "Denied";
req.DecidedAt = now;
req.DecidedByUserId = userId;
req.DecisionNote = body.Note!.Trim();
req.LastModificationTime = now;
_db.WorkOrderAuditLogs.Add(new WorkOrderAuditLog
{
WorkOrderId = dispatch?.WorkOrderId ?? 0,
UserId = userId,
FieldName = $"Dispatch {dispatch?.DispatchNumber} Uplift",
OldValue = "Pending",
NewValue = "Denied",
Action = "uplift_denied",
CreatedAt = now
});
await _db.SaveChangesAsync();
return Ok(new DataResponse { Status = "Success", Data = new { req.Id, req.Status } });
}
[HttpGet("can-approve")]
public IActionResult CanApprove([FromQuery] int tier)
{
return Ok(new DataResponse { Status = "Success", Data = new { canApprove = UserCanApprove(tier) } });
}
private bool UserCanApprove(int requiredTier)
{
var key = $"Approvals:Tier{requiredTier}Roles";
var roles = _config.GetSection(key).Get<string[]>() ?? Array.Empty<string>();
foreach (var r in roles)
{
if (!string.IsNullOrWhiteSpace(r) && User.IsInRole(r)) return true;
}
return false;
}
public class DecisionRequest
{
public string? Note { get; set; }
}
}
}

View file

@ -94,9 +94,7 @@ namespace Api.SeaHavenIndustries.Controllers
else
{
var exist1 = await _userManager.Users.FirstOrDefaultAsync(w => w.Id == model.Id);
if (exist1 == null)
return BadRequest(new Response { Status = "Error", Message = "User not found" });
var exist1 = await _userManager.Users.FirstAsync(w => w.Id == model.Id);
var existingRole = await _userManager.GetRolesAsync(exist1);
@ -136,8 +134,6 @@ namespace Api.SeaHavenIndustries.Controllers
try
{
var exist = _db.Users.Where(w => w.Id == user.Id).FirstOrDefault();
if (exist == null)
return BadRequest(new Response { Status = "Error", Message = "User not found" });
exist.FirstName = user.Name;
exist.EmailConfirmed = true;
exist.UserName = user.Email;

View file

@ -1,205 +0,0 @@
using Api.SeaHavenIndustries.DTOs;
using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SeaHaven.Services.Interfaces;
using SeaHaven.Services.DTOs;
namespace Api.SeaHavenIndustries.Controllers
{
[Authorize]
[ApiController]
[Route("api/[controller]")]
[Route("api/vendors")]
public class VendorController : Controller
{
private readonly IVendorService _vendorService;
private readonly ApplicationDbContext _db; // Keep for complex queries like nearby vendors
private readonly Helper.ZipCodeDistance _zipDistance;
private readonly Helper.VendorPortalTokenService _vendorTokens;
private readonly IConfiguration _config;
public VendorController(IVendorService vendorService, ApplicationDbContext db, Helper.ZipCodeDistance zipDistance, Helper.VendorPortalTokenService vendorTokens, IConfiguration config)
{
_vendorService = vendorService;
_db = db;
_zipDistance = zipDistance;
_vendorTokens = vendorTokens;
_config = config;
}
[HttpGet("GetVendorList")]
public async Task<IActionResult> GetVendorList(string? search = "", int page = 1, int pageSize = 10)
{
var pagedResult = await _vendorService.GetVendorsPagedAsync(page, pageSize, search);
var data = pagedResult.Items.Select(v => new
{
v.Id,
CompanyName = v.Name,
v.Email,
v.Phone
});
return Ok(new Pagination_DTO
{
Data = data,
PageNumber = page,
PageSize = pageSize,
TotalCount = pagedResult.TotalCount,
TotalPages = (int)Math.Ceiling(pagedResult.TotalCount / (double)pageSize)
});
}
[HttpGet("{id}")]
public async Task<IActionResult> GetVendorById(int id)
{
var vendor = await _vendorService.GetVendorByIdAsync(id);
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 createDto = new CreateVendorDTO
{
Name = model.CompanyName ?? throw new ArgumentException("CompanyName is required")
};
var userId = User.Identity?.Name ?? "system";
await _vendorService.CreateVendorAsync(createDto, userId);
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 updateDto = new UpdateVendorDTO
{
Name = model.CompanyName
};
var userId = User.Identity?.Name ?? "system";
await _vendorService.UpdateVendorAsync(id, updateDto, userId);
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 userId = User.Identity?.Name ?? "system";
await _vendorService.DeleteVendorAsync(id, userId);
return Ok(new DataResponse { Message = "Vendor Deleted", Status = "200" });
}
[HttpGet("Dropdown")]
public async Task<IActionResult> GetDropdown([FromQuery] string? trade = null, [FromQuery] string? siteZip = 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,
v.Address,
v.City,
v.State,
v.Zip
})
.ToListAsync();
var result = vendors.Select(v =>
{
var distance = _zipDistance.GetDistanceMiles(siteZip, v.Zip);
var addr = new[] { v.Address, v.City, v.State, v.Zip }
.Where(s => !string.IsNullOrWhiteSpace(s));
return new
{
v.Id,
v.CompanyName,
v.TradeSpecialties,
address = string.Join(", ", addr),
distanceMiles = distance.HasValue ? Math.Round(distance.Value, 1) : (double?)null
};
}).ToList();
if (!string.IsNullOrWhiteSpace(trade))
{
var tradeMatched = result.Where(v => (v.TradeSpecialties ?? "").Contains(trade)).ToList();
var rest = result.Where(v => !(v.TradeSpecialties ?? "").Contains(trade)).ToList();
result = tradeMatched.Concat(rest).ToList();
}
if (!string.IsNullOrWhiteSpace(siteZip))
{
result = result.OrderBy(v => v.distanceMiles ?? 99999).ToList();
}
return Ok(result);
}
[HttpGet("{id:int}/portal-token")]
public async Task<IActionResult> GetPortalToken(int id)
{
var vendor = await _db.Vendors.FindAsync(id);
if (vendor == null) return NotFound();
var token = await _vendorTokens.GetOrCreateActiveTokenAsync(id);
return Ok(BuildPortalTokenResponse(token));
}
[HttpPost("{id:int}/portal-token/rotate")]
public async Task<IActionResult> RotatePortalToken(int id)
{
var vendor = await _db.Vendors.FindAsync(id);
if (vendor == null) return NotFound();
var token = await _vendorTokens.RotateAsync(id);
return Ok(BuildPortalTokenResponse(token));
}
[HttpPost("{id:int}/portal-token/revoke")]
public async Task<IActionResult> RevokePortalToken(int id)
{
var vendor = await _db.Vendors.FindAsync(id);
if (vendor == null) return NotFound();
await _vendorTokens.RevokeAllAsync(id);
return Ok(new { revoked = true });
}
private object BuildPortalTokenResponse(VendorAccessToken token)
{
var frontendBase = _config.GetValue<string>("FrontendBaseUrl")?.TrimEnd('/') ?? "";
return new
{
token.Token,
token.IssuedAt,
token.ExpiresAt,
token.LastUsedAt,
PortalUrl = $"{frontendBase}/v/{token.Token}/dashboard"
};
}
}
}

View file

@ -1,696 +0,0 @@
using Api.SeaHavenIndustries.Helper;
using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
namespace Api.SeaHavenIndustries.Controllers
{
[ApiController]
[Route("api/vendor-portal")]
[Route("api/vendorportal")]
public class VendorPortalController : Controller
{
private const string TokenHeader = "X-Vendor-Token";
private static readonly string[] VendorViewableStatuses =
new[] { "Sent", "Acknowledged", "In Progress", "Completed", "Verified", "Cancelled" };
private readonly ApplicationDbContext _db;
private readonly VendorPortalTokenService _tokens;
private readonly IConfiguration _config;
private readonly SendMessage _sendMessage;
public VendorPortalController(ApplicationDbContext db, VendorPortalTokenService tokens, IConfiguration config, SendMessage sendMessage)
{
_db = db;
_tokens = tokens;
_config = config;
_sendMessage = sendMessage;
}
[HttpGet("session")]
public async Task<IActionResult> Session()
{
var vendor = await ResolveVendorAsync();
if (vendor == null) return Unauthorized(new Response { Status = "Error", Message = "Invalid or expired token" });
return Ok(new DataResponse
{
Status = "Success",
Data = new
{
vendor.Id,
vendor.CompanyName,
vendor.ContactName,
vendor.Email,
vendor.Phone
}
});
}
[HttpGet("dispatches")]
public async Task<IActionResult> ListDispatches([FromQuery] string? status = null)
{
var vendor = await ResolveVendorAsync();
if (vendor == null) return Unauthorized(new Response { Status = "Error", Message = "Invalid or expired token" });
var query = _db.Dispatches
.Include(d => d.WorkOrder).ThenInclude(w => w!.Locations)
.Where(d => d.VendorId == vendor.Id && (d.IsDeleted == null || d.IsDeleted == false));
if (!string.IsNullOrWhiteSpace(status))
{
query = query.Where(d => d.Status == status);
}
var dispatches = await query
.OrderByDescending(d => d.DispatchedAt ?? d.CreatedDate)
.Select(d => new
{
d.Id,
d.DispatchNumber,
d.PONumber,
d.Status,
d.NTEAmount,
d.ScheduledDate,
d.CompletedDate,
d.DispatchedAt,
d.AcknowledgedAt,
WorkOrderTitle = d.WorkOrder!.WorkerOrderTitle,
InternalWONumber = d.WorkOrder!.InternalWONumber,
LocationName = d.WorkOrder!.Locations != null ? d.WorkOrder!.Locations.Name : null,
LocationCity = d.WorkOrder!.Locations != null ? d.WorkOrder!.Locations.City : null,
LocationState = d.WorkOrder!.Locations != null ? d.WorkOrder!.Locations.State : null
})
.ToListAsync();
return Ok(new DataResponse { Status = "Success", Data = dispatches });
}
[HttpGet("dispatches/{id:int}")]
public async Task<IActionResult> GetDispatch(int id)
{
var vendor = await ResolveVendorAsync();
if (vendor == null) return Unauthorized(new Response { Status = "Error", Message = "Invalid or expired token" });
var dispatch = await LoadVendorDispatchAsync(id, vendor.Id);
if (dispatch == null) return NotFound(new Response { Status = "Error", Message = "Dispatch not found" });
var checklist = await _db.DispatchChecklistItems
.Where(c => c.DispatchId == id && (c.IsDeleted == null || c.IsDeleted == false))
.OrderBy(c => c.SortOrder)
.Select(c => new { c.Id, c.ItemText, c.IsCompleted, c.CompletedBy, c.CompletedAt })
.ToListAsync();
var signoffs = await _db.DispatchSignoffs
.Where(s => s.DispatchId == id && (s.IsDeleted == null || s.IsDeleted == false))
.Select(s => new { s.Id, s.SignoffType, s.Name, s.SignatureMethod, s.SignedAt })
.ToListAsync();
var rawUplifts = await (from u in _db.DispatchUpliftRequests
where u.DispatchId == id && (u.IsDeleted == null || u.IsDeleted == false)
join dec in _db.Users on u.DecidedByUserId equals dec.Id into decs
from dec in decs.DefaultIfEmpty()
orderby u.CreatedDate descending
select new
{
u.Id,
u.CurrentNTE,
u.RequestedNTE,
u.VendorReason,
u.Status,
u.RequiredTier,
u.RequestedByVendorName,
u.CreatedDate,
u.DecidedAt,
u.DecisionNote,
DecidedByFirstName = dec != null ? dec.FirstName : null,
DecidedByLastName = dec != null ? dec.LastName : null
})
.ToListAsync();
var upliftRequests = rawUplifts.Select(u => new
{
u.Id,
u.CurrentNTE,
u.RequestedNTE,
u.VendorReason,
u.Status,
u.RequiredTier,
u.RequestedByVendorName,
RequestedAt = u.CreatedDate,
u.DecidedAt,
u.DecisionNote,
DecidedByName = string.Join(" ", new[] { u.DecidedByFirstName, u.DecidedByLastName }
.Where(s => !string.IsNullOrWhiteSpace(s))).Trim()
}).ToList();
var rawComments = await (from c in _db.Comments
where c.DispatchId == id
&& c.CommentType != "internal"
&& (c.IsDeleted == null || c.IsDeleted == false)
join u in _db.Users on c.UserId equals u.Id into users
from u in users.DefaultIfEmpty()
orderby c.CreatedDate
select new
{
c.Id,
c.Commenttext,
c.Commenter,
c.CommentType,
c.CreatedDate,
c.UserId,
UserFirstName = u != null ? u.FirstName : null,
UserLastName = u != null ? u.LastName : null
})
.ToListAsync();
var comments = rawComments.Select(c =>
{
var hasShocUser = !string.IsNullOrWhiteSpace(c.UserId);
var userName = string.Join(" ", new[] { c.UserFirstName, c.UserLastName }
.Where(s => !string.IsNullOrWhiteSpace(s))).Trim();
string resolvedType;
string resolvedCommenter;
if (hasShocUser)
{
resolvedType = "dispatcher";
resolvedCommenter = !string.IsNullOrWhiteSpace(userName) ? userName
: !string.IsNullOrWhiteSpace(c.Commenter) ? c.Commenter!
: "Dispatcher";
}
else if (c.CommentType == "customer")
{
resolvedType = "customer";
resolvedCommenter = !string.IsNullOrWhiteSpace(c.Commenter) ? c.Commenter! : "Customer";
}
else
{
resolvedType = "vendor";
resolvedCommenter = !string.IsNullOrWhiteSpace(c.Commenter) ? c.Commenter! : vendor.CompanyName ?? "Vendor";
}
return new
{
c.Id,
c.Commenttext,
Commenter = resolvedCommenter,
CommentType = resolvedType,
c.CreatedDate
};
}).ToList();
return Ok(new DataResponse
{
Status = "Success",
Data = new
{
dispatch.Id,
dispatch.DispatchNumber,
dispatch.PONumber,
dispatch.Status,
dispatch.NTEAmount,
dispatch.Description,
dispatch.ScheduledDate,
dispatch.CompletedDate,
dispatch.DispatchedAt,
dispatch.AcknowledgedAt,
WorkOrder = dispatch.WorkOrder == null ? null : new
{
dispatch.WorkOrder.Id,
dispatch.WorkOrder.InternalWONumber,
dispatch.WorkOrder.WorkerOrderTitle,
dispatch.WorkOrder.Description,
dispatch.WorkOrder.Priority,
dispatch.WorkOrder.DueDate,
dispatch.WorkOrder.Trade,
dispatch.WorkOrder.SubTrade,
dispatch.WorkOrder.Problem
},
Location = dispatch.WorkOrder?.Locations == null ? null : new
{
dispatch.WorkOrder.Locations.Name,
dispatch.WorkOrder.Locations.Address1,
dispatch.WorkOrder.Locations.City,
dispatch.WorkOrder.Locations.State,
dispatch.WorkOrder.Locations.Zip
},
Checklist = checklist,
Signoffs = signoffs,
Comments = comments,
UpliftRequests = upliftRequests
}
});
}
[HttpPost("dispatches/{id:int}/accept")]
public async Task<IActionResult> Accept(int id)
{
var vendor = await ResolveVendorAsync();
if (vendor == null) return Unauthorized(new Response { Status = "Error", Message = "Invalid or expired token" });
var dispatch = await LoadVendorDispatchAsync(id, vendor.Id);
if (dispatch == null) return NotFound(new Response { Status = "Error", Message = "Dispatch not found" });
if (dispatch.Status != "Sent")
{
return BadRequest(new Response { Status = "Error", Message = $"Cannot accept a dispatch with status '{dispatch.Status}'" });
}
var now = DateTime.UtcNow;
dispatch.Status = "Acknowledged";
dispatch.AcknowledgedAt = now;
dispatch.LastModificationTime = now;
_db.WorkOrderAuditLogs.Add(new WorkOrderAuditLog
{
WorkOrderId = dispatch.WorkOrderId ?? 0,
FieldName = $"Dispatch {dispatch.DispatchNumber} Status",
OldValue = "Sent",
NewValue = "Acknowledged",
Action = "vendor_accept",
CreatedAt = now
});
await _db.SaveChangesAsync();
return Ok(new DataResponse { Status = "Success", Data = new { dispatch.Id, dispatch.Status, dispatch.AcknowledgedAt } });
}
[HttpPost("dispatches/{id:int}/status")]
public async Task<IActionResult> ChangeStatus(int id, [FromBody] ChangeStatusRequest body)
{
var vendor = await ResolveVendorAsync();
if (vendor == null) return Unauthorized(new Response { Status = "Error", Message = "Invalid or expired token" });
var dispatch = await LoadVendorDispatchAsync(id, vendor.Id);
if (dispatch == null) return NotFound(new Response { Status = "Error", Message = "Dispatch not found" });
var from = dispatch.Status;
var to = body.Status;
if (!IsAllowedVendorTransition(from, to))
{
return BadRequest(new Response { Status = "Error", Message = $"Transition from '{from}' to '{to}' is not allowed" });
}
var now = DateTime.UtcNow;
dispatch.Status = to;
dispatch.LastModificationTime = now;
if (to == "Completed") dispatch.CompletedDate = now;
_db.WorkOrderAuditLogs.Add(new WorkOrderAuditLog
{
WorkOrderId = dispatch.WorkOrderId ?? 0,
FieldName = $"Dispatch {dispatch.DispatchNumber} Status",
OldValue = from,
NewValue = to,
Action = "vendor_status_change",
CreatedAt = now
});
await _db.SaveChangesAsync();
return Ok(new DataResponse { Status = "Success", Data = new { dispatch.Id, dispatch.Status, dispatch.CompletedDate } });
}
[HttpPost("dispatches/{id:int}/request-cancel")]
public async Task<IActionResult> RequestCancel(int id, [FromBody] RequestCancelRequest body)
{
var vendor = await ResolveVendorAsync();
if (vendor == null) return Unauthorized(new Response { Status = "Error", Message = "Invalid or expired token" });
var dispatch = await LoadVendorDispatchAsync(id, vendor.Id);
if (dispatch == null) return NotFound(new Response { Status = "Error", Message = "Dispatch not found" });
if (dispatch.Status == "Verified" || dispatch.Status == "Cancelled")
{
return BadRequest(new Response { Status = "Error", Message = $"Cannot request cancel on a '{dispatch.Status}' dispatch" });
}
var now = DateTime.UtcNow;
var reason = string.IsNullOrWhiteSpace(body?.Reason) ? "(no reason provided)" : body!.Reason!.Trim();
_db.Comments.Add(new Comments
{
DispatchId = id,
WorkerOrderId = dispatch.WorkOrderId,
Commenter = vendor.CompanyName,
CommentType = "vendor",
RecordType = "cancel_request",
Commenttext = $"Vendor requested cancellation: {reason}",
CreatedDate = now
});
_db.WorkOrderAuditLogs.Add(new WorkOrderAuditLog
{
WorkOrderId = dispatch.WorkOrderId ?? 0,
FieldName = $"Dispatch {dispatch.DispatchNumber}",
OldValue = dispatch.Status,
NewValue = "Cancel Requested",
Action = "vendor_request_cancel",
CreatedAt = now
});
await _db.SaveChangesAsync();
return Ok(new DataResponse { Status = "Success", Message = "Cancel request sent to dispatcher" });
}
[HttpPost("dispatches/{id:int}/checklist/{itemId:int}")]
public async Task<IActionResult> UpdateChecklistItem(int id, int itemId, [FromBody] ChecklistUpdateRequest body)
{
var vendor = await ResolveVendorAsync();
if (vendor == null) return Unauthorized(new Response { Status = "Error", Message = "Invalid or expired token" });
var dispatch = await LoadVendorDispatchAsync(id, vendor.Id);
if (dispatch == null) return NotFound(new Response { Status = "Error", Message = "Dispatch not found" });
if (dispatch.Status == "Verified" || dispatch.Status == "Cancelled")
{
return BadRequest(new Response { Status = "Error", Message = "Dispatch is locked" });
}
var item = await _db.DispatchChecklistItems
.FirstOrDefaultAsync(c => c.Id == itemId && c.DispatchId == id);
if (item == null) return NotFound(new Response { Status = "Error", Message = "Checklist item not found" });
var now = DateTime.UtcNow;
item.IsCompleted = body.IsCompleted;
item.CompletedBy = body.IsCompleted ? vendor.CompanyName : null;
item.CompletedAt = body.IsCompleted ? now : null;
item.LastModificationTime = now;
await _db.SaveChangesAsync();
return Ok(new DataResponse
{
Status = "Success",
Data = new { item.Id, item.IsCompleted, item.CompletedBy, item.CompletedAt }
});
}
[HttpPost("dispatches/{id:int}/signoff")]
public async Task<IActionResult> AddSignoff(int id, [FromBody] SignoffRequest body)
{
var vendor = await ResolveVendorAsync();
if (vendor == null) return Unauthorized(new Response { Status = "Error", Message = "Invalid or expired token" });
var dispatch = await LoadVendorDispatchAsync(id, vendor.Id);
if (dispatch == null) return NotFound(new Response { Status = "Error", Message = "Dispatch not found" });
if (dispatch.Status != "In Progress" && dispatch.Status != "Completed")
{
return BadRequest(new Response { Status = "Error", Message = "Signoffs only allowed on In Progress or Completed dispatches" });
}
if (string.IsNullOrWhiteSpace(body?.Name) || string.IsNullOrWhiteSpace(body?.Signature))
{
return BadRequest(new Response { Status = "Error", Message = "Name and signature are required" });
}
var signoffType = (body?.SignoffType ?? "vendor").Trim().ToLowerInvariant();
if (signoffType != "vendor" && signoffType != "customer")
{
return BadRequest(new Response { Status = "Error", Message = "signoffType must be 'vendor' or 'customer'" });
}
var existing = await _db.DispatchSignoffs
.FirstOrDefaultAsync(s => s.DispatchId == id && s.SignoffType == signoffType);
if (existing != null)
{
return BadRequest(new Response { Status = "Error", Message = $"A {signoffType} signoff already exists for this dispatch" });
}
var now = DateTime.UtcNow;
var signoff = new DispatchSignoff
{
DispatchId = id,
SignoffType = signoffType,
Name = body!.Name,
Signature = body.Signature,
SignatureMethod = body.SignatureMethod ?? "drawn",
SignedAt = now,
CreatedDate = now
};
_db.DispatchSignoffs.Add(signoff);
_db.WorkOrderAuditLogs.Add(new WorkOrderAuditLog
{
WorkOrderId = dispatch.WorkOrderId ?? 0,
FieldName = $"Dispatch {dispatch.DispatchNumber} Signoff",
OldValue = null,
NewValue = $"{(signoffType == "customer" ? "Customer" : "Vendor")}: {body.Name}",
Action = signoffType == "customer" ? "customer_signoff" : "vendor_signoff",
CreatedAt = now
});
await _db.SaveChangesAsync();
return Ok(new DataResponse { Status = "Success", Data = new { signoff.Id, signoff.SignedAt } });
}
[HttpPost("dispatches/{id:int}/comments")]
public async Task<IActionResult> AddComment(int id, [FromBody] CommentRequest body)
{
var vendor = await ResolveVendorAsync();
if (vendor == null) return Unauthorized(new Response { Status = "Error", Message = "Invalid or expired token" });
var dispatch = await LoadVendorDispatchAsync(id, vendor.Id);
if (dispatch == null) return NotFound(new Response { Status = "Error", Message = "Dispatch not found" });
if (string.IsNullOrWhiteSpace(body?.CommentText))
{
return BadRequest(new Response { Status = "Error", Message = "Comment cannot be empty" });
}
var now = DateTime.UtcNow;
var comment = new Comments
{
DispatchId = id,
WorkerOrderId = dispatch.WorkOrderId,
Commenter = vendor.CompanyName,
CommentType = "vendor",
RecordType = "comment",
Commenttext = body!.CommentText,
CreatedDate = now
};
_db.Comments.Add(comment);
await _db.SaveChangesAsync();
return Ok(new DataResponse
{
Status = "Success",
Data = new { comment.Id, comment.Commenttext, comment.Commenter, comment.CreatedDate }
});
}
[HttpPost("dispatches/{id:int}/uplift-request")]
public async Task<IActionResult> RequestUplift(int id, [FromBody] UpliftRequestBody body)
{
var vendor = await ResolveVendorAsync();
if (vendor == null) return Unauthorized(new Response { Status = "Error", Message = "Invalid or expired token" });
var dispatch = await LoadVendorDispatchAsync(id, vendor.Id);
if (dispatch == null) return NotFound(new Response { Status = "Error", Message = "Dispatch not found" });
if (dispatch.Status == "Verified" || dispatch.Status == "Cancelled")
{
return BadRequest(new Response { Status = "Error", Message = $"Cannot request uplift on a '{dispatch.Status}' dispatch" });
}
if (body == null || body.RequestedNTE <= 0)
{
return BadRequest(new Response { Status = "Error", Message = "Requested NTE must be greater than zero" });
}
var current = dispatch.NTEAmount ?? 0m;
if (body.RequestedNTE <= current)
{
return BadRequest(new Response { Status = "Error", Message = "Requested NTE must be greater than the current NTE" });
}
var pendingExists = await _db.DispatchUpliftRequests
.AnyAsync(u => u.DispatchId == id && u.Status == "Pending" && (u.IsDeleted == null || u.IsDeleted == false));
if (pendingExists)
{
return BadRequest(new Response { Status = "Error", Message = "A pending uplift request already exists for this dispatch" });
}
var tier1Max = _config.GetValue<decimal?>("Approvals:UpliftTier1MaxUsd") ?? 2500m;
var delta = body.RequestedNTE - current;
var requiredTier = delta > tier1Max ? 2 : 1;
var now = DateTime.UtcNow;
var req = new DispatchUpliftRequest
{
DispatchId = id,
CurrentNTE = current,
RequestedNTE = body.RequestedNTE,
VendorReason = string.IsNullOrWhiteSpace(body.Reason) ? null : body.Reason!.Trim(),
Status = "Pending",
RequiredTier = requiredTier,
RequestedByVendorName = vendor.CompanyName,
CreatedDate = now
};
_db.DispatchUpliftRequests.Add(req);
_db.WorkOrderAuditLogs.Add(new WorkOrderAuditLog
{
WorkOrderId = dispatch.WorkOrderId ?? 0,
FieldName = $"Dispatch {dispatch.DispatchNumber} Uplift",
OldValue = $"${current:F2}",
NewValue = $"${body.RequestedNTE:F2}",
Action = "uplift_requested",
CreatedAt = now
});
await _db.SaveChangesAsync();
await NotifyDispatcherOfUpliftAsync(dispatch, req, vendor);
return Ok(new DataResponse
{
Status = "Success",
Data = new { req.Id, req.Status, req.RequiredTier, req.CurrentNTE, req.RequestedNTE }
});
}
[HttpPost("dispatches/{id:int}/uplift-request/{requestId:int}/cancel")]
public async Task<IActionResult> CancelUpliftRequest(int id, int requestId)
{
var vendor = await ResolveVendorAsync();
if (vendor == null) return Unauthorized(new Response { Status = "Error", Message = "Invalid or expired token" });
var dispatch = await LoadVendorDispatchAsync(id, vendor.Id);
if (dispatch == null) return NotFound(new Response { Status = "Error", Message = "Dispatch not found" });
var req = await _db.DispatchUpliftRequests
.FirstOrDefaultAsync(u => u.Id == requestId && u.DispatchId == id);
if (req == null) return NotFound(new Response { Status = "Error", Message = "Uplift request not found" });
if (req.Status != "Pending")
{
return BadRequest(new Response { Status = "Error", Message = $"Cannot cancel a '{req.Status}' uplift request" });
}
var now = DateTime.UtcNow;
req.Status = "Cancelled";
req.DecidedAt = now;
req.LastModificationTime = now;
_db.WorkOrderAuditLogs.Add(new WorkOrderAuditLog
{
WorkOrderId = dispatch.WorkOrderId ?? 0,
FieldName = $"Dispatch {dispatch.DispatchNumber} Uplift",
OldValue = "Pending",
NewValue = "Cancelled",
Action = "uplift_cancelled",
CreatedAt = now
});
await _db.SaveChangesAsync();
return Ok(new DataResponse { Status = "Success", Data = new { req.Id, req.Status } });
}
private async Task NotifyDispatcherOfUpliftAsync(Dispatch dispatch, DispatchUpliftRequest req, Vendor vendor)
{
try
{
var dispatchAudit = await _db.WorkOrderAuditLogs
.Where(a => a.WorkOrderId == (dispatch.WorkOrderId ?? 0) && a.Action == "dispatch" && !string.IsNullOrEmpty(a.UserId))
.OrderByDescending(a => a.CreatedAt)
.FirstOrDefaultAsync();
if (dispatchAudit == null || string.IsNullOrWhiteSpace(dispatchAudit.UserId)) return;
var dispatcher = await _db.Users.FindAsync(dispatchAudit.UserId);
if (dispatcher == null || string.IsNullOrWhiteSpace(dispatcher.Email)) return;
var frontendBase = _config.GetValue<string>("FrontendBaseUrl")?.TrimEnd('/') ?? "";
var workOrderLink = dispatch.WorkOrderId.HasValue
? $"{frontendBase}/workorders/{dispatch.WorkOrderId.Value}"
: frontendBase;
var vendorName = System.Net.WebUtility.HtmlEncode(vendor.CompanyName ?? "Vendor");
var reason = System.Net.WebUtility.HtmlEncode(req.VendorReason ?? "(no reason provided)");
var dispatchNum = System.Net.WebUtility.HtmlEncode(dispatch.DispatchNumber ?? "");
var tierText = req.RequiredTier == 2 ? "Tier 2 (Manager approval required)" : "Tier 1";
var subject = $"[Uplift Request] {dispatch.DispatchNumber} — {vendor.CompanyName} requests ${req.RequestedNTE:F2} (was ${req.CurrentNTE ?? 0m:F2})";
var body = $@"
<h2>Vendor Uplift Request</h2>
<p><strong>Dispatch:</strong> {dispatchNum}</p>
<p><strong>Vendor:</strong> {vendorName}</p>
<table style='border-collapse:collapse;font-family:Arial,sans-serif;'>
<tr><td style='padding:4px 10px;'><strong>Current NTE</strong></td><td style='padding:4px 10px;'>${req.CurrentNTE ?? 0m:F2}</td></tr>
<tr><td style='padding:4px 10px;'><strong>Requested NTE</strong></td><td style='padding:4px 10px;'>${req.RequestedNTE:F2}</td></tr>
<tr><td style='padding:4px 10px;'><strong>Delta</strong></td><td style='padding:4px 10px;'>${(req.RequestedNTE - (req.CurrentNTE ?? 0m)):F2}</td></tr>
<tr><td style='padding:4px 10px;'><strong>Required Approval</strong></td><td style='padding:4px 10px;'>{tierText}</td></tr>
</table>
<h3>Vendor Reason</h3>
<p>{reason}</p>
<div style='margin:20px 0;'>
<a href='{workOrderLink}' style='display:inline-block;padding:10px 22px;background:#2563eb;color:white;text-decoration:none;border-radius:6px;font-weight:bold;'>Review in SHOC</a>
</div>";
await _sendMessage.SendEMail(dispatcher.Email, subject, body);
}
catch
{
}
}
private async Task<Vendor?> ResolveVendorAsync()
{
if (!Request.Headers.TryGetValue(TokenHeader, out var values)) return null;
var token = values.ToString();
return await _tokens.ResolveVendorAsync(token);
}
private Task<Dispatch?> LoadVendorDispatchAsync(int id, int vendorId)
{
return _db.Dispatches
.Include(d => d.WorkOrder).ThenInclude(w => w!.Locations)
.FirstOrDefaultAsync(d => d.Id == id
&& d.VendorId == vendorId
&& (d.IsDeleted == null || d.IsDeleted == false));
}
private static bool IsAllowedVendorTransition(string? from, string? to)
{
if (from == "Acknowledged" && to == "In Progress") return true;
if (from == "In Progress" && to == "Completed") return true;
return false;
}
public class ChangeStatusRequest
{
public string? Status { get; set; }
}
public class RequestCancelRequest
{
public string? Reason { get; set; }
}
public class ChecklistUpdateRequest
{
public bool IsCompleted { get; set; }
}
public class SignoffRequest
{
public string? Name { get; set; }
public string? Signature { get; set; }
public string? SignatureMethod { get; set; }
public string? SignoffType { get; set; }
}
public class CommentRequest
{
public string? CommentText { get; set; }
}
public class UpliftRequestBody
{
public decimal RequestedNTE { get; set; }
public string? Reason { get; set; }
}
}
}

View file

@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Mvc;
namespace Api.SeaHavenIndustries.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -2,11 +2,10 @@
{
public class Comments_DTO
{
public string? Text { get; set; }
public string Text { get; set; }
public IFormFile? Document { get; set; }
public int WorkorderId { get; set; }
public DateTime? CreatedDate { get; set; }
public string? UserId { get; set; }
public string? CommentType { get; set; }
public DateTime CreatedDate { get; set; }
public string UserId { get; set; }
}
}

View file

@ -1,7 +1,4 @@
using Data.SeaHavenIndustries;
using SeaHaven.Services.DTOs;
namespace Api.SeaHavenIndustries.DTOs
namespace Api.SeaHavenIndustries.DTOs
{
public class Contact_DTO
{
@ -23,100 +20,5 @@ namespace Api.SeaHavenIndustries.DTOs
public string? TwitterUrl { get; set; }
public int? LocationId { get; set; }
public int? AccountId { get; set; }
// ✅ Map API DTO to Service DTO
public CreateContactDTO ToServiceCreateDTO()
{
return new CreateContactDTO
{
FirstName = this.FirstName,
LastName = this.LastName,
Email = this.Email ?? "",
Phone = this.PhoneNumber,
Title = this.Title,
Type = this.ContactType,
AccountId = this.AccountId
};
}
// Map from Entity to DTO (Model)
public static Contact_DTO MapToModel(Contacts entity)
{
return new Contact_DTO
{
Title = entity.Title,
Owner = entity.Owner,
FirstName = entity.FirstName,
MiddleName = entity.MiddleName,
LastName = entity.LastName,
ContactType = entity.ContactType,
PhoneNumber = entity.PhoneNumber,
Email = entity.Email,
Address1 = entity.Address1,
Address2 = entity.Address2,
City = entity.City,
State = entity.State,
Zip = entity.Zip,
FacebookUrl = entity.FacebookUrl,
LinkedInUrl = entity.LinkedInUrl,
TwitterUrl = entity.TwitterUrl,
LocationId = entity.LocationId,
AccountId = entity.AccountId
};
}
// Map from DTO (Model) to Entity
public static Contacts MapToEntity(Contact_DTO dto, Contacts? existingEntity = null)
{
var entity = existingEntity ?? new Contacts();
entity.Title = dto.Title;
entity.Owner = dto.Owner;
entity.FirstName = dto.FirstName;
entity.MiddleName = dto.MiddleName;
entity.LastName = dto.LastName;
entity.ContactType = dto.ContactType;
entity.PhoneNumber = dto.PhoneNumber;
entity.Email = dto.Email;
entity.Address1 = dto.Address1;
entity.Address2 = dto.Address2;
entity.City = dto.City;
entity.State = dto.State;
entity.Zip = dto.Zip;
entity.FacebookUrl = dto.FacebookUrl;
entity.LinkedInUrl = dto.LinkedInUrl;
entity.TwitterUrl = dto.TwitterUrl;
entity.LocationId = dto.LocationId;
entity.AccountId = dto.AccountId;
return entity;
}
}
// DTO for contact list view
public class ContactListItem_DTO
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Email { get; set; }
public string? Phone { get; set; }
// ✅ Map from Service DTO
public static ContactListItem_DTO FromServiceDTO(ContactDTO serviceDto)
{
return new ContactListItem_DTO
{
Id = serviceDto.Id,
Name = (serviceDto.FirstName + " " + serviceDto.LastName).Trim(),
Email = serviceDto.Email,
Phone = serviceDto.Phone
};
}
// ✅ Map collection from Service DTOs
public static IEnumerable<ContactListItem_DTO> FromServiceDTOs(IEnumerable<ContactDTO> serviceDtos)
{
return serviceDtos.Select(FromServiceDTO);
}
}
}

View file

@ -1,11 +0,0 @@
namespace Api.SeaHavenIndustries.DTOs
{
public class DispatchSignoff_DTO
{
public int DispatchId { get; set; }
public string? SignoffType { get; set; }
public string? Name { get; set; }
public string? Signature { get; set; }
public string? SignatureMethod { get; set; }
}
}

View file

@ -1,27 +0,0 @@
namespace Api.SeaHavenIndustries.DTOs
{
public class Dispatch_DTO
{
public List<int> WorkOrderIds { get; set; } = new();
public List<int> VendorIds { get; set; } = new();
public decimal? NTEAmount { get; set; }
public DateTime? ScheduledDate { 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; }
}
}

View file

@ -1,7 +1,4 @@
using Data.SeaHavenIndustries;
using SeaHaven.Services.DTOs;
namespace Api.SeaHavenIndustries.DTOs
namespace Api.SeaHavenIndustries.DTOs
{
public class EditContact_DTO
{
@ -24,72 +21,5 @@ namespace Api.SeaHavenIndustries.DTOs
public string? TwitterUrl { get; set; }
public int? LocationId { get; set; }
public int? AccountId { get; set; }
// ✅ Map API DTO to Service DTO
public UpdateContactDTO ToServiceUpdateDTO()
{
return new UpdateContactDTO
{
FirstName = this.FirstName,
LastName = this.LastName,
Email = this.Email,
Phone = this.PhoneNumber,
Title = this.Title,
Type = this.ContactType,
AccountId = this.AccountId
};
}
// Map from Entity to DTO (Model)
public static EditContact_DTO MapToModel(Contacts entity)
{
return new EditContact_DTO
{
Id = entity.Id,
Title = entity.Title,
Owner = entity.Owner,
FirstName = entity.FirstName,
MiddleName = entity.MiddleName,
LastName = entity.LastName,
ContactType = entity.ContactType,
PhoneNumber = entity.PhoneNumber,
Email = entity.Email,
Address1 = entity.Address1,
Address2 = entity.Address2,
City = entity.City,
State = entity.State,
Zip = entity.Zip,
FacebookUrl = entity.FacebookUrl,
LinkedInUrl = entity.LinkedInUrl,
TwitterUrl = entity.TwitterUrl,
LocationId = entity.LocationId,
AccountId = entity.AccountId
};
}
// Map from DTO (Model) to Entity - updates existing entity
public static Contacts MapToEntity(EditContact_DTO dto, Contacts existingEntity)
{
existingEntity.Title = dto.Title;
existingEntity.Owner = dto.Owner;
existingEntity.FirstName = dto.FirstName;
existingEntity.MiddleName = dto.MiddleName;
existingEntity.LastName = dto.LastName;
existingEntity.ContactType = dto.ContactType;
existingEntity.PhoneNumber = dto.PhoneNumber;
existingEntity.Email = dto.Email;
existingEntity.Address1 = dto.Address1;
existingEntity.Address2 = dto.Address2;
existingEntity.City = dto.City;
existingEntity.State = dto.State;
existingEntity.Zip = dto.Zip;
existingEntity.FacebookUrl = dto.FacebookUrl;
existingEntity.LinkedInUrl = dto.LinkedInUrl;
existingEntity.TwitterUrl = dto.TwitterUrl;
existingEntity.LocationId = dto.LocationId;
existingEntity.AccountId = dto.AccountId;
return existingEntity;
}
}
}

View file

@ -0,0 +1,48 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace Api.SeaHavenIndustries.DTOs
{
/// <summary>
/// DTO for POST/PUT requests - Create/Edit Event
/// Used to create or update event information
/// </summary>
public class EditEvent_DTO
{
public int? Id { get; set; } // Null for create, populated for edit
[Required(ErrorMessage = "Please enter a title")]
[StringLength(100, ErrorMessage = "Title cannot be longer than 100 characters")]
public string Title { get; set; } = null!;
public string? Description { get; set; }
[StringLength(150, ErrorMessage = "Location cannot be longer than 150 characters")]
public string? Location { get; set; }
public int? RecordID { get; set; }
public string? RecordName { get; set; }
public string? RecordTargetType { get; set; }
[Required(ErrorMessage = "The Remind Attendees field is required")]
public int ReminderMinutes { get; set; } = 15;
[Required(ErrorMessage = "The Select a Calendar Color field is required")]
public string EventColor { get; set; } = "primary";
[Required(ErrorMessage = "Please select a start date")]
public DateTime StartDate { get; set; }
[Required(ErrorMessage = "Please select a start time")]
public string StartTime { get; set; } = null!;
[Required(ErrorMessage = "Please select an end date")]
public DateTime EndDate { get; set; }
[Required(ErrorMessage = "Please select an end time")]
public string EndTime { get; set; } = null!;
public bool AllDay { get; set; } = false;
}
}

View file

@ -1,37 +1,9 @@
using SeaHaven.Services.DTOs;
namespace Api.SeaHavenIndustries.DTOs
namespace Api.SeaHavenIndustries.DTOs
{
public class EditLocation_DTO
{
public int Id { get; set; }
public string? Title { get; set; }
public string? Name { get; set; }
public string? Address { get; set; }
public string? City { get; set; }
public string? State { get; set; }
public string? ZipCode { get; set; }
public string? Phone { get; set; }
public string? Contact { get; set; }
public string? ContactEmail { get; set; }
public string? Status { get; set; }
public string? AccountId { get; set; }
public int? GetAccountId() =>
int.TryParse(AccountId, out var id) ? id : null;
// ✅ Map API DTO to Service DTO
public UpdateLocationDTO ToServiceUpdateDTO()
{
return new UpdateLocationDTO
{
LocationName = this.Name,
Address = this.Address,
City = this.City,
State = this.State,
Zipcode = this.ZipCode,
AccountId = this.GetAccountId()
};
}
}
}

View file

@ -0,0 +1,33 @@
using System;
namespace Api.SeaHavenIndustries.DTOs
{
/// <summary>
/// DTO for GET responses - Event data
/// Used to return event information to the client
/// </summary>
public class Event_DTO
{
public int Id { get; set; }
public string Title { get; set; } = null!;
public string? Description { get; set; }
public string? Location { get; set; }
public int? RecordID { get; set; }
public string? RecordName { get; set; }
public string? RecordTargetType { get; set; }
public int ReminderMinutes { get; set; }
public string EventColor { get; set; } = null!;
public DateTime StartDate { get; set; }
public string? StartTime { get; set; }
public DateTime EndDate { get; set; }
public string? EndTime { get; set; }
public bool AllDay { get; set; }
public DateTime? CreatedDate { get; set; }
public string? createdby { get; set; }
}
}

View file

@ -1,135 +1,8 @@
using Data.SeaHavenIndustries;
using SeaHaven.Services.DTOs;
namespace Api.SeaHavenIndustries.DTOs
namespace Api.SeaHavenIndustries.DTOs
{
public class Location_DTO
{
public string? Title { get; set; }
public string? Name { get; set; }
public string? Address { get; set; }
public string? City { get; set; }
public string? State { get; set; }
public string? ZipCode { get; set; }
public string? Phone { get; set; }
public string? Contact { get; set; }
public string? ContactEmail { get; set; }
public string? Status { get; set; }
public string? AccountId { get; set; }
public int? GetAccountId() =>
int.TryParse(AccountId, out var id) ? id : null;
// ✅ Map API DTO to Service DTO
public CreateLocationDTO ToServiceCreateDTO()
{
return new CreateLocationDTO
{
LocationName = this.Name ?? "",
Address = this.Address,
City = this.City,
State = this.State,
Zipcode = this.ZipCode,
AccountId = this.GetAccountId()
};
}
// Map from Entity to DTO
public static Location_DTO MapToModel(Locations entity)
{
return new Location_DTO
{
Title = entity.Title,
Name = entity.Name,
Address = entity.Address1,
City = entity.City,
State = entity.State,
ZipCode = entity.Zip,
Phone = entity.PhoneNumber,
Contact = null, // Not in database schema
ContactEmail = entity.Email,
Status = entity.Status,
AccountId = null // Not in database schema
};
}
// Map from DTO to Entity
public static Locations MapToEntity(Location_DTO dto, Locations? existingEntity = null)
{
var entity = existingEntity ?? new Locations();
entity.Title = dto.Title;
entity.Name = dto.Name;
entity.Address1 = dto.Address;
entity.City = dto.City;
entity.State = dto.State;
entity.Zip = dto.ZipCode;
entity.PhoneNumber = dto.Phone;
// Contact field doesn't exist in database schema - ignored
entity.Email = dto.ContactEmail;
entity.Status = dto.Status;
// AccountId field doesn't exist in database schema - ignored
return entity;
}
}
// DTO for list views
public class LocationListItem_DTO
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Title { get; set; }
// Map from Entity
public static LocationListItem_DTO MapToModel(Locations entity)
{
return new LocationListItem_DTO
{
Id = entity.Id,
Name = entity.Name,
Title = entity.Title
};
}
// ✅ Map from Service DTO (no logic in controller)
public static LocationListItem_DTO FromServiceDTO(LocationDTO serviceDto)
{
return new LocationListItem_DTO
{
Id = serviceDto.Id,
Name = serviceDto.Name ?? serviceDto.Title,
Title = serviceDto.Title ?? serviceDto.Name
};
}
// ✅ Map collection from Service DTOs
public static IEnumerable<LocationListItem_DTO> FromServiceDTOs(IEnumerable<LocationDTO> serviceDtos)
{
return serviceDtos.Select(FromServiceDTO);
}
}
// DTO for location dropdown/simple view
public class LocationViewModel_DTO
{
public int? Id { get; set; }
public string? Name { get; set; }
// ✅ Map from Service DTO
public static LocationViewModel_DTO FromServiceDTO(LocationDTO serviceDto)
{
return new LocationViewModel_DTO
{
Id = serviceDto.Id,
Name = serviceDto.Name ?? serviceDto.Title
};
}
// ✅ Map collection from Service DTOs
public static IEnumerable<LocationViewModel_DTO> FromServiceDTOs(IEnumerable<LocationDTO> serviceDtos)
{
return serviceDtos.Select(FromServiceDTO);
}
}
}

View file

@ -1,16 +0,0 @@
namespace Api.SeaHavenIndustries.DTOs
{
public class TaskListTemplate_DTO
{
public string? Name { get; set; }
public string? Description { get; set; }
public bool IsActive { get; set; } = true;
public List<TaskListTemplateItem_DTO> Items { get; set; } = new();
}
public class TaskListTemplateItem_DTO
{
public string? ItemText { get; set; }
public int SortOrder { get; set; }
}
}

View file

@ -1,19 +0,0 @@
namespace Api.SeaHavenIndustries.DTOs
{
public class UpdateDispatch_DTO
{
public int Id { get; set; }
public string? Status { get; set; }
public decimal? NTEAmount { get; set; }
public DateTime? ScheduledDate { get; set; }
public DateTime? CompletedDate { get; set; }
public string? Description { get; set; }
}
public class DispatchComment_DTO
{
public int DispatchId { get; set; }
public string? Text { get; set; }
public bool SendEmail { get; set; } = true;
}
}

View file

@ -1,21 +0,0 @@
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; }
}
}

View file

@ -44,18 +44,12 @@ namespace Api.SeaHavenIndustries.DTOs
public string? Description { get; set; }
public string? PO { get; set; }
public string? TT { get; set; }
//public int? ContactId { get; set; }
public string? AssignTo { get; set; }
public DateTime? DueDate { get; set; }
public string? Priority { get; set; }
public string? Status { get; set; }
public int? LocationId { 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 int? CategoryId { get; set; }
public List<IFormFile>? Attachments { get; set; }
public string? BeforPhotoIssue { get; set; }

View file

@ -98,27 +98,8 @@ 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;
}
}
}
}

View file

@ -1,92 +0,0 @@
using System.Security.Cryptography;
using Data.SeaHavenIndustries;
using Microsoft.EntityFrameworkCore;
namespace Api.SeaHavenIndustries.Helper
{
public class VendorPortalTokenService
{
private readonly ApplicationDbContext _db;
private readonly int _lifetimeDays;
public VendorPortalTokenService(ApplicationDbContext db, IConfiguration config)
{
_db = db;
_lifetimeDays = config.GetValue<int?>("VendorPortal:TokenLifetimeDays") ?? 365;
}
public async Task<VendorAccessToken> GetOrCreateActiveTokenAsync(int vendorId)
{
var now = DateTime.UtcNow;
var existing = await _db.VendorAccessTokens
.Where(t => t.VendorId == vendorId
&& t.RevokedAt == null
&& t.ExpiresAt > now
&& (t.IsDeleted == null || t.IsDeleted == false))
.OrderByDescending(t => t.IssuedAt)
.FirstOrDefaultAsync();
if (existing != null) return existing;
var token = new VendorAccessToken
{
VendorId = vendorId,
Token = GenerateToken(),
IssuedAt = now,
ExpiresAt = now.AddDays(_lifetimeDays),
CreatedDate = now
};
_db.VendorAccessTokens.Add(token);
await _db.SaveChangesAsync();
return token;
}
public async Task<Vendor?> ResolveVendorAsync(string? token)
{
if (string.IsNullOrWhiteSpace(token)) return null;
var now = DateTime.UtcNow;
var record = await _db.VendorAccessTokens
.Include(t => t.Vendor)
.FirstOrDefaultAsync(t => t.Token == token
&& t.RevokedAt == null
&& t.ExpiresAt > now
&& (t.IsDeleted == null || t.IsDeleted == false));
if (record?.Vendor == null) return null;
record.LastUsedAt = now;
await _db.SaveChangesAsync();
return record.Vendor;
}
public async Task<VendorAccessToken> RotateAsync(int vendorId)
{
var now = DateTime.UtcNow;
var active = await _db.VendorAccessTokens
.Where(t => t.VendorId == vendorId && t.RevokedAt == null)
.ToListAsync();
foreach (var t in active) t.RevokedAt = now;
await _db.SaveChangesAsync();
return await GetOrCreateActiveTokenAsync(vendorId);
}
public async Task RevokeAllAsync(int vendorId)
{
var now = DateTime.UtcNow;
var active = await _db.VendorAccessTokens
.Where(t => t.VendorId == vendorId && t.RevokedAt == null)
.ToListAsync();
foreach (var t in active) t.RevokedAt = now;
await _db.SaveChangesAsync();
}
private static string GenerateToken()
{
var bytes = RandomNumberGenerator.GetBytes(32);
return Convert.ToBase64String(bytes)
.Replace("+", "-")
.Replace("/", "_")
.TrimEnd('=');
}
}
}

View file

@ -1,48 +0,0 @@
namespace Api.SeaHavenIndustries.Helper
{
public class ZipCodeDistance
{
private readonly Dictionary<string, (double lat, double lng)> _coords = new();
private bool _loaded;
public void Load(string csvPath)
{
if (_loaded) return;
foreach (var line in File.ReadLines(csvPath).Skip(1))
{
var parts = line.Split(',');
if (parts.Length >= 3
&& double.TryParse(parts[1].Trim(), out var lat)
&& double.TryParse(parts[2].Trim(), out var lng))
{
_coords[parts[0].Trim().PadLeft(5, '0')] = (lat, lng);
}
}
_loaded = true;
}
public double? GetDistanceMiles(string? zip1, string? zip2)
{
if (string.IsNullOrWhiteSpace(zip1) || string.IsNullOrWhiteSpace(zip2)) return null;
var z1 = zip1.Trim().PadLeft(5, '0');
var z2 = zip2.Trim().PadLeft(5, '0');
if (z1.Length > 5) z1 = z1.Substring(0, 5);
if (z2.Length > 5) z2 = z2.Substring(0, 5);
if (!_coords.TryGetValue(z1, out var c1) || !_coords.TryGetValue(z2, out var c2)) return null;
return Haversine(c1.lat, c1.lng, c2.lat, c2.lng);
}
private static double Haversine(double lat1, double lon1, double lat2, double lon2)
{
const double R = 3958.8;
var dLat = ToRad(lat2 - lat1);
var dLon = ToRad(lon2 - lon1);
var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
Math.Cos(ToRad(lat1)) * Math.Cos(ToRad(lat2)) *
Math.Sin(dLon / 2) * Math.Sin(dLon / 2);
return R * 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
}
private static double ToRad(double deg) => deg * Math.PI / 180.0;
}
}

View file

@ -31,49 +31,7 @@ builder.Services.AddResponseCompression(opts =>
});
builder.Services.AddCors(option =>
option.AddDefaultPolicy(builder => builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()));
// Helper services
builder.Services.AddScoped<SendMessage>();
builder.Services.AddScoped<Api.SeaHavenIndustries.Helper.VendorPortalTokenService>();
builder.Services.AddSingleton<Api.SeaHavenIndustries.Helper.ZipCodeDistance>(sp =>
{
var zipService = new Api.SeaHavenIndustries.Helper.ZipCodeDistance();
var env = sp.GetRequiredService<IWebHostEnvironment>();
var csvPath = Path.Combine(env.WebRootPath, "zipcodes.csv");
if (File.Exists(csvPath)) zipService.Load(csvPath);
return zipService;
});
// ========================================================================
// MULTI-TIER ARCHITECTURE - Dependency Injection Registration
// ========================================================================
// Data Services (Data Access Layer)
builder.Services.AddScoped<SeaHaven.DataServices.Interfaces.IContactDataService, SeaHaven.DataServices.Implementation.ContactDataService>();
builder.Services.AddScoped<SeaHaven.DataServices.Interfaces.IWorkOrderDataService, SeaHaven.DataServices.Implementation.WorkOrderDataService>();
builder.Services.AddScoped<SeaHaven.DataServices.Interfaces.ILocationDataService, SeaHaven.DataServices.Implementation.LocationDataService>();
builder.Services.AddScoped<SeaHaven.DataServices.Interfaces.IEmployeeDataService, SeaHaven.DataServices.Implementation.EmployeeDataService>();
builder.Services.AddScoped<SeaHaven.DataServices.Interfaces.IAccountDataService, SeaHaven.DataServices.Implementation.AccountDataService>();
builder.Services.AddScoped<SeaHaven.DataServices.Interfaces.IVendorDataService, SeaHaven.DataServices.Implementation.VendorDataService>();
builder.Services.AddScoped<SeaHaven.DataServices.Interfaces.IDispatchDataService, SeaHaven.DataServices.Implementation.DispatchDataService>();
builder.Services.AddScoped<SeaHaven.DataServices.Interfaces.IFollowUpDataService, SeaHaven.DataServices.Implementation.FollowUpDataService>();
builder.Services.AddScoped<SeaHaven.DataServices.Interfaces.IPMScheduleDataService, SeaHaven.DataServices.Implementation.PMScheduleDataService>();
builder.Services.AddScoped<SeaHaven.DataServices.Interfaces.IAssetDataService, SeaHaven.DataServices.Implementation.AssetDataService>();
builder.Services.AddScoped<SeaHaven.DataServices.Interfaces.ICategoryDataService, SeaHaven.DataServices.Implementation.CategoryDataService>();
// Business Services (Business Logic Layer)
builder.Services.AddScoped<SeaHaven.Services.Interfaces.IContactService, SeaHaven.Services.Implementation.ContactService>();
builder.Services.AddScoped<SeaHaven.Services.Interfaces.IWorkOrderService, SeaHaven.Services.Implementation.WorkOrderService>();
builder.Services.AddScoped<SeaHaven.Services.Interfaces.ILocationService, SeaHaven.Services.Implementation.LocationService>();
builder.Services.AddScoped<SeaHaven.Services.Interfaces.IEmployeeService, SeaHaven.Services.Implementation.EmployeeService>();
builder.Services.AddScoped<SeaHaven.Services.Interfaces.IAccountService, SeaHaven.Services.Implementation.AccountService>();
builder.Services.AddScoped<SeaHaven.Services.Interfaces.IVendorService, SeaHaven.Services.Implementation.VendorService>();
builder.Services.AddScoped<SeaHaven.Services.Interfaces.IDispatchService, SeaHaven.Services.Implementation.DispatchService>();
builder.Services.AddScoped<SeaHaven.Services.Interfaces.IFollowUpService, SeaHaven.Services.Implementation.FollowUpService>();
builder.Services.AddScoped<SeaHaven.Services.Interfaces.IPMScheduleService, SeaHaven.Services.Implementation.PMScheduleService>();
builder.Services.AddScoped<SeaHaven.Services.Interfaces.IAssetService, SeaHaven.Services.Implementation.AssetService>();
// ========================================================================
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
@ -135,8 +93,7 @@ if (app.Environment.IsDevelopment() || app.Environment.IsProduction())
});
}
if (!app.Environment.IsDevelopment())
app.UseHttpsRedirection();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCors();
app.UseAuthentication();
@ -144,60 +101,4 @@ app.UseAuthorization();
app.MapControllers();
//if (app.Environment.IsDevelopment())
//{
// using var scope = app.Services.CreateScope();
// var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
// var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
// if (!await roleManager.RoleExistsAsync("Admin"))
// await roleManager.CreateAsync(new IdentityRole("Admin"));
// if (!await roleManager.RoleExistsAsync("Dispatcher"))
// await roleManager.CreateAsync(new IdentityRole("Dispatcher"));
// if (!await roleManager.RoleExistsAsync("Manager"))
// await roleManager.CreateAsync(new IdentityRole("Manager"));
// var adminEmail = "admin@seahavenind.com";
// if (await userManager.FindByNameAsync(adminEmail) == null)
// {
// var admin = new ApplicationUser
// {
// UserName = adminEmail,
// Email = adminEmail,
// FirstName = "Admin",
// LastName = "User",
// EmailConfirmed = true,
// UniqueName = "Active",
// CreatedDate = DateTime.Now
// };
// var result = await userManager.CreateAsync(admin, "Admin@123");
// 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();

View file

@ -1,26 +1,8 @@
{
"ConnectionStrings": {
//"DefaultConnection": "Server=DESKTOP-64LC14O\SQLEXPRESS;Database=SeahavenIndustries;TrustServerCertificate=True;Trusted_Connection=True;MultipleActiveResultSets=true",
// Test database
//"DefaultConnection": "Server=SQL5112.site4now.net;Initial Catalog=db_a7141e_seahavenindustries;User Id=db_a7141e_seahavenindustries_admin;Password=Abc123!@#;MultipleActiveResultSets=true"
//Client Database
"DefaultConnection": "Server=SQL5112.site4now.net;Initial Catalog=db_a7141e_seahavenindustry;User Id=db_a7141e_seahavenindustry_admin;Password=Abc123!@#;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"SendGrid": {
"ApiKey": "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"
},
"AllowedHosts": "*",
"JWT": {
"ValidAudience": "http://localhost:4200",
"ValidIssuer": "http://localhost:7195",
"Secret": "JWTAuthenticationHIGHsecuredPasswordVVVp1OH7Xzyr"
}
}
}

View file

@ -1,20 +0,0 @@
{
"ConnectionStrings": {
"DefaultConnection": "Server=SQL5112.site4now.net;Initial Catalog=db_a7141e_seahavenindustry;User Id=db_a7141e_seahavenindustry_admin;Password=Abc123!@#;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"SendGrid": {
"ApiKey": "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"
},
"AllowedHosts": "*",
"JWT": {
"ValidAudience": "http://console.seahavenind.com",
"ValidIssuer": "http://console.seahavenind.com",
"Secret": "JWTAuthenticationHIGHsecuredPasswordVVVp1OH7Xzyr"
}
}

View file

@ -23,4 +23,4 @@
"ValidIssuer": "http://localhost:7195",
"Secret": "JWTAuthenticationHIGHsecuredPasswordVVVp1OH7Xzyr"
}
}
}

View file

@ -924,206 +924,6 @@ PRIMARY KEY CLUSTERED
GO
-- ==================================================================
-- DATABASE CHANGES APPLIED ON 2026-04-22
-- Migration: Calendar Module + Missing Column Fixes
-- Applied by: AI Agent
-- ==================================================================
-- ==================================================================
-- 1. EVENTS TABLE (Calendar Module)
-- Migration: 20260422021353_AddEventsTable
-- ==================================================================
USE [db_a7141e_seahavenindustry]
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Events]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[Events](
[Id] [int] IDENTITY(1,1) NOT NULL,
-- Basic Event Information
[Title] [nvarchar](100) NOT NULL,
[Description] [nvarchar](max) NULL,
[Location] [nvarchar](150) NULL,
-- Related Record (Link to Accounts, Contacts, Work Orders, etc.)
[RecordID] [int] NULL,
[RecordName] [nvarchar](max) NULL,
[RecordTargetType] [nvarchar](max) NULL,
-- Reminder & Display Settings
[ReminderMinutes] [int] NOT NULL CONSTRAINT [DF_Events_ReminderMinutes] DEFAULT ((15)),
[EventColor] [nvarchar](20) NOT NULL CONSTRAINT [DF_Events_EventColor] DEFAULT (N'primary'),
-- Schedule Information
[StartDate] [datetime2](7) NOT NULL,
[StartTime] [nvarchar](10) NULL,
[EndDate] [datetime2](7) NOT NULL,
[EndTime] [nvarchar](10) NULL,
[AllDay] [bit] NOT NULL CONSTRAINT [DF_Events_AllDay] DEFAULT ((0)),
-- Audit Fields (from FullAuditEntity)
[IsDeleted] [bit] NOT NULL CONSTRAINT [DF_Events_IsDeleted] DEFAULT ((0)),
[DeletionTime] [datetime2](7) NULL,
[createdby] [nvarchar](max) NULL,
[CreatedDate] [datetime2](7) NULL,
[lastmodifiedby] [nvarchar](max) NULL,
[LastModificationTime] [datetime2](7) NULL,
CONSTRAINT [PK_Events] PRIMARY KEY CLUSTERED ([Id] ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
END
GO
-- Create Index on StartDate for faster calendar queries
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_Events_StartDate' AND object_id = OBJECT_ID('[dbo].[Events]'))
BEGIN
CREATE NONCLUSTERED INDEX [IX_Events_StartDate] ON [dbo].[Events]
(
[StartDate] ASC
)
WHERE ([IsDeleted] = 0)
END
GO
-- Create Index on IsDeleted for soft delete queries
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_Events_IsDeleted' AND object_id = OBJECT_ID('[dbo].[Events]'))
BEGIN
CREATE NONCLUSTERED INDEX [IX_Events_IsDeleted] ON [dbo].[Events]
(
[IsDeleted] ASC
)
END
GO
-- ==================================================================
-- 2. WORKORDERS TABLE - MISSING COLUMNS
-- Migrations: 20260416212330, 20260416224015, 20260417142453
-- ==================================================================
-- Migration: 20260416224015_AddInternalWONumber
ALTER TABLE [dbo].[WorkOrders]
ADD [InternalWONumber] [nvarchar](max) NULL
GO
-- Migration: 20260416212330_AddExternalSyncFields
ALTER TABLE [dbo].[WorkOrders]
ADD [ExternalWorkOrderId] [nvarchar](max) NULL,
[Source] [nvarchar](max) NULL
GO
-- Migration: 20260417142453_AddDropdownOptionsAndWOFields
ALTER TABLE [dbo].[WorkOrders]
ADD [Problem] [nvarchar](max) NULL,
[Trade] [nvarchar](max) NULL,
[SubTrade] [nvarchar](max) NULL,
[VendorNTE] [decimal](18, 2) NULL,
[ScheduledDate] [datetime2](7) NULL,
[CompletedDate] [datetime2](7) NULL
GO
-- ==================================================================
-- 3. COMMENTS TABLE - MISSING COLUMNS
-- Migrations: 20260416220656, 20260417171152
-- ==================================================================
-- Migration: 20260416220656_AddCommentTypeAndAuditLog
ALTER TABLE [dbo].[Comments]
ADD [CommentType] [nvarchar](max) NULL,
[Commenter] [nvarchar](max) NULL,
[RecordType] [nvarchar](max) NULL
GO
-- Migration: 20260417171152_AddDispatchNumberAndCommentDispatchId
ALTER TABLE [dbo].[Comments]
ADD [DispatchId] [int] NULL
GO
-- ==================================================================
-- 4. LOCATIONS TABLE - MISSING COLUMNS
-- Migration: 20260416210833_AddLocationFields
-- ==================================================================
ALTER TABLE [dbo].[Locations]
ADD [Address1] [nvarchar](max) NULL,
[Address2] [nvarchar](max) NULL,
[City] [nvarchar](max) NULL,
[State] [nvarchar](max) NULL,
[Zip] [nvarchar](max) NULL,
[PhoneNumber] [nvarchar](max) NULL,
[Email] [nvarchar](max) NULL,
[Status] [nvarchar](max) NULL
GO
-- ==================================================================
-- 5. MIGRATION HISTORY RECORDS
-- Marked these migrations as applied to sync with code
-- ==================================================================
INSERT INTO [dbo].[__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES
(N'20260416204109_SyncContactsAndModels', N'8.0.8'),
(N'20260416210833_AddLocationFields', N'8.0.8'),
(N'20260416212330_AddExternalSyncFields', N'8.0.8'),
(N'20260416220656_AddCommentTypeAndAuditLog', N'8.0.8'),
(N'20260416224015_AddInternalWONumber', N'8.0.8'),
(N'20260417142453_AddDropdownOptionsAndWOFields', N'8.0.8'),
(N'20260417153405_AddVendorAndDispatch', N'8.0.8'),
(N'20260417154641_AddDispatchScheduledDate', N'8.0.8'),
(N'20260417171152_AddDispatchNumberAndCommentDispatchId', N'8.0.8'),
(N'20260417182910_AddTaskListTemplates', N'8.0.8'),
(N'20260417190743_MultiWODispatch', N'8.0.8'),
(N'20260417192012_AddDispatchChecklist', N'8.0.8'),
(N'20260417194438_AddDispatchSignoffs', N'8.0.8'),
(N'20260417195316_AddDispatchVerification', N'8.0.8'),
(N'20260420151445_AddVendorAccessToken', N'8.0.8'),
(N'20260420164708_AddDispatchUpliftRequest', N'8.0.8'),
(N'20260422021353_AddEventsTable', N'8.0.8')
GO
-- ==================================================================
-- SUMMARY OF CHANGES (2026-04-22)
-- ==================================================================
-- Tables Created (11):
-- 1. Events (Calendar/Events module)
-- 2. WorkOrderAuditLogs (Audit trail for work order changes)
-- 3. DropdownOptions (Dynamic dropdown values)
-- 4. Vendors (Vendor management)
-- 5. Dispatches (Vendor dispatch workflow)
-- 6. TaskListTemplates (Reusable task templates)
-- 7. TaskListTemplateItems (Template task items)
-- 8. DispatchWorkOrders (Many-to-many: Dispatch <-> WorkOrders)
-- 9. DispatchChecklistItems (Dispatch checklist)
-- 10. DispatchSignoffs (Customer & Vendor signatures)
-- 11. DispatchUpliftRequests (NTE increase requests)
--
-- Columns Added to WorkOrders (9):
-- - InternalWONumber, ExternalWorkOrderId, Source
-- - Problem, Trade, SubTrade
-- - VendorNTE, ScheduledDate, CompletedDate
--
-- Columns Added to Comments (4):
-- - CommentType, Commenter, RecordType, DispatchId
--
-- Columns Added to Locations (8):
-- - Address1, Address2, City, State, Zip
-- - PhoneNumber, Email, Status
--
-- Columns Added to Dispatches (7):
-- - ScheduledDate, DispatchNumber, CompletedDate
-- - VerifiedBy, VerifiedAt, AcceptToken, AcknowledgedAt
--
-- Columns Added to Vendors (1):
-- - AccessToken
--
-- Migrations Applied: 17
-- From: 20260416204109_SyncContactsAndModels
-- To: 20260422021353_AddEventsTable
--
-- Database Status: UP TO DATE ✓
-- ==================================================================

View file

@ -0,0 +1,553 @@
/*
===================================================================================
CALENDAR/EVENTS MODULE - COMPLETE IMPLEMENTATION
===================================================================================
This file contains everything needed for the Calendar/Events module:
1. Events Model (Database Entity)
2. Event_DTO (for GET responses)
3. EditEvent_DTO (for POST/PUT requests)
4. CalendarController (API endpoints)
5. SQL Script for database table creation and sample data
Author: AI Agent
Date: 2026-03-30
===================================================================================
*/
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Api.SeaHavenIndustries.DTOs;
using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
// ===================================================================================
// SECTION 1: DATABASE MODEL (Data.SeaHavenIndustries/Models/Events.cs)
// ===================================================================================
namespace Data.SeaHavenIndustries
{
public class Events : FullAuditEntity
{
public string Title { get; set; } = null!;
public string? Description { get; set; }
public string? Location { get; set; }
// Related Record (can link to Accounts, Contacts, Work Orders, etc.)
public int? RecordID { get; set; }
public string? RecordName { get; set; }
public string? RecordTargetType { get; set; }
// Reminder Settings
public int ReminderMinutes { get; set; } = 15; // Default: 15 minutes before
// Color for calendar display
public string EventColor { get; set; } = "primary"; // primary, success, info, warning, danger
// Schedule
public DateTime StartDate { get; set; }
public string? StartTime { get; set; } // e.g., "08:00 AM"
public DateTime EndDate { get; set; }
public string? EndTime { get; set; } // e.g., "08:30 AM"
public bool AllDay { get; set; } = false;
}
}
// ===================================================================================
// SECTION 2: DTOs (Api.SeaHavenIndustries/DTOs/Event_DTO.cs)
// ===================================================================================
namespace Api.SeaHavenIndustries.DTOs
{
// DTO for GET responses
public class Event_DTO
{
public int Id { get; set; }
public string Title { get; set; } = null!;
public string? Description { get; set; }
public string? Location { get; set; }
public int? RecordID { get; set; }
public string? RecordName { get; set; }
public string? RecordTargetType { get; set; }
public int ReminderMinutes { get; set; }
public string EventColor { get; set; } = null!;
public DateTime StartDate { get; set; }
public string? StartTime { get; set; }
public DateTime EndDate { get; set; }
public string? EndTime { get; set; }
public bool AllDay { get; set; }
public DateTime? CreatedDate { get; set; }
public string? createdby { get; set; }
}
// DTO for POST/PUT requests (Create/Edit)
public class EditEvent_DTO
{
public int? Id { get; set; } // Null for create, populated for edit
[Required(ErrorMessage = "Please enter a title")]
[StringLength(100, ErrorMessage = "Title cannot be longer than 100 characters")]
public string Title { get; set; } = null!;
public string? Description { get; set; }
[StringLength(150, ErrorMessage = "Location cannot be longer than 150 characters")]
public string? Location { get; set; }
public int? RecordID { get; set; }
public string? RecordName { get; set; }
public string? RecordTargetType { get; set; }
[Required(ErrorMessage = "The Remind Attendees field is required")]
public int ReminderMinutes { get; set; } = 15;
[Required(ErrorMessage = "The Select a Calendar Color field is required")]
public string EventColor { get; set; } = "primary";
[Required(ErrorMessage = "Please select a start date")]
public DateTime StartDate { get; set; }
[Required(ErrorMessage = "Please select a start time")]
public string StartTime { get; set; } = null!;
[Required(ErrorMessage = "Please select an end date")]
public DateTime EndDate { get; set; }
[Required(ErrorMessage = "Please select an end time")]
public string EndTime { get; set; } = null!;
public bool AllDay { get; set; } = false;
}
}
// ===================================================================================
// SECTION 3: CONTROLLER (Api.SeaHavenIndustries/Controllers/CalendarController.cs)
// ===================================================================================
namespace Api.SeaHavenIndustries.Controllers
{
//[Authorize] // Uncomment when authentication is needed
[ApiController]
[Route("api/Calendar")]
public class CalendarController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly ApplicationDbContext _db;
public CalendarController(UserManager<ApplicationUser> userManager, ApplicationDbContext db)
{
_userManager = userManager;
_db = db;
}
// ===================================================================================
// CREATE EVENT
// ===================================================================================
[HttpPost]
[Route("AddEvent")]
public async Task<IActionResult> AddEvent(EditEvent_DTO eventDto)
{
try
{
var model = new Events
{
Title = eventDto.Title,
Description = eventDto.Description,
Location = eventDto.Location,
RecordID = eventDto.RecordID,
RecordName = eventDto.RecordName,
RecordTargetType = eventDto.RecordTargetType,
ReminderMinutes = eventDto.ReminderMinutes,
EventColor = eventDto.EventColor,
StartDate = eventDto.StartDate,
StartTime = eventDto.StartTime,
EndDate = eventDto.EndDate,
EndTime = eventDto.EndTime,
AllDay = eventDto.AllDay,
CreatedDate = DateTime.Now,
IsDeleted = false
};
_db.Events.Add(model);
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Event Created Successfully", Status = "200" });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
// ===================================================================================
// UPDATE EVENT
// ===================================================================================
[HttpPost]
[Route("EditEvent")]
public async Task<IActionResult> EditEvent(EditEvent_DTO eventDto)
{
try
{
if (!eventDto.Id.HasValue)
{
return BadRequest(new Response { Status = "Error", Message = "Event ID is required for editing" });
}
var model = await _db.Events.FindAsync(eventDto.Id.Value);
if (model == null || model.IsDeleted == true)
{
return NotFound(new Response { Status = "Error", Message = "Event not found" });
}
model.Title = eventDto.Title;
model.Description = eventDto.Description;
model.Location = eventDto.Location;
model.RecordID = eventDto.RecordID;
model.RecordName = eventDto.RecordName;
model.RecordTargetType = eventDto.RecordTargetType;
model.ReminderMinutes = eventDto.ReminderMinutes;
model.EventColor = eventDto.EventColor;
model.StartDate = eventDto.StartDate;
model.StartTime = eventDto.StartTime;
model.EndDate = eventDto.EndDate;
model.EndTime = eventDto.EndTime;
model.AllDay = eventDto.AllDay;
model.LastModificationTime = DateTime.Now;
_db.Events.Update(model);
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Event Updated Successfully", Status = "200" });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
// ===================================================================================
// GET EVENT BY ID
// ===================================================================================
[HttpGet]
[Route("GetEventById/{id}")]
public async Task<IActionResult> GetEventById(int id)
{
try
{
var model = await _db.Events
.Where(e => e.Id == id && e.IsDeleted != true)
.FirstOrDefaultAsync();
if (model == null)
{
return NotFound(new Response { Status = "Error", Message = "Event not found" });
}
var dto = new Event_DTO
{
Id = model.Id,
Title = model.Title,
Description = model.Description,
Location = model.Location,
RecordID = model.RecordID,
RecordName = model.RecordName,
RecordTargetType = model.RecordTargetType,
ReminderMinutes = model.ReminderMinutes,
EventColor = model.EventColor,
StartDate = model.StartDate,
StartTime = model.StartTime,
EndDate = model.EndDate,
EndTime = model.EndTime,
AllDay = model.AllDay,
CreatedDate = model.CreatedDate,
createdby = model.createdby
};
return Ok(new DataResponse { Message = "Success", Status = "200", Data = dto });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
// ===================================================================================
// DELETE EVENT
// ===================================================================================
[HttpDelete]
[Route("DeleteEvent/{id}")]
public async Task<IActionResult> DeleteEvent(int id)
{
try
{
var model = await _db.Events.FindAsync(id);
if (model == null)
{
return NotFound(new Response { Status = "Error", Message = "Event not found" });
}
// Soft delete
model.IsDeleted = true;
model.DeletionTime = DateTime.Now;
_db.Events.Update(model);
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Event Deleted Successfully", Status = "200" });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
// ===================================================================================
// GET EVENTS LIST (WITH PAGINATION AND FILTERS)
// ===================================================================================
[HttpPost]
[Route("GetEventList")]
public IActionResult GetEventList(Pagination_DTO dto)
{
try
{
var query = _db.Events.Where(e => e.IsDeleted != true);
// Apply search filter if provided
if (!string.IsNullOrWhiteSpace(dto.Search))
{
query = query.Where(e =>
e.Title.Contains(dto.Search) ||
(e.Description != null && e.Description.Contains(dto.Search)) ||
(e.Location != null && e.Location.Contains(dto.Search))
);
}
// IMPORTANT: Calculate totalCount AFTER applying filters
var totalCount = query.Count();
// Apply pagination
var events = query
.OrderByDescending(e => e.StartDate)
.ThenByDescending(e => e.CreatedDate)
.Skip((dto.PageNumber - 1) * dto.PageSize)
.Take(dto.PageSize)
.Select(e => new Event_DTO
{
Id = e.Id,
Title = e.Title,
Description = e.Description,
Location = e.Location,
RecordID = e.RecordID,
RecordName = e.RecordName,
RecordTargetType = e.RecordTargetType,
ReminderMinutes = e.ReminderMinutes,
EventColor = e.EventColor,
StartDate = e.StartDate,
StartTime = e.StartTime,
EndDate = e.EndDate,
EndTime = e.EndTime,
AllDay = e.AllDay,
CreatedDate = e.CreatedDate,
createdby = e.createdby
})
.ToList();
return Ok(new DataResponse
{
Message = "Success",
Status = "200",
Data = new { rows = events, totalCount }
});
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
// ===================================================================================
// GET EVENTS FOR CALENDAR (Date Range Filter)
// ===================================================================================
[HttpGet]
[Route("GetEventsForCalendar")]
public IActionResult GetEventsForCalendar([FromQuery] DateTime? start, [FromQuery] DateTime? end)
{
try
{
var query = _db.Events.Where(e => e.IsDeleted != true);
// Filter by date range if provided
if (start.HasValue && end.HasValue)
{
query = query.Where(e =>
(e.StartDate >= start.Value && e.StartDate <= end.Value) ||
(e.EndDate >= start.Value && e.EndDate <= end.Value) ||
(e.StartDate <= start.Value && e.EndDate >= end.Value)
);
}
var events = query
.Select(e => new Event_DTO
{
Id = e.Id,
Title = e.Title,
Description = e.Description,
Location = e.Location,
RecordID = e.RecordID,
RecordName = e.RecordName,
RecordTargetType = e.RecordTargetType,
ReminderMinutes = e.ReminderMinutes,
EventColor = e.EventColor,
StartDate = e.StartDate,
StartTime = e.StartTime,
EndDate = e.EndDate,
EndTime = e.EndTime,
AllDay = e.AllDay,
CreatedDate = e.CreatedDate,
createdby = e.createdby
})
.ToList();
return Ok(new DataResponse { Message = "Success", Status = "200", Data = events });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
}
}
/*
===================================================================================
SECTION 4: DATABASE SETUP
===================================================================================
STEP 1: Add Events DbSet to ApplicationDbContext.cs
-------------------------------------------------------
Add this line in the ApplicationDbContext class (around line 48):
public DbSet<Events> Events { get; set; }
STEP 2: Create Database Migration
-------------------------------------------------------
Run these commands in the terminal from the Api.SeaHavenIndustries project directory:
dotnet ef migrations add AddEventsTable --project ../Data.SeaHavenIndustries
dotnet ef database update
STEP 3: SQL Script for Manual Table Creation (Alternative to migrations)
-------------------------------------------------------
*/
-- SQL SCRIPT TO CREATE EVENTS TABLE
/*
CREATE TABLE Events (
Id INT PRIMARY KEY IDENTITY(1,1),
Title NVARCHAR(100) NOT NULL,
Description NVARCHAR(MAX) NULL,
Location NVARCHAR(150) NULL,
RecordID INT NULL,
RecordName NVARCHAR(255) NULL,
RecordTargetType NVARCHAR(50) NULL,
ReminderMinutes INT NOT NULL DEFAULT 15,
EventColor NVARCHAR(20) NOT NULL DEFAULT 'primary',
StartDate DATETIME NOT NULL,
StartTime NVARCHAR(10) NULL,
EndDate DATETIME NOT NULL,
EndTime NVARCHAR(10) NULL,
AllDay BIT NOT NULL DEFAULT 0,
-- Audit fields
IsDeleted BIT NULL,
createdby NVARCHAR(450) NULL,
DeleterUserId NVARCHAR(450) NULL,
DeletionTime DATETIME NULL,
CreatedDate DATETIME NULL,
LastModificationTime DATETIME NULL,
LastModifierUserId INT NULL
);
-- Insert sample data
INSERT INTO Events (Title, Description, Location, StartDate, StartTime, EndDate, EndTime, AllDay, EventColor, ReminderMinutes, CreatedDate, IsDeleted)
VALUES
('Team Meeting', 'Weekly team sync', 'Conference Room A', '2026-04-01', '10:00 AM', '2026-04-01', '11:00 AM', 0, 'primary', 15, GETDATE(), 0),
('Client Presentation', 'Q1 Results Review with ABC Corp', 'Virtual - Zoom', '2026-04-03', '02:00 PM', '2026-04-03', '03:30 PM', 0, 'success', 30, GETDATE(), 0),
('Project Deadline', 'Final delivery for Project Phoenix', NULL, '2026-04-05', NULL, '2026-04-05', NULL, 1, 'danger', 1440, GETDATE(), 0),
('Training Session', 'New system training for all employees', 'Main Office', '2026-04-08', '09:00 AM', '2026-04-08', '12:00 PM', 0, 'info', 60, GETDATE(), 0),
('Equipment Maintenance', 'Scheduled maintenance for all assets', 'Warehouse', '2026-04-10', '08:00 AM', '2026-04-10', '05:00 PM', 0, 'warning', 120, GETDATE(), 0);
*/
/*
===================================================================================
SECTION 5: IMPLEMENTATION CHECKLIST
===================================================================================
BACKEND SETUP:
--------------
1. ✓ Create Events.cs in Data.SeaHavenIndustries/Models/
2. ✓ Create Event_DTO.cs in Api.SeaHavenIndustries/DTOs/
3. ✓ Create EditEvent_DTO.cs in Api.SeaHavenIndustries/DTOs/
4. ✓ Create CalendarController.cs in Api.SeaHavenIndustries/Controllers/
5. ☐ Add DbSet<Events> to ApplicationDbContext.cs
6. ☐ Run migrations (or execute SQL script)
7. ☐ Test all endpoints using Postman/Swagger
FRONTEND SETUP (Next Step):
---------------------------
1. ☐ Create src/pages/calendar/CalendarPage.js (List view with FullCalendar)
2. ☐ Create src/pages/calendar/EventFormPage.js (Create/Edit form)
3. ☐ Create src/pages/calendar/api.js (API calls)
4. ☐ Add routes to App.js
5. ☐ Install FullCalendar: npm install @fullcalendar/react @fullcalendar/daygrid @fullcalendar/timegrid @fullcalendar/interaction
API ENDPOINTS SUMMARY:
---------------------
POST /api/Calendar/AddEvent - Create new event
POST /api/Calendar/EditEvent - Update existing event
GET /api/Calendar/GetEventById/{id} - Get single event by ID
DELETE /api/Calendar/DeleteEvent/{id} - Delete event (soft delete)
POST /api/Calendar/GetEventList - Get paginated list with search
GET /api/Calendar/GetEventsForCalendar - Get events for calendar view (with date range)
KEY FEATURES IMPLEMENTED:
-------------------------
✓ CRUD operations (Create, Read, Update, Delete)
✓ Pagination with search filter
✓ Date range filtering for calendar view
✓ Soft delete (IsDeleted flag)
✓ Related record linking (RecordID, RecordName, RecordTargetType)
✓ Color coding for calendar display
✓ All-day event support
✓ Reminder settings
✓ Proper error handling
✓ Consistent with existing controller patterns (AssetController)
✓ Fixed pagination bug (totalCount calculated AFTER filters)
NOTES:
------
- EventColor values: 'primary', 'success', 'info', 'warning', 'danger'
- ReminderMinutes: 0=Never, 5, 10, 15, 30, 60, 120, 240, 360, 720, 1440 (1 day)
- StartTime/EndTime format: "08:00 AM", "02:30 PM", etc.
- AllDay: When true, time fields can be null/ignored
- RecordTargetType: Can be "Account", "Contact", "WorkOrder", "FollowUp", etc.
===================================================================================
END OF FILE
===================================================================================
*/

File diff suppressed because it is too large Load diff

View file

@ -24,15 +24,6 @@ namespace Data.SeaHavenIndustries
{
foreignKey.DeleteBehavior = DeleteBehavior.Restrict;
}
builder.Entity<VendorAccessToken>()
.HasIndex(t => t.Token)
.IsUnique();
builder.Entity<DispatchUpliftRequest>()
.HasIndex(u => new { u.DispatchId, u.Status })
.IsUnique()
.HasFilter("[Status] = 'Pending'");
}
public DbSet<Category> Categories { get; set; }
public DbSet<Locations> Locations { get; set; }
@ -46,17 +37,6 @@ namespace Data.SeaHavenIndustries
public DbSet<WorkOrderContacts> WorkOrderContacts { get; set; }
public DbSet<WorkOrderCategories> workOrderCategories { get; set; }
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<VendorAccessToken> VendorAccessTokens { get; set; }
public DbSet<Dispatch> Dispatches { get; set; }
public DbSet<DispatchWorkOrder> DispatchWorkOrders { get; set; }
public DbSet<DispatchChecklistItem> DispatchChecklistItems { get; set; }
public DbSet<DispatchSignoff> DispatchSignoffs { get; set; }
public DbSet<DispatchUpliftRequest> DispatchUpliftRequests { get; set; }
public DbSet<TaskListTemplate> TaskListTemplates { get; set; }
public DbSet<TaskListTemplateItem> TaskListTemplateItems { get; set; }
// New DbSets for added entities
public DbSet<Accounts> Accounts { get; set; }
@ -66,6 +46,7 @@ namespace Data.SeaHavenIndustries
public DbSet<FollowUps> FollowUps { get; set; }
public DbSet<PMSchedules> PMSchedules { get; set; }
public DbSet<ContactDetails> ContactDetails { get; set; }
public DbSet<Events> Events { get; set; }
// Employee related DbSets
public DbSet<EmployeeAddress> EmployeeAddresses { get; set; }

View file

@ -1,852 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.SeaHavenIndustries.Migrations
{
/// <inheritdoc />
public partial class SyncContactsAndModels : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "Name",
table: "Contacts",
newName: "Zip");
migrationBuilder.AddColumn<int>(
name: "AccountId",
table: "Contacts",
type: "int",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Address1",
table: "Contacts",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Address2",
table: "Contacts",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "City",
table: "Contacts",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ContactType",
table: "Contacts",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "FacebookUrl",
table: "Contacts",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "FirstName",
table: "Contacts",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "LastName",
table: "Contacts",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "LinkedInUrl",
table: "Contacts",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "LocationId",
table: "Contacts",
type: "int",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "MiddleName",
table: "Contacts",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Owner",
table: "Contacts",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "State",
table: "Contacts",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Title",
table: "Contacts",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "TwitterUrl",
table: "Contacts",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "AspNetUserTokens",
type: "nvarchar(450)",
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(128)",
oldMaxLength: 128);
migrationBuilder.AlterColumn<string>(
name: "LoginProvider",
table: "AspNetUserTokens",
type: "nvarchar(450)",
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(128)",
oldMaxLength: 128);
migrationBuilder.AlterColumn<string>(
name: "ProviderKey",
table: "AspNetUserLogins",
type: "nvarchar(450)",
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(128)",
oldMaxLength: 128);
migrationBuilder.AlterColumn<string>(
name: "LoginProvider",
table: "AspNetUserLogins",
type: "nvarchar(450)",
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(128)",
oldMaxLength: 128);
migrationBuilder.CreateTable(
name: "Accounts",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(max)", nullable: true),
Type = table.Column<string>(type: "nvarchar(max)", nullable: true),
Importance = table.Column<int>(type: "int", nullable: true),
TerritoryId = table.Column<int>(type: "int", nullable: true),
OwnerEmployeeId = table.Column<int>(type: "int", nullable: true),
PrivacySetting = table.Column<string>(type: "nvarchar(max)", nullable: true),
About = table.Column<string>(type: "nvarchar(max)", nullable: true),
Tags = table.Column<string>(type: "nvarchar(max)", nullable: true),
Phone = table.Column<string>(type: "nvarchar(max)", nullable: true),
Email = table.Column<string>(type: "nvarchar(max)", nullable: true),
IndustryId = table.Column<int>(type: "int", nullable: true),
TimeZone = table.Column<string>(type: "nvarchar(max)", nullable: true),
Website = table.Column<string>(type: "nvarchar(max)", nullable: true),
CurrencyCode = table.Column<string>(type: "nvarchar(max)", nullable: true),
ServiceInstructions = table.Column<string>(type: "nvarchar(max)", nullable: true),
FacebookUrl = table.Column<string>(type: "nvarchar(max)", nullable: true),
LinkedInUrl = table.Column<string>(type: "nvarchar(max)", nullable: true),
TwitterUrl = 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_Accounts", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ContactDetails",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
ContactId = table.Column<int>(type: "int", nullable: false),
Gender = table.Column<string>(type: "nvarchar(max)", nullable: true),
DateOfBirth = table.Column<DateTime>(type: "datetime2", nullable: true),
Territory = table.Column<string>(type: "nvarchar(max)", nullable: true),
PrivacySetting = table.Column<string>(type: "nvarchar(max)", nullable: true),
About = table.Column<string>(type: "nvarchar(max)", nullable: true),
Tags = table.Column<string>(type: "nvarchar(max)", nullable: true),
FacebookUrl = table.Column<string>(type: "nvarchar(max)", nullable: true),
LinkedInUrl = table.Column<string>(type: "nvarchar(max)", nullable: true),
TwitterUrl = 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_ContactDetails", x => x.Id);
table.ForeignKey(
name: "FK_ContactDetails_Contacts_ContactId",
column: x => x.ContactId,
principalTable: "Contacts",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "Departments",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, 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_Departments", x => x.Id);
});
migrationBuilder.CreateTable(
name: "JobTitles",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, 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_JobTitles", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Regions",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, 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_Regions", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Addresses",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
AddressType = table.Column<string>(type: "nvarchar(max)", nullable: true),
Address1 = table.Column<string>(type: "nvarchar(max)", nullable: true),
Address2 = 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),
Country = table.Column<string>(type: "nvarchar(max)", nullable: true),
County = table.Column<string>(type: "nvarchar(max)", nullable: true),
AccountId = table.Column<int>(type: "int", nullable: true),
ContactId = table.Column<int>(type: "int", 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_Addresses", x => x.Id);
table.ForeignKey(
name: "FK_Addresses_Accounts_AccountId",
column: x => x.AccountId,
principalTable: "Accounts",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Addresses_Contacts_ContactId",
column: x => x.ContactId,
principalTable: "Contacts",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "Assets",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
AssetCode = table.Column<string>(type: "nvarchar(max)", nullable: false),
Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
Description = table.Column<string>(type: "nvarchar(max)", nullable: true),
AccountId = table.Column<int>(type: "int", nullable: true),
LocationId = table.Column<int>(type: "int", 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_Assets", x => x.Id);
table.ForeignKey(
name: "FK_Assets_Accounts_AccountId",
column: x => x.AccountId,
principalTable: "Accounts",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Assets_Locations_LocationId",
column: x => x.LocationId,
principalTable: "Locations",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "Employees",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
FirstName = table.Column<string>(type: "nvarchar(max)", nullable: false),
MiddleName = table.Column<string>(type: "nvarchar(max)", nullable: true),
LastName = table.Column<string>(type: "nvarchar(max)", nullable: false),
DepartmentId = table.Column<int>(type: "int", nullable: true),
TimeZone = table.Column<string>(type: "nvarchar(max)", nullable: true),
Gender = table.Column<int>(type: "int", nullable: true),
DateOfBirth = table.Column<DateTime>(type: "datetime2", nullable: true),
JobTitleId = table.Column<int>(type: "int", nullable: true),
EmployeeNumber = table.Column<string>(type: "nvarchar(max)", nullable: true),
HireDate = table.Column<DateTime>(type: "datetime2", nullable: true),
RegionId = table.Column<int>(type: "int", nullable: true),
CanChangeProviderNumber = table.Column<bool>(type: "bit", nullable: true),
About = table.Column<string>(type: "nvarchar(max)", nullable: true),
PermissionGroupId = table.Column<int>(type: "int", nullable: true),
UserName = table.Column<string>(type: "nvarchar(max)", nullable: true),
ConfirmUserName = table.Column<string>(type: "nvarchar(max)", nullable: true),
SMSAddress = table.Column<string>(type: "nvarchar(max)", nullable: true),
Website = table.Column<string>(type: "nvarchar(max)", nullable: true),
FacebookUrl = table.Column<string>(type: "nvarchar(max)", nullable: true),
LinkedInUrl = table.Column<string>(type: "nvarchar(max)", nullable: true),
TwitterUrl = table.Column<string>(type: "nvarchar(max)", nullable: true),
Status = table.Column<string>(type: "nvarchar(max)", nullable: true),
LastLogin = table.Column<DateTime>(type: "datetime2", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: true),
CreatedBy = table.Column<string>(type: "nvarchar(max)", 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_Employees", x => x.Id);
table.ForeignKey(
name: "FK_Employees_Departments_DepartmentId",
column: x => x.DepartmentId,
principalTable: "Departments",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Employees_JobTitles_JobTitleId",
column: x => x.JobTitleId,
principalTable: "JobTitles",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Employees_Regions_RegionId",
column: x => x.RegionId,
principalTable: "Regions",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "PMSchedules",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
Frequency = table.Column<string>(type: "nvarchar(max)", nullable: false),
StartDate = table.Column<DateTime>(type: "datetime2", nullable: false),
IsActive = table.Column<bool>(type: "bit", nullable: false),
AssetId = table.Column<int>(type: "int", nullable: true),
LocationId = table.Column<int>(type: "int", 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_PMSchedules", x => x.Id);
table.ForeignKey(
name: "FK_PMSchedules_Assets_AssetId",
column: x => x.AssetId,
principalTable: "Assets",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_PMSchedules_Locations_LocationId",
column: x => x.LocationId,
principalTable: "Locations",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "EmployeeAddresses",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
EmployeeId = table.Column<int>(type: "int", nullable: false),
Street = table.Column<string>(type: "nvarchar(max)", nullable: true),
StreetLineTwo = table.Column<string>(type: "nvarchar(max)", nullable: true),
City = table.Column<string>(type: "nvarchar(max)", nullable: true),
Zip = table.Column<string>(type: "nvarchar(max)", nullable: true),
CountryCode = table.Column<string>(type: "nvarchar(max)", nullable: true),
StateCode = table.Column<string>(type: "nvarchar(max)", nullable: true),
CountyId = table.Column<int>(type: "int", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_EmployeeAddresses", x => x.Id);
table.ForeignKey(
name: "FK_EmployeeAddresses_Employees_EmployeeId",
column: x => x.EmployeeId,
principalTable: "Employees",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "EmployeeEmails",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
EmployeeId = table.Column<int>(type: "int", nullable: false),
EmailTypeId = table.Column<int>(type: "int", nullable: true),
Address = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
IsPrimary = table.Column<bool>(type: "bit", 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_EmployeeEmails", x => x.Id);
table.ForeignKey(
name: "FK_EmployeeEmails_Employees_EmployeeId",
column: x => x.EmployeeId,
principalTable: "Employees",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "EmployeePhones",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
EmployeeId = table.Column<int>(type: "int", nullable: false),
PhoneTypeId = table.Column<int>(type: "int", nullable: true),
Number = table.Column<string>(type: "nvarchar(max)", nullable: true),
Extension = table.Column<string>(type: "nvarchar(max)", nullable: true),
IsPrimary = table.Column<bool>(type: "bit", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_EmployeePhones", x => x.Id);
table.ForeignKey(
name: "FK_EmployeePhones_Employees_EmployeeId",
column: x => x.EmployeeId,
principalTable: "Employees",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "FollowUps",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Reason = table.Column<string>(type: "nvarchar(max)", nullable: false),
ScheduleDate = table.Column<DateTime>(type: "datetime2", nullable: false),
ScheduleStartTime = table.Column<TimeSpan>(type: "time", nullable: false),
Status = table.Column<string>(type: "nvarchar(max)", nullable: false),
EmployeeId = table.Column<int>(type: "int", nullable: true),
WorkOrderId = table.Column<int>(type: "int", nullable: true),
AccountId = table.Column<int>(type: "int", nullable: true),
LocationId = table.Column<int>(type: "int", 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_FollowUps", x => x.Id);
table.ForeignKey(
name: "FK_FollowUps_Accounts_AccountId",
column: x => x.AccountId,
principalTable: "Accounts",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_FollowUps_Employees_EmployeeId",
column: x => x.EmployeeId,
principalTable: "Employees",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_FollowUps_Locations_LocationId",
column: x => x.LocationId,
principalTable: "Locations",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_FollowUps_workOrders_WorkOrderId",
column: x => x.WorkOrderId,
principalTable: "workOrders",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_Contacts_AccountId",
table: "Contacts",
column: "AccountId");
migrationBuilder.CreateIndex(
name: "IX_Contacts_LocationId",
table: "Contacts",
column: "LocationId");
migrationBuilder.CreateIndex(
name: "IX_Addresses_AccountId",
table: "Addresses",
column: "AccountId");
migrationBuilder.CreateIndex(
name: "IX_Addresses_ContactId",
table: "Addresses",
column: "ContactId");
migrationBuilder.CreateIndex(
name: "IX_Assets_AccountId",
table: "Assets",
column: "AccountId");
migrationBuilder.CreateIndex(
name: "IX_Assets_LocationId",
table: "Assets",
column: "LocationId");
migrationBuilder.CreateIndex(
name: "IX_ContactDetails_ContactId",
table: "ContactDetails",
column: "ContactId");
migrationBuilder.CreateIndex(
name: "IX_EmployeeAddresses_EmployeeId",
table: "EmployeeAddresses",
column: "EmployeeId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_EmployeeEmails_EmployeeId",
table: "EmployeeEmails",
column: "EmployeeId");
migrationBuilder.CreateIndex(
name: "IX_EmployeePhones_EmployeeId",
table: "EmployeePhones",
column: "EmployeeId");
migrationBuilder.CreateIndex(
name: "IX_Employees_DepartmentId",
table: "Employees",
column: "DepartmentId");
migrationBuilder.CreateIndex(
name: "IX_Employees_JobTitleId",
table: "Employees",
column: "JobTitleId");
migrationBuilder.CreateIndex(
name: "IX_Employees_RegionId",
table: "Employees",
column: "RegionId");
migrationBuilder.CreateIndex(
name: "IX_FollowUps_AccountId",
table: "FollowUps",
column: "AccountId");
migrationBuilder.CreateIndex(
name: "IX_FollowUps_EmployeeId",
table: "FollowUps",
column: "EmployeeId");
migrationBuilder.CreateIndex(
name: "IX_FollowUps_LocationId",
table: "FollowUps",
column: "LocationId");
migrationBuilder.CreateIndex(
name: "IX_FollowUps_WorkOrderId",
table: "FollowUps",
column: "WorkOrderId");
migrationBuilder.CreateIndex(
name: "IX_PMSchedules_AssetId",
table: "PMSchedules",
column: "AssetId");
migrationBuilder.CreateIndex(
name: "IX_PMSchedules_LocationId",
table: "PMSchedules",
column: "LocationId");
migrationBuilder.AddForeignKey(
name: "FK_Contacts_Accounts_AccountId",
table: "Contacts",
column: "AccountId",
principalTable: "Accounts",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
migrationBuilder.AddForeignKey(
name: "FK_Contacts_Locations_LocationId",
table: "Contacts",
column: "LocationId",
principalTable: "Locations",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Contacts_Accounts_AccountId",
table: "Contacts");
migrationBuilder.DropForeignKey(
name: "FK_Contacts_Locations_LocationId",
table: "Contacts");
migrationBuilder.DropTable(
name: "Addresses");
migrationBuilder.DropTable(
name: "ContactDetails");
migrationBuilder.DropTable(
name: "EmployeeAddresses");
migrationBuilder.DropTable(
name: "EmployeeEmails");
migrationBuilder.DropTable(
name: "EmployeePhones");
migrationBuilder.DropTable(
name: "FollowUps");
migrationBuilder.DropTable(
name: "PMSchedules");
migrationBuilder.DropTable(
name: "Employees");
migrationBuilder.DropTable(
name: "Assets");
migrationBuilder.DropTable(
name: "Departments");
migrationBuilder.DropTable(
name: "JobTitles");
migrationBuilder.DropTable(
name: "Regions");
migrationBuilder.DropTable(
name: "Accounts");
migrationBuilder.DropIndex(
name: "IX_Contacts_AccountId",
table: "Contacts");
migrationBuilder.DropIndex(
name: "IX_Contacts_LocationId",
table: "Contacts");
migrationBuilder.DropColumn(
name: "AccountId",
table: "Contacts");
migrationBuilder.DropColumn(
name: "Address1",
table: "Contacts");
migrationBuilder.DropColumn(
name: "Address2",
table: "Contacts");
migrationBuilder.DropColumn(
name: "City",
table: "Contacts");
migrationBuilder.DropColumn(
name: "ContactType",
table: "Contacts");
migrationBuilder.DropColumn(
name: "FacebookUrl",
table: "Contacts");
migrationBuilder.DropColumn(
name: "FirstName",
table: "Contacts");
migrationBuilder.DropColumn(
name: "LastName",
table: "Contacts");
migrationBuilder.DropColumn(
name: "LinkedInUrl",
table: "Contacts");
migrationBuilder.DropColumn(
name: "LocationId",
table: "Contacts");
migrationBuilder.DropColumn(
name: "MiddleName",
table: "Contacts");
migrationBuilder.DropColumn(
name: "Owner",
table: "Contacts");
migrationBuilder.DropColumn(
name: "State",
table: "Contacts");
migrationBuilder.DropColumn(
name: "Title",
table: "Contacts");
migrationBuilder.DropColumn(
name: "TwitterUrl",
table: "Contacts");
migrationBuilder.RenameColumn(
name: "Zip",
table: "Contacts",
newName: "Name");
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "AspNetUserTokens",
type: "nvarchar(128)",
maxLength: 128,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(450)");
migrationBuilder.AlterColumn<string>(
name: "LoginProvider",
table: "AspNetUserTokens",
type: "nvarchar(128)",
maxLength: 128,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(450)");
migrationBuilder.AlterColumn<string>(
name: "ProviderKey",
table: "AspNetUserLogins",
type: "nvarchar(128)",
maxLength: 128,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(450)");
migrationBuilder.AlterColumn<string>(
name: "LoginProvider",
table: "AspNetUserLogins",
type: "nvarchar(128)",
maxLength: 128,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(450)");
}
}
}

View file

@ -1,129 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.SeaHavenIndustries.Migrations
{
/// <inheritdoc />
public partial class AddLocationFields : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "AccountId",
table: "Locations",
type: "int",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Address",
table: "Locations",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "City",
table: "Locations",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Contact",
table: "Locations",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ContactEmail",
table: "Locations",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Phone",
table: "Locations",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "State",
table: "Locations",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Status",
table: "Locations",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ZipCode",
table: "Locations",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_Locations_AccountId",
table: "Locations",
column: "AccountId");
migrationBuilder.AddForeignKey(
name: "FK_Locations_Accounts_AccountId",
table: "Locations",
column: "AccountId",
principalTable: "Accounts",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Locations_Accounts_AccountId",
table: "Locations");
migrationBuilder.DropIndex(
name: "IX_Locations_AccountId",
table: "Locations");
migrationBuilder.DropColumn(
name: "AccountId",
table: "Locations");
migrationBuilder.DropColumn(
name: "Address",
table: "Locations");
migrationBuilder.DropColumn(
name: "City",
table: "Locations");
migrationBuilder.DropColumn(
name: "Contact",
table: "Locations");
migrationBuilder.DropColumn(
name: "ContactEmail",
table: "Locations");
migrationBuilder.DropColumn(
name: "Phone",
table: "Locations");
migrationBuilder.DropColumn(
name: "State",
table: "Locations");
migrationBuilder.DropColumn(
name: "Status",
table: "Locations");
migrationBuilder.DropColumn(
name: "ZipCode",
table: "Locations");
}
}
}

View file

@ -1,129 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.SeaHavenIndustries.Migrations
{
/// <inheritdoc />
public partial class AddExternalSyncFields : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Building",
table: "workOrders",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Customer",
table: "workOrders",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateReported",
table: "workOrders",
type: "datetime2",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ExternalWorkOrderId",
table: "workOrders",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "ScheduledStart",
table: "workOrders",
type: "datetime2",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Severity",
table: "workOrders",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "SiteCode",
table: "workOrders",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "SourceEmailS3Key",
table: "workOrders",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Commenter",
table: "Comments",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ExternalCommentId",
table: "Comments",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "RecordType",
table: "Comments",
type: "nvarchar(max)",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Building",
table: "workOrders");
migrationBuilder.DropColumn(
name: "Customer",
table: "workOrders");
migrationBuilder.DropColumn(
name: "DateReported",
table: "workOrders");
migrationBuilder.DropColumn(
name: "ExternalWorkOrderId",
table: "workOrders");
migrationBuilder.DropColumn(
name: "ScheduledStart",
table: "workOrders");
migrationBuilder.DropColumn(
name: "Severity",
table: "workOrders");
migrationBuilder.DropColumn(
name: "SiteCode",
table: "workOrders");
migrationBuilder.DropColumn(
name: "SourceEmailS3Key",
table: "workOrders");
migrationBuilder.DropColumn(
name: "Commenter",
table: "Comments");
migrationBuilder.DropColumn(
name: "ExternalCommentId",
table: "Comments");
migrationBuilder.DropColumn(
name: "RecordType",
table: "Comments");
}
}
}

View file

@ -1,73 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.SeaHavenIndustries.Migrations
{
/// <inheritdoc />
public partial class AddCommentTypeAndAuditLog : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "CommentType",
table: "Comments",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.CreateTable(
name: "WorkOrderAuditLogs",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
WorkOrderId = table.Column<int>(type: "int", nullable: false),
UserId = table.Column<string>(type: "nvarchar(450)", nullable: true),
FieldName = table.Column<string>(type: "nvarchar(max)", nullable: true),
OldValue = table.Column<string>(type: "nvarchar(max)", nullable: true),
NewValue = table.Column<string>(type: "nvarchar(max)", nullable: true),
Action = table.Column<string>(type: "nvarchar(max)", nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_WorkOrderAuditLogs", x => x.Id);
table.ForeignKey(
name: "FK_WorkOrderAuditLogs_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_WorkOrderAuditLogs_workOrders_WorkOrderId",
column: x => x.WorkOrderId,
principalTable: "workOrders",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_WorkOrderAuditLogs_UserId",
table: "WorkOrderAuditLogs",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_WorkOrderAuditLogs_WorkOrderId",
table: "WorkOrderAuditLogs",
column: "WorkOrderId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "WorkOrderAuditLogs");
migrationBuilder.DropColumn(
name: "CommentType",
table: "Comments");
}
}
}

View file

@ -1,28 +0,0 @@
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

@ -1,109 +0,0 @@
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");
}
}
}

View file

@ -1,104 +0,0 @@
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");
}
}
}

View file

@ -1,29 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.SeaHavenIndustries.Migrations
{
/// <inheritdoc />
public partial class AddDispatchScheduledDate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "ScheduledDate",
table: "Dispatches",
type: "datetime2",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ScheduledDate",
table: "Dispatches");
}
}
}

View file

@ -1,70 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.SeaHavenIndustries.Migrations
{
/// <inheritdoc />
public partial class AddDispatchNumberAndCommentDispatchId : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "CompletedDate",
table: "Dispatches",
type: "datetime2",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "DispatchNumber",
table: "Dispatches",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "DispatchId",
table: "Comments",
type: "int",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_Comments_DispatchId",
table: "Comments",
column: "DispatchId");
migrationBuilder.AddForeignKey(
name: "FK_Comments_Dispatches_DispatchId",
table: "Comments",
column: "DispatchId",
principalTable: "Dispatches",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Comments_Dispatches_DispatchId",
table: "Comments");
migrationBuilder.DropIndex(
name: "IX_Comments_DispatchId",
table: "Comments");
migrationBuilder.DropColumn(
name: "CompletedDate",
table: "Dispatches");
migrationBuilder.DropColumn(
name: "DispatchNumber",
table: "Dispatches");
migrationBuilder.DropColumn(
name: "DispatchId",
table: "Comments");
}
}
}

View file

@ -1,80 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.SeaHavenIndustries.Migrations
{
/// <inheritdoc />
public partial class AddTaskListTemplates : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "TaskListTemplates",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(max)", nullable: true),
Description = 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_TaskListTemplates", x => x.Id);
});
migrationBuilder.CreateTable(
name: "TaskListTemplateItems",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
TaskListTemplateId = table.Column<int>(type: "int", nullable: false),
ItemText = table.Column<string>(type: "nvarchar(max)", 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_TaskListTemplateItems", x => x.Id);
table.ForeignKey(
name: "FK_TaskListTemplateItems_TaskListTemplates_TaskListTemplateId",
column: x => x.TaskListTemplateId,
principalTable: "TaskListTemplates",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_TaskListTemplateItems_TaskListTemplateId",
table: "TaskListTemplateItems",
column: "TaskListTemplateId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "TaskListTemplateItems");
migrationBuilder.DropTable(
name: "TaskListTemplates");
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,96 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.SeaHavenIndustries.Migrations
{
/// <inheritdoc />
public partial class MultiWODispatch : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<int>(
name: "WorkOrderId",
table: "Dispatches",
type: "int",
nullable: true,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.AddColumn<string>(
name: "AcceptToken",
table: "Dispatches",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "AcknowledgedAt",
table: "Dispatches",
type: "datetime2",
nullable: true);
migrationBuilder.CreateTable(
name: "DispatchWorkOrders",
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: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DispatchWorkOrders", x => x.Id);
table.ForeignKey(
name: "FK_DispatchWorkOrders_Dispatches_DispatchId",
column: x => x.DispatchId,
principalTable: "Dispatches",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_DispatchWorkOrders_workOrders_WorkOrderId",
column: x => x.WorkOrderId,
principalTable: "workOrders",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_DispatchWorkOrders_DispatchId",
table: "DispatchWorkOrders",
column: "DispatchId");
migrationBuilder.CreateIndex(
name: "IX_DispatchWorkOrders_WorkOrderId",
table: "DispatchWorkOrders",
column: "WorkOrderId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "DispatchWorkOrders");
migrationBuilder.DropColumn(
name: "AcceptToken",
table: "Dispatches");
migrationBuilder.DropColumn(
name: "AcknowledgedAt",
table: "Dispatches");
migrationBuilder.AlterColumn<int>(
name: "WorkOrderId",
table: "Dispatches",
type: "int",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "int",
oldNullable: true);
}
}
}

View file

@ -1,70 +0,0 @@
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");
}
}
}

View file

@ -1,58 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.SeaHavenIndustries.Migrations
{
/// <inheritdoc />
public partial class AddDispatchSignoffs : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "DispatchSignoffs",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
DispatchId = table.Column<int>(type: "int", nullable: false),
SignoffType = table.Column<string>(type: "nvarchar(max)", nullable: true),
Name = table.Column<string>(type: "nvarchar(max)", nullable: true),
Signature = table.Column<string>(type: "nvarchar(max)", nullable: true),
SignatureMethod = table.Column<string>(type: "nvarchar(max)", nullable: true),
SignedAt = table.Column<DateTime>(type: "datetime2", 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_DispatchSignoffs", x => x.Id);
table.ForeignKey(
name: "FK_DispatchSignoffs_Dispatches_DispatchId",
column: x => x.DispatchId,
principalTable: "Dispatches",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_DispatchSignoffs_DispatchId",
table: "DispatchSignoffs",
column: "DispatchId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "DispatchSignoffs");
}
}
}

View file

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

View file

@ -1,64 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.SeaHavenIndustries.Migrations
{
/// <inheritdoc />
public partial class AddVendorAccessToken : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "VendorAccessTokens",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
VendorId = table.Column<int>(type: "int", nullable: false),
Token = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
IssuedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
ExpiresAt = table.Column<DateTime>(type: "datetime2", nullable: false),
RevokedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
LastUsedAt = table.Column<DateTime>(type: "datetime2", 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_VendorAccessTokens", x => x.Id);
table.ForeignKey(
name: "FK_VendorAccessTokens_Vendors_VendorId",
column: x => x.VendorId,
principalTable: "Vendors",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_VendorAccessTokens_Token",
table: "VendorAccessTokens",
column: "Token",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_VendorAccessTokens_VendorId",
table: "VendorAccessTokens",
column: "VendorId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "VendorAccessTokens");
}
}
}

View file

@ -1,64 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.SeaHavenIndustries.Migrations
{
/// <inheritdoc />
public partial class AddDispatchUpliftRequest : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "DispatchUpliftRequests",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
DispatchId = table.Column<int>(type: "int", nullable: false),
CurrentNTE = table.Column<decimal>(type: "decimal(18,2)", nullable: true),
RequestedNTE = table.Column<decimal>(type: "decimal(18,2)", nullable: false),
VendorReason = table.Column<string>(type: "nvarchar(max)", nullable: true),
Status = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: false),
RequiredTier = table.Column<int>(type: "int", nullable: false),
RequestedByVendorName = table.Column<string>(type: "nvarchar(max)", nullable: true),
DecidedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
DecidedByUserId = table.Column<string>(type: "nvarchar(max)", nullable: true),
DecisionNote = 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_DispatchUpliftRequests", x => x.Id);
table.ForeignKey(
name: "FK_DispatchUpliftRequests_Dispatches_DispatchId",
column: x => x.DispatchId,
principalTable: "Dispatches",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_DispatchUpliftRequests_DispatchId_Status",
table: "DispatchUpliftRequests",
columns: new[] { "DispatchId", "Status" },
unique: true,
filter: "[Status] = 'Pending'");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "DispatchUpliftRequests");
}
}
}

View file

@ -7,10 +7,6 @@ namespace Data.SeaHavenIndustries
public class Comments : FullAuditEntity
{
public string? Commenttext { get; set; }
public string? Commenter { get; set; }
public string? RecordType { get; set; }
public string? CommentType { get; set; }
public string? ExternalCommentId { get; set; }
public string? Documents { get; set; }
public string? UserId { get; set; }
[ForeignKey(nameof(UserId))]
@ -18,8 +14,5 @@ namespace Data.SeaHavenIndustries
public int? WorkerOrderId { get; set; }
[ForeignKey(nameof(WorkerOrderId))]
public virtual WorkOrder? WorkOrder { get; set; }
public int? DispatchId { get; set; }
[ForeignKey(nameof(DispatchId))]
public virtual Dispatch? Dispatch { get; set; }
}
}

View file

@ -1,34 +0,0 @@
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? DispatchNumber { get; set; }
public string? PONumber { get; set; }
public decimal? NTEAmount { get; set; }
public string? Description { get; set; }
public string? Status { get; set; }
public DateTime? ScheduledDate { get; set; }
public DateTime? CompletedDate { get; set; }
public DateTime? DispatchedAt { get; set; }
public DateTime? AcknowledgedAt { get; set; }
public bool EmailSent { get; set; }
public string? ReplyToAddress { get; set; }
public string? AcceptToken { get; set; }
public string? VerifiedBy { get; set; }
public DateTime? VerifiedAt { get; set; }
public ICollection<Comments>? Comments { get; set; }
public ICollection<DispatchWorkOrder>? DispatchWorkOrders { get; set; }
public ICollection<DispatchChecklistItem>? ChecklistItems { get; set; }
public ICollection<DispatchSignoff>? Signoffs { get; set; }
}
}

View file

@ -1,19 +0,0 @@
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; }
}
}

View file

@ -1,16 +0,0 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Data.SeaHavenIndustries
{
public class DispatchSignoff : FullAuditEntity
{
public int DispatchId { get; set; }
[ForeignKey(nameof(DispatchId))]
public virtual Dispatch? Dispatch { get; set; }
public string? SignoffType { get; set; }
public string? Name { get; set; }
public string? Signature { get; set; }
public string? SignatureMethod { get; set; }
public DateTime? SignedAt { get; set; }
}
}

View file

@ -1,32 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Data.SeaHavenIndustries
{
public class DispatchUpliftRequest : FullAuditEntity
{
public int DispatchId { get; set; }
[ForeignKey(nameof(DispatchId))]
public virtual Dispatch? Dispatch { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal? CurrentNTE { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal RequestedNTE { get; set; }
public string? VendorReason { get; set; }
[Required]
[MaxLength(20)]
public string Status { get; set; } = "Pending";
public int RequiredTier { get; set; }
public string? RequestedByVendorName { get; set; }
public DateTime? DecidedAt { get; set; }
public string? DecidedByUserId { get; set; }
public string? DecisionNote { get; set; }
}
}

View file

@ -1,15 +0,0 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Data.SeaHavenIndustries
{
public class DispatchWorkOrder
{
public int Id { get; set; }
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; }
}
}

View file

@ -1,12 +0,0 @@
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;
}
}

View file

@ -86,9 +86,8 @@ namespace Data.SeaHavenIndustries
public Employee Employee { get; set; } = null!;
}
public class EmployeeEmail
public class EmployeeEmail : FullAuditEntity
{
public int Id { get; set; }
public int EmployeeId { get; set; }
public Employee? Employee { get; set; }
@ -100,28 +99,22 @@ namespace Data.SeaHavenIndustries
public bool? IsPrimary { get; set; }
}
public class Department
public class Department : FullAuditEntity
{
public int Id { get; set; }
[MaxLength(200)]
public string Name { get; set; } = null!;
public ICollection<Employee>? Employees { get; set; }
}
public class JobTitle
public class JobTitle : FullAuditEntity
{
public int Id { get; set; }
[MaxLength(200)]
public string Name { get; set; } = null!;
public ICollection<Employee>? Employees { get; set; }
}
public class Region
public class Region : FullAuditEntity
{
public int Id { get; set; }
[MaxLength(200)]
public string Name { get; set; } = null!;
public ICollection<Employee>? Employees { get; set; }

View file

@ -0,0 +1,49 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace Data.SeaHavenIndustries
{
/// <summary>
/// Events entity for Calendar/Events module
/// Tracks calendar events, appointments, meetings, etc.
/// </summary>
public class Events : FullAuditEntity
{
[Required]
[StringLength(100)]
public string Title { get; set; } = null!;
public string? Description { get; set; }
[StringLength(150)]
public string? Location { get; set; }
// Related Record (can link to Accounts, Contacts, Work Orders, etc.)
public int? RecordID { get; set; }
public string? RecordName { get; set; }
public string? RecordTargetType { get; set; }
// Reminder Settings
public int ReminderMinutes { get; set; } = 15; // Default: 15 minutes before
// Color for calendar display
[StringLength(20)]
public string EventColor { get; set; } = "primary"; // primary, success, info, warning, danger
// Schedule
[Required]
public DateTime StartDate { get; set; }
[StringLength(10)]
public string? StartTime { get; set; } // e.g., "08:00 AM"
[Required]
public DateTime EndDate { get; set; }
[StringLength(10)]
public string? EndTime { get; set; } // e.g., "08:30 AM"
public bool AllDay { get; set; } = false;
}
}

View file

@ -6,26 +6,12 @@ namespace Data.SeaHavenIndustries
{
public class Locations : FullAuditEntity
{
public int Id { get; set; }
public string? Title { get; set; }
public string? Name { get; set; }
public string? Latitude { get; set; }
public string? Longitude { get; set; }
// Address fields - match actual database columns
public string? Address1 { get; set; }
public string? Address2 { get; set; }
public string? City { get; set; }
public string? State { get; set; }
public string? Zip { get; set; }
// Contact fields - match actual database columns
public string? PhoneNumber { get; set; }
public string? Email { get; set; }
public string? Status { get; set; }
// Navigation Properties
public ICollection<Template>? Templates { get; set; }
public ICollection<WorkOrder>? workOrders { get; set; }
}
}

View file

@ -1,10 +0,0 @@
namespace Data.SeaHavenIndustries
{
public class TaskListTemplate : FullAuditEntity
{
public string? Name { get; set; }
public string? Description { get; set; }
public bool IsActive { get; set; } = true;
public ICollection<TaskListTemplateItem>? Items { get; set; }
}
}

View file

@ -1,13 +0,0 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Data.SeaHavenIndustries
{
public class TaskListTemplateItem : FullAuditEntity
{
public int TaskListTemplateId { get; set; }
[ForeignKey(nameof(TaskListTemplateId))]
public virtual TaskListTemplate? Template { get; set; }
public string? ItemText { get; set; }
public int SortOrder { get; set; }
}
}

View file

@ -1,17 +0,0 @@
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; }
}
}

View file

@ -1,21 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Data.SeaHavenIndustries
{
public class VendorAccessToken : FullAuditEntity
{
public int VendorId { get; set; }
[ForeignKey(nameof(VendorId))]
public virtual Vendor? Vendor { get; set; }
[Required]
[MaxLength(64)]
public string Token { get; set; } = string.Empty;
public DateTime IssuedAt { get; set; }
public DateTime ExpiresAt { get; set; }
public DateTime? RevokedAt { get; set; }
public DateTime? LastUsedAt { get; set; }
}
}

View file

@ -1,20 +0,0 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Data.SeaHavenIndustries
{
public class WorkOrderAuditLog
{
public int Id { get; set; }
public int WorkOrderId { get; set; }
[ForeignKey(nameof(WorkOrderId))]
public virtual WorkOrder? WorkOrder { get; set; }
public string? UserId { get; set; }
[ForeignKey(nameof(UserId))]
public virtual ApplicationUser? User { get; set; }
public string? FieldName { get; set; }
public string? OldValue { get; set; }
public string? NewValue { get; set; }
public string? Action { get; set; }
public DateTime CreatedAt { get; set; }
}
}

View file

@ -6,25 +6,9 @@ namespace Data.SeaHavenIndustries
{
public class WorkOrder : FullAuditEntity
{
public string? InternalWONumber { get; set; }
public string? ExternalWorkOrderId { get; set; }
public string? WorkerOrderNumber { get; set; }
public string? WorkerOrderTitle { get; set; }
public string? Description { get; set; }
public string? Customer { get; set; }
public string? SiteCode { get; set; }
public string? Building { get; set; }
public string? Severity { get; set; }
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; }
@ -56,6 +40,7 @@ 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; }
}
}

View file

@ -1,109 +0,0 @@
-- SQL Script to add missing columns to Locations table
-- Run this in SQL Server Management Studio
USE [db_a7141e_seahavenindustry]
GO
-- Check and add AccountId column
IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Locations' AND COLUMN_NAME = 'AccountId')
BEGIN
ALTER TABLE [dbo].[Locations] ADD [AccountId] int NULL;
PRINT 'Added AccountId column';
END
ELSE
PRINT 'AccountId column already exists';
-- Check and add Address column
IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Locations' AND COLUMN_NAME = 'Address')
BEGIN
ALTER TABLE [dbo].[Locations] ADD [Address] nvarchar(max) NULL;
PRINT 'Added Address column';
END
ELSE
PRINT 'Address column already exists';
-- Check and add City column
IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Locations' AND COLUMN_NAME = 'City')
BEGIN
ALTER TABLE [dbo].[Locations] ADD [City] nvarchar(max) NULL;
PRINT 'Added City column';
END
ELSE
PRINT 'City column already exists';
-- Check and add Contact column
IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Locations' AND COLUMN_NAME = 'Contact')
BEGIN
ALTER TABLE [dbo].[Locations] ADD [Contact] nvarchar(max) NULL;
PRINT 'Added Contact column';
END
ELSE
PRINT 'Contact column already exists';
-- Check and add ContactEmail column
IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Locations' AND COLUMN_NAME = 'ContactEmail')
BEGIN
ALTER TABLE [dbo].[Locations] ADD [ContactEmail] nvarchar(max) NULL;
PRINT 'Added ContactEmail column';
END
ELSE
PRINT 'ContactEmail column already exists';
-- Check and add Phone column
IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Locations' AND COLUMN_NAME = 'Phone')
BEGIN
ALTER TABLE [dbo].[Locations] ADD [Phone] nvarchar(max) NULL;
PRINT 'Added Phone column';
END
ELSE
PRINT 'Phone column already exists';
-- Check and add State column
IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Locations' AND COLUMN_NAME = 'State')
BEGIN
ALTER TABLE [dbo].[Locations] ADD [State] nvarchar(max) NULL;
PRINT 'Added State column';
END
ELSE
PRINT 'State column already exists';
-- Check and add Status column
IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Locations' AND COLUMN_NAME = 'Status')
BEGIN
ALTER TABLE [dbo].[Locations] ADD [Status] nvarchar(max) NULL;
PRINT 'Added Status column';
END
ELSE
PRINT 'Status column already exists';
-- Check and add ZipCode column
IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Locations' AND COLUMN_NAME = 'ZipCode')
BEGIN
ALTER TABLE [dbo].[Locations] ADD [ZipCode] nvarchar(max) NULL;
PRINT 'Added ZipCode column';
END
ELSE
PRINT 'ZipCode column already exists';
-- Check and add foreign key index
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_Locations_AccountId' AND object_id = OBJECT_ID('dbo.Locations'))
BEGIN
CREATE NONCLUSTERED INDEX [IX_Locations_AccountId] ON [dbo].[Locations] ([AccountId] ASC);
PRINT 'Added IX_Locations_AccountId index';
END
ELSE
PRINT 'IX_Locations_AccountId index already exists';
-- Check and add foreign key constraint if Accounts table exists
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'Accounts')
AND NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE name = 'FK_Locations_Accounts_AccountId')
BEGIN
ALTER TABLE [dbo].[Locations] WITH CHECK ADD CONSTRAINT [FK_Locations_Accounts_AccountId]
FOREIGN KEY([AccountId]) REFERENCES [dbo].[Accounts] ([Id]);
PRINT 'Added FK_Locations_Accounts_AccountId foreign key';
END
ELSE
PRINT 'FK_Locations_Accounts_AccountId foreign key already exists or Accounts table does not exist';
PRINT 'Schema fix completed!';
GO

Some files were not shown because too many files have changed in this diff Show more