This commit is contained in:
npalOmega 2026-04-28 18:55:14 -05:00
parent 64adcae4ed
commit ac76b201de
56 changed files with 2602 additions and 45 deletions

View file

@ -20,6 +20,8 @@
<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

@ -327,10 +327,10 @@ namespace Api.SeaHavenIndustries.Controllers
{
dispatch.WorkOrder.Locations.Id,
dispatch.WorkOrder.Locations.Name,
dispatch.WorkOrder.Locations.Address,
dispatch.WorkOrder.Locations.Address1,
dispatch.WorkOrder.Locations.City,
dispatch.WorkOrder.Locations.State,
dispatch.WorkOrder.Locations.ZipCode
dispatch.WorkOrder.Locations.Zip
},
WorkOrders = linkedWOs,
Checklist = checklist,

View file

@ -30,7 +30,7 @@ namespace Api.SeaHavenIndustries.Controllers
query = query.Where(l =>
(l.Name ?? "").ToLower().Contains(s) ||
(l.Title ?? "").ToLower().Contains(s) ||
(l.Address ?? "").ToLower().Contains(s) ||
(l.Address1 ?? "").ToLower().Contains(s) ||
(l.City ?? "").ToLower().Contains(s));
}
@ -45,15 +45,17 @@ namespace Api.SeaHavenIndustries.Controllers
l.Id,
l.Name,
l.Title,
l.Address,
Address = l.Address1, // Database has Address1, not Address
l.Address1,
l.Address2,
l.City,
l.State,
l.ZipCode,
l.Phone,
l.Contact,
l.ContactEmail,
l.Status,
l.AccountId
ZipCode = l.Zip, // Database has Zip, not ZipCode
Phone = l.PhoneNumber, // Database has PhoneNumber, not Phone
Contact = (string?)null, // Contact column doesn't exist in database
ContactEmail = l.Email, // Database has Email, not ContactEmail
l.Status
// AccountId doesn't exist in database
})
.ToList();
@ -79,15 +81,17 @@ namespace Api.SeaHavenIndustries.Controllers
l.Id,
l.Name,
l.Title,
l.Address,
Address = l.Address1, // Database has Address1
l.Address1,
l.Address2,
l.City,
l.State,
l.ZipCode,
l.Phone,
l.Contact,
l.ContactEmail,
l.Status,
l.AccountId
ZipCode = l.Zip, // Database has Zip
Phone = l.PhoneNumber, // Database has PhoneNumber
Contact = (string?)null, // Contact doesn't exist
ContactEmail = l.Email, // Database has Email
l.Status
// AccountId doesn't exist
})
.FirstOrDefaultAsync();
@ -106,15 +110,15 @@ namespace Api.SeaHavenIndustries.Controllers
{
Name = model.Name,
Title = model.Title,
Address = model.Address,
Address1 = model.Address, // Map to Address1
City = model.City,
State = model.State,
ZipCode = model.ZipCode,
Phone = model.Phone,
Contact = model.Contact,
ContactEmail = model.ContactEmail,
Zip = model.ZipCode, // Map to Zip
PhoneNumber = model.Phone, // Map to PhoneNumber
Email = model.ContactEmail, // Map to Email
Status = model.Status,
AccountId = model.GetAccountId()
CreatedDate = DateTime.Now
// Contact and AccountId don't exist in database
};
_db.Locations.Add(location);
@ -138,15 +142,15 @@ namespace Api.SeaHavenIndustries.Controllers
exist.Name = model.Name;
exist.Title = model.Title;
exist.Address = model.Address;
exist.Address1 = model.Address; // Map to Address1
exist.City = model.City;
exist.State = model.State;
exist.ZipCode = model.ZipCode;
exist.Phone = model.Phone;
exist.Contact = model.Contact;
exist.ContactEmail = model.ContactEmail;
exist.Zip = model.ZipCode; // Map to Zip
exist.PhoneNumber = model.Phone; // Map to PhoneNumber
exist.Email = model.ContactEmail; // Map to Email
exist.Status = model.Status;
exist.AccountId = model.GetAccountId();
exist.LastModificationTime = DateTime.Now;
// Contact and AccountId don't exist in database
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Location Updated Successfully", Status = "200" });

View file

@ -375,7 +375,7 @@ namespace Api.SeaHavenIndustries.Controllers
{
Name = matchCode,
Title = building,
Address = address,
Address1 = address,
Status = "Active"
};

View file

@ -232,10 +232,10 @@ namespace Api.SeaHavenIndustries.Controllers
Location = dispatch.WorkOrder?.Locations == null ? null : new
{
dispatch.WorkOrder.Locations.Name,
dispatch.WorkOrder.Locations.Address,
dispatch.WorkOrder.Locations.Address1,
dispatch.WorkOrder.Locations.City,
dispatch.WorkOrder.Locations.State,
dispatch.WorkOrder.Locations.ZipCode
dispatch.WorkOrder.Locations.Zip
},
Checklist = checklist,
Signoffs = signoffs,

View file

@ -644,7 +644,7 @@ namespace Api.SeaHavenIndustries.Controllers
assignee = s.AssignToUser != null ? s.AssignToUser.FirstName : "",
location = s.Locations != null ? s.Locations.Name : "",
locationId = s.LocationId,
locationZip = s.Locations != null ? (s.Locations.ZipCode ?? ExtractZip(s.Locations.Address)) : "",
locationZip = s.Locations != null ? (s.Locations.Zip ?? ExtractZip(s.Locations.Address1)) : "",
priority = s.Priority,
s.Problem,
s.Trade,
@ -1123,7 +1123,7 @@ namespace Api.SeaHavenIndustries.Controllers
$"<td style='padding:6px 8px;border:1px solid #ddd;'>{wo.DueDate?.ToString("yyyy-MM-dd") ?? "N/A"}</td></tr>"));
var locationName = System.Net.WebUtility.HtmlEncode(primaryWO.Locations?.Name ?? "");
var locationAddr = System.Net.WebUtility.HtmlEncode(primaryWO.Locations?.Address ?? "");
var locationAddr = System.Net.WebUtility.HtmlEncode(primaryWO.Locations?.Address1 ?? "");
var subject = $"[{dispatchNumber}] {(workOrders.Count > 1 ? $"{workOrders.Count} Work Orders" : primaryWO.WorkerOrderTitle)} at {primaryWO.Locations?.Name ?? ""}";
var descriptionHtml = System.Net.WebUtility.HtmlEncode(model.Description ?? primaryWO.Description ?? "");
var senderNameHtml = System.Net.WebUtility.HtmlEncode(senderName);

View file

@ -31,6 +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 =>
@ -41,6 +42,33 @@ builder.Services.AddSingleton<Api.SeaHavenIndustries.Helper.ZipCodeDistance>(sp
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) - Only register services that exist
builder.Services.AddScoped<SeaHaven.Services.Interfaces.IContactService, SeaHaven.Services.Implementation.ContactService>();
builder.Services.AddScoped<SeaHaven.Services.Interfaces.IWorkOrderService, SeaHaven.Services.Implementation.WorkOrderService>();
// TODO: Add other business services as they are implemented
// - LocationService, EmployeeService, AccountService, VendorService, DispatchService
// - FollowUpService, PMScheduleService, AssetService, CategoryService
// ========================================================================
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;

View file

@ -6,21 +6,25 @@ namespace Data.SeaHavenIndustries
{
public class Locations : FullAuditEntity
{
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? Latitude { get; set; }
public string? Longitude { get; set; }
public int? AccountId { get; set; }
[ForeignKey("AccountId")]
public Accounts? Account { 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; }
}

109
FIX_LOCATIONS_SCHEMA.sql Normal file
View file

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

View file

@ -0,0 +1,91 @@
using Data.SeaHavenIndustries;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Interfaces;
namespace SeaHaven.DataServices.Implementation
{
public class AccountDataService : IAccountDataService
{
private readonly ApplicationDbContext _context;
public AccountDataService(ApplicationDbContext context)
{
_context = context;
}
public async Task<Accounts?> GetByIdAsync(int id)
{
return await _context.Accounts.FindAsync(id);
}
public async Task<Accounts?> GetByIdWithDetailsAsync(int id)
{
return await _context.Accounts
.Include(a => a.Contacts)
.Include(a => a.Addresses)
.FirstOrDefaultAsync(a => a.Id == id);
}
public async Task<IEnumerable<Accounts>> GetAllAsync()
{
return await _context.Accounts.ToListAsync();
}
public async Task<(IEnumerable<Accounts> Items, int TotalCount)> GetPagedAsync(
int page,
int pageSize,
string? search = null)
{
var query = _context.Accounts.AsQueryable();
if (!string.IsNullOrWhiteSpace(search))
{
query = query.Where(a => a.Name!.Contains(search));
}
var totalCount = await query.CountAsync();
var items = await query
.OrderBy(a => a.Name)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return (items, totalCount);
}
public async Task<Accounts> AddAsync(Accounts account)
{
account.CreatedDate = DateTime.Now;
await _context.Accounts.AddAsync(account);
await _context.SaveChangesAsync();
return account;
}
public async Task UpdateAsync(Accounts account)
{
account.LastModificationTime = DateTime.Now;
_context.Accounts.Update(account);
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(int id)
{
var entity = await GetByIdAsync(id);
if (entity != null)
{
_context.Accounts.Remove(entity);
await _context.SaveChangesAsync();
}
}
public async Task<bool> ExistsAsync(int id)
{
return await _context.Accounts.AnyAsync(a => a.Id == id);
}
public async Task<int> CountAsync()
{
return await _context.Accounts.CountAsync();
}
}
}

View file

@ -0,0 +1,91 @@
using Data.SeaHavenIndustries;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Interfaces;
namespace SeaHaven.DataServices.Implementation
{
public class AssetDataService : IAssetDataService
{
private readonly ApplicationDbContext _context;
public AssetDataService(ApplicationDbContext context)
{
_context = context;
}
public async Task<Assets?> GetByIdAsync(int id)
{
return await _context.Assets.FindAsync(id);
}
public async Task<Assets?> GetByIdWithDetailsAsync(int id)
{
return await _context.Assets
.FirstOrDefaultAsync(a => a.Id == id);
}
public async Task<IEnumerable<Assets>> GetAllAsync()
{
return await _context.Assets.ToListAsync();
}
public async Task<IEnumerable<Assets>> GetByLocationIdAsync(int locationId)
{
return await _context.Assets
.Where(a => a.LocationId == locationId)
.ToListAsync();
}
public async Task<(IEnumerable<Assets> Items, int TotalCount)> GetPagedAsync(
int page,
int pageSize,
string? search = null)
{
var query = _context.Assets.AsQueryable();
var totalCount = await query.CountAsync();
var items = await query
.OrderBy(a => a.Id)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return (items, totalCount);
}
public async Task<Assets> AddAsync(Assets asset)
{
asset.CreatedDate = DateTime.Now;
await _context.Assets.AddAsync(asset);
await _context.SaveChangesAsync();
return asset;
}
public async Task UpdateAsync(Assets asset)
{
asset.LastModificationTime = DateTime.Now;
_context.Assets.Update(asset);
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(int id)
{
var entity = await GetByIdAsync(id);
if (entity != null)
{
_context.Assets.Remove(entity);
await _context.SaveChangesAsync();
}
}
public async Task<bool> ExistsAsync(int id)
{
return await _context.Assets.AnyAsync(a => a.Id == id);
}
public async Task<int> CountAsync()
{
return await _context.Assets.CountAsync();
}
}
}

View file

@ -0,0 +1,56 @@
using Data.SeaHavenIndustries;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Interfaces;
namespace SeaHaven.DataServices.Implementation
{
public class CategoryDataService : ICategoryDataService
{
private readonly ApplicationDbContext _context;
public CategoryDataService(ApplicationDbContext context)
{
_context = context;
}
public async Task<Category?> GetByIdAsync(int id)
{
return await _context.Categories.FindAsync(id);
}
public async Task<IEnumerable<Category>> GetAllAsync()
{
return await _context.Categories.ToListAsync();
}
public async Task<Category> AddAsync(Category category)
{
category.CreatedDate = DateTime.Now;
await _context.Categories.AddAsync(category);
await _context.SaveChangesAsync();
return category;
}
public async Task UpdateAsync(Category category)
{
category.LastModificationTime = DateTime.Now;
_context.Categories.Update(category);
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(int id)
{
var entity = await GetByIdAsync(id);
if (entity != null)
{
_context.Categories.Remove(entity);
await _context.SaveChangesAsync();
}
}
public async Task<bool> ExistsAsync(int id)
{
return await _context.Categories.AnyAsync(c => c.Id == id);
}
}
}

View file

@ -0,0 +1,156 @@
using Data.SeaHavenIndustries;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Interfaces;
namespace SeaHaven.DataServices.Implementation
{
public class ContactDataService : IContactDataService
{
private readonly ApplicationDbContext _context;
public ContactDataService(ApplicationDbContext context)
{
_context = context;
}
public async Task<Contacts?> GetByIdAsync(int id)
{
return await _context.Contacts.FindAsync(id);
}
public async Task<Contacts?> GetByIdWithDetailsAsync(int id)
{
return await _context.Contacts
.Include(c => c.Location)
.Include(c => c.Account)
.FirstOrDefaultAsync(c => c.Id == id);
}
public async Task<IEnumerable<Contacts>> GetAllAsync()
{
return await _context.Contacts.ToListAsync();
}
public async Task<IEnumerable<Contacts>> GetByLocationIdAsync(int locationId)
{
return await _context.Contacts
.Where(c => c.LocationId == locationId)
.ToListAsync();
}
public async Task<IEnumerable<Contacts>> GetByAccountIdAsync(int accountId)
{
return await _context.Contacts
.Where(c => c.AccountId == accountId)
.ToListAsync();
}
public async Task<IEnumerable<Contacts>> GetByTypeAsync(string type)
{
return await _context.Contacts
.Where(c => c.ContactType == type)
.ToListAsync();
}
public async Task<Contacts?> GetByEmailAsync(string email)
{
return await _context.Contacts
.FirstOrDefaultAsync(c => c.Email == email);
}
public async Task<Contacts?> GetByPhoneAsync(string phone)
{
return await _context.Contacts
.FirstOrDefaultAsync(c => c.PhoneNumber == phone);
}
public async Task<(IEnumerable<Contacts> Items, int TotalCount)> GetPagedAsync(
int page,
int pageSize,
string? search = null,
string? type = null)
{
var query = _context.Contacts.AsQueryable();
if (!string.IsNullOrWhiteSpace(search))
{
query = query.Where(c =>
(c.FirstName + " " + c.LastName).Contains(search) ||
c.Email!.Contains(search));
}
if (!string.IsNullOrWhiteSpace(type))
{
query = query.Where(c => c.ContactType == type);
}
var totalCount = await query.CountAsync();
var items = await query
.OrderBy(c => c.LastName)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return (items, totalCount);
}
public async Task<Contacts> AddAsync(Contacts contact)
{
contact.CreatedDate = DateTime.Now;
await _context.Contacts.AddAsync(contact);
await _context.SaveChangesAsync();
return contact;
}
public async Task UpdateAsync(Contacts contact)
{
contact.LastModificationTime = DateTime.Now;
_context.Contacts.Update(contact);
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(int id)
{
var entity = await GetByIdAsync(id);
if (entity != null)
{
_context.Contacts.Remove(entity);
await _context.SaveChangesAsync();
}
}
public async Task<bool> ExistsAsync(int id)
{
return await _context.Contacts.AnyAsync(c => c.Id == id);
}
public async Task<bool> EmailExistsAsync(string email, int? excludeId = null)
{
return await _context.Contacts.AnyAsync(c =>
c.Email == email &&
(excludeId == null || c.Id != excludeId));
}
public async Task<bool> PhoneExistsAsync(string phone, int? excludeId = null)
{
return await _context.Contacts.AnyAsync(c =>
c.PhoneNumber == phone &&
(excludeId == null || c.Id != excludeId));
}
public async Task<int> CountAsync()
{
return await _context.Contacts.CountAsync();
}
public async Task<int> CountByAccountIdAsync(int accountId)
{
return await _context.Contacts.CountAsync(c => c.AccountId == accountId);
}
public async Task<int> CountByTypeAsync(string type)
{
return await _context.Contacts.CountAsync(c => c.ContactType == type);
}
}
}

View file

@ -0,0 +1,115 @@
using Data.SeaHavenIndustries;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Interfaces;
namespace SeaHaven.DataServices.Implementation
{
public class DispatchDataService : IDispatchDataService
{
private readonly ApplicationDbContext _context;
public DispatchDataService(ApplicationDbContext context)
{
_context = context;
}
public async Task<Dispatch?> GetByIdAsync(int id)
{
return await _context.Dispatches.FindAsync(id);
}
public async Task<Dispatch?> GetByIdWithDetailsAsync(int id)
{
return await _context.Dispatches
.Include(d => d.Vendor)
.FirstOrDefaultAsync(d => d.Id == id);
}
public async Task<IEnumerable<Dispatch>> GetAllAsync()
{
return await _context.Dispatches.ToListAsync();
}
public async Task<IEnumerable<Dispatch>> GetByVendorIdAsync(int vendorId)
{
return await _context.Dispatches
.Where(d => d.VendorId == vendorId)
.ToListAsync();
}
public async Task<IEnumerable<Dispatch>> GetByStatusAsync(string status)
{
return await _context.Dispatches
.Where(d => d.Status == status)
.ToListAsync();
}
public async Task<(IEnumerable<Dispatch> Items, int TotalCount)> GetPagedAsync(
int page,
int pageSize,
string? search = null,
string? status = null)
{
var query = _context.Dispatches.AsQueryable();
if (!string.IsNullOrWhiteSpace(search))
{
query = query.Where(d => d.DispatchNumber!.Contains(search));
}
if (!string.IsNullOrWhiteSpace(status))
{
query = query.Where(d => d.Status == status);
}
var totalCount = await query.CountAsync();
var items = await query
.OrderByDescending(d => d.CreatedDate)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return (items, totalCount);
}
public async Task<Dispatch> AddAsync(Dispatch dispatch)
{
dispatch.CreatedDate = DateTime.Now;
await _context.Dispatches.AddAsync(dispatch);
await _context.SaveChangesAsync();
return dispatch;
}
public async Task UpdateAsync(Dispatch dispatch)
{
dispatch.LastModificationTime = DateTime.Now;
_context.Dispatches.Update(dispatch);
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(int id)
{
var entity = await GetByIdAsync(id);
if (entity != null)
{
_context.Dispatches.Remove(entity);
await _context.SaveChangesAsync();
}
}
public async Task<bool> ExistsAsync(int id)
{
return await _context.Dispatches.AnyAsync(d => d.Id == id);
}
public async Task<string> GenerateNextDispatchNumberAsync()
{
return await Task.FromResult($"DISP-{DateTime.Now:yyyyMMdd}-{new Random().Next(1000, 9999)}");
}
public async Task<int> CountAsync()
{
return await _context.Dispatches.CountAsync();
}
}
}

View file

@ -0,0 +1,92 @@
using Data.SeaHavenIndustries;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Interfaces;
namespace SeaHaven.DataServices.Implementation
{
public class EmployeeDataService : IEmployeeDataService
{
private readonly ApplicationDbContext _context;
public EmployeeDataService(ApplicationDbContext context)
{
_context = context;
}
public async Task<Employee?> GetByIdAsync(int id)
{
return await _context.Employees.FindAsync(id);
}
public async Task<Employee?> GetByIdWithDetailsAsync(int id)
{
return await _context.Employees
.Include(e => e.Department)
.Include(e => e.JobTitle)
.Include(e => e.Emails)
.FirstOrDefaultAsync(e => e.Id == id);
}
public async Task<IEnumerable<Employee>> GetAllAsync()
{
return await _context.Employees.ToListAsync();
}
public async Task<(IEnumerable<Employee> Items, int TotalCount)> GetPagedAsync(
int page,
int pageSize,
string? search = null)
{
var query = _context.Employees.AsQueryable();
if (!string.IsNullOrWhiteSpace(search))
{
query = query.Where(e => (e.FirstName + " " + e.LastName).Contains(search));
}
var totalCount = await query.CountAsync();
var items = await query
.OrderBy(e => e.LastName)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return (items, totalCount);
}
public async Task<Employee> AddAsync(Employee employee)
{
employee.CreatedDate = DateTime.Now;
await _context.Employees.AddAsync(employee);
await _context.SaveChangesAsync();
return employee;
}
public async Task UpdateAsync(Employee employee)
{
employee.LastModificationTime = DateTime.Now;
_context.Employees.Update(employee);
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(int id)
{
var entity = await GetByIdAsync(id);
if (entity != null)
{
_context.Employees.Remove(entity);
await _context.SaveChangesAsync();
}
}
public async Task<bool> ExistsAsync(int id)
{
return await _context.Employees.AnyAsync(e => e.Id == id);
}
public async Task<int> CountAsync()
{
return await _context.Employees.CountAsync();
}
}
}

View file

@ -0,0 +1,104 @@
using Data.SeaHavenIndustries;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Interfaces;
namespace SeaHaven.DataServices.Implementation
{
public class FollowUpDataService : IFollowUpDataService
{
private readonly ApplicationDbContext _context;
public FollowUpDataService(ApplicationDbContext context)
{
_context = context;
}
public async Task<FollowUps?> GetByIdAsync(int id)
{
return await _context.FollowUps.FindAsync(id);
}
public async Task<FollowUps?> GetByIdWithDetailsAsync(int id)
{
return await _context.FollowUps
.FirstOrDefaultAsync(f => f.Id == id);
}
public async Task<IEnumerable<FollowUps>> GetAllAsync()
{
return await _context.FollowUps.ToListAsync();
}
public async Task<IEnumerable<FollowUps>> GetByWorkOrderIdAsync(int workOrderId)
{
return await _context.FollowUps
.Where(f => f.WorkOrderId == workOrderId)
.ToListAsync();
}
public async Task<IEnumerable<FollowUps>> GetByUserIdAsync(string userId)
{
return await _context.FollowUps
.Where(f => f.createdby == userId)
.ToListAsync();
}
public async Task<(IEnumerable<FollowUps> Items, int TotalCount)> GetPagedAsync(
int page,
int pageSize,
string? search = null,
string? status = null)
{
var query = _context.FollowUps.AsQueryable();
if (!string.IsNullOrWhiteSpace(status))
{
query = query.Where(f => f.Status == status);
}
var totalCount = await query.CountAsync();
var items = await query
.OrderByDescending(f => f.CreatedDate)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return (items, totalCount);
}
public async Task<FollowUps> AddAsync(FollowUps followUp)
{
followUp.CreatedDate = DateTime.Now;
await _context.FollowUps.AddAsync(followUp);
await _context.SaveChangesAsync();
return followUp;
}
public async Task UpdateAsync(FollowUps followUp)
{
followUp.LastModificationTime = DateTime.Now;
_context.FollowUps.Update(followUp);
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(int id)
{
var entity = await GetByIdAsync(id);
if (entity != null)
{
_context.FollowUps.Remove(entity);
await _context.SaveChangesAsync();
}
}
public async Task<bool> ExistsAsync(int id)
{
return await _context.FollowUps.AnyAsync(f => f.Id == id);
}
public async Task<int> CountAsync()
{
return await _context.FollowUps.CountAsync();
}
}
}

View file

@ -0,0 +1,98 @@
using Data.SeaHavenIndustries;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Interfaces;
namespace SeaHaven.DataServices.Implementation
{
public class LocationDataService : ILocationDataService
{
private readonly ApplicationDbContext _context;
public LocationDataService(ApplicationDbContext context)
{
_context = context;
}
public async Task<Locations?> GetByIdAsync(int id)
{
return await _context.Locations.FindAsync(id);
}
public async Task<Locations?> GetByIdWithDetailsAsync(int id)
{
return await _context.Locations
.FirstOrDefaultAsync(l => l.Id == id);
}
public async Task<IEnumerable<Locations>> GetAllAsync()
{
return await _context.Locations.ToListAsync();
}
public async Task<IEnumerable<Locations>> GetByAccountIdAsync(int accountId)
{
// AccountId doesn't exist in database - return all for now
// TODO: Add AccountId column to database if needed
return await _context.Locations.ToListAsync();
}
public async Task<(IEnumerable<Locations> Items, int TotalCount)> GetPagedAsync(
int page,
int pageSize,
string? search = null)
{
var query = _context.Locations.AsQueryable();
if (!string.IsNullOrWhiteSpace(search))
{
query = query.Where(l =>
l.Name!.Contains(search) ||
l.City!.Contains(search));
}
var totalCount = await query.CountAsync();
var items = await query
.OrderBy(l => l.Name)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return (items, totalCount);
}
public async Task<Locations> AddAsync(Locations location)
{
location.CreatedDate = DateTime.Now;
await _context.Locations.AddAsync(location);
await _context.SaveChangesAsync();
return location;
}
public async Task UpdateAsync(Locations location)
{
location.LastModificationTime = DateTime.Now;
_context.Locations.Update(location);
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(int id)
{
var entity = await GetByIdAsync(id);
if (entity != null)
{
_context.Locations.Remove(entity);
await _context.SaveChangesAsync();
}
}
public async Task<bool> ExistsAsync(int id)
{
return await _context.Locations.AnyAsync(l => l.Id == id);
}
public async Task<int> CountAsync()
{
return await _context.Locations.CountAsync();
}
}
}

View file

@ -0,0 +1,91 @@
using Data.SeaHavenIndustries;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Interfaces;
namespace SeaHaven.DataServices.Implementation
{
public class PMScheduleDataService : IPMScheduleDataService
{
private readonly ApplicationDbContext _context;
public PMScheduleDataService(ApplicationDbContext context)
{
_context = context;
}
public async Task<PMSchedules?> GetByIdAsync(int id)
{
return await _context.PMSchedules.FindAsync(id);
}
public async Task<PMSchedules?> GetByIdWithDetailsAsync(int id)
{
return await _context.PMSchedules
.FirstOrDefaultAsync(p => p.Id == id);
}
public async Task<IEnumerable<PMSchedules>> GetAllAsync()
{
return await _context.PMSchedules.ToListAsync();
}
public async Task<IEnumerable<PMSchedules>> GetByLocationIdAsync(int locationId)
{
return await _context.PMSchedules
.Where(p => p.LocationId == locationId)
.ToListAsync();
}
public async Task<(IEnumerable<PMSchedules> Items, int TotalCount)> GetPagedAsync(
int page,
int pageSize,
string? search = null)
{
var query = _context.PMSchedules.AsQueryable();
var totalCount = await query.CountAsync();
var items = await query
.OrderBy(p => p.Id)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return (items, totalCount);
}
public async Task<PMSchedules> AddAsync(PMSchedules schedule)
{
schedule.CreatedDate = DateTime.Now;
await _context.PMSchedules.AddAsync(schedule);
await _context.SaveChangesAsync();
return schedule;
}
public async Task UpdateAsync(PMSchedules schedule)
{
schedule.LastModificationTime = DateTime.Now;
_context.PMSchedules.Update(schedule);
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(int id)
{
var entity = await GetByIdAsync(id);
if (entity != null)
{
_context.PMSchedules.Remove(entity);
await _context.SaveChangesAsync();
}
}
public async Task<bool> ExistsAsync(int id)
{
return await _context.PMSchedules.AnyAsync(p => p.Id == id);
}
public async Task<int> CountAsync()
{
return await _context.PMSchedules.CountAsync();
}
}
}

View file

@ -0,0 +1,94 @@
using Data.SeaHavenIndustries;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Interfaces;
namespace SeaHaven.DataServices.Implementation
{
public class VendorDataService : IVendorDataService
{
private readonly ApplicationDbContext _context;
public VendorDataService(ApplicationDbContext context)
{
_context = context;
}
public async Task<Vendor?> GetByIdAsync(int id)
{
return await _context.Vendors.FindAsync(id);
}
public async Task<Vendor?> GetByIdWithDetailsAsync(int id)
{
return await _context.Vendors
.FirstOrDefaultAsync(v => v.Id == id);
}
public async Task<IEnumerable<Vendor>> GetAllAsync()
{
return await _context.Vendors.ToListAsync();
}
public async Task<(IEnumerable<Vendor> Items, int TotalCount)> GetPagedAsync(
int page,
int pageSize,
string? search = null)
{
var query = _context.Vendors.AsQueryable();
var totalCount = await query.CountAsync();
var items = await query
.OrderBy(v => v.Id)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return (items, totalCount);
}
public async Task<Vendor> AddAsync(Vendor vendor)
{
await _context.Vendors.AddAsync(vendor);
await _context.SaveChangesAsync();
return vendor;
}
public async Task UpdateAsync(Vendor vendor)
{
_context.Vendors.Update(vendor);
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(int id)
{
var entity = await GetByIdAsync(id);
if (entity != null)
{
_context.Vendors.Remove(entity);
await _context.SaveChangesAsync();
}
}
public async Task<bool> ExistsAsync(int id)
{
return await _context.Vendors.AnyAsync(v => v.Id == id);
}
public async Task<VendorAccessToken?> GetActiveTokenAsync(int vendorId)
{
return await _context.VendorAccessTokens
.FirstOrDefaultAsync(t => t.VendorId == vendorId);
}
public async Task<VendorAccessToken?> GetTokenByStringAsync(string token)
{
return await _context.VendorAccessTokens
.FirstOrDefaultAsync(t => t.Token == token);
}
public async Task<int> CountAsync()
{
return await _context.Vendors.CountAsync();
}
}
}

View file

@ -0,0 +1,189 @@
using Data.SeaHavenIndustries;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Interfaces;
namespace SeaHaven.DataServices.Implementation
{
public class WorkOrderDataService : IWorkOrderDataService
{
private readonly ApplicationDbContext _context;
public WorkOrderDataService(ApplicationDbContext context)
{
_context = context;
}
public async Task<WorkOrder?> GetByIdAsync(int id)
{
return await _context.workOrders.FindAsync(id);
}
public async Task<WorkOrder?> GetByIdWithDetailsAsync(int id)
{
return await _context.workOrders
.Include(w => w.Locations)
.Include(w => w.AssignToUser)
.FirstOrDefaultAsync(w => w.Id == id);
}
public async Task<WorkOrder?> GetByInternalNumberAsync(string internalNumber)
{
return await _context.workOrders
.FirstOrDefaultAsync(w => w.InternalWONumber == internalNumber);
}
public async Task<IEnumerable<WorkOrder>> GetAllAsync()
{
return await _context.workOrders.ToListAsync();
}
public async Task<IEnumerable<WorkOrder>> GetByLocationIdAsync(int locationId)
{
return await _context.workOrders
.Where(w => w.LocationId == locationId)
.ToListAsync();
}
public async Task<IEnumerable<WorkOrder>> GetByAssigneeAsync(string assignee)
{
return await _context.workOrders
.Where(w => w.AssignTo == assignee)
.ToListAsync();
}
public async Task<IEnumerable<WorkOrder>> GetByStatusAsync(string status)
{
return await _context.workOrders
.Where(w => w.Status == status)
.ToListAsync();
}
public async Task<IEnumerable<WorkOrder>> GetByPriorityAsync(string priority)
{
return await _context.workOrders
.Where(w => w.Priority == priority)
.ToListAsync();
}
public async Task<(IEnumerable<WorkOrder> Items, int TotalCount)> GetPagedAsync(
int page,
int pageSize,
string? search = null,
string? status = null,
string? priority = null)
{
var query = _context.workOrders.AsQueryable();
if (!string.IsNullOrWhiteSpace(search))
{
query = query.Where(w =>
w.WorkerOrderNumber!.Contains(search) ||
w.WorkerOrderTitle!.Contains(search));
}
if (!string.IsNullOrWhiteSpace(status))
{
query = query.Where(w => w.Status == status);
}
if (!string.IsNullOrWhiteSpace(priority))
{
query = query.Where(w => w.Priority == priority);
}
var totalCount = await query.CountAsync();
var items = await query
.OrderByDescending(w => w.CreatedDate)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return (items, totalCount);
}
public async Task<WorkOrder> AddAsync(WorkOrder workOrder)
{
workOrder.CreatedDate = DateTime.Now;
await _context.workOrders.AddAsync(workOrder);
await _context.SaveChangesAsync();
return workOrder;
}
public async Task UpdateAsync(WorkOrder workOrder)
{
workOrder.LastModificationTime = DateTime.Now;
_context.workOrders.Update(workOrder);
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(int id)
{
var entity = await GetByIdAsync(id);
if (entity != null)
{
_context.workOrders.Remove(entity);
await _context.SaveChangesAsync();
}
}
public async Task<IEnumerable<WorkOrderContacts>> GetWorkOrderContactsAsync(int workOrderId)
{
return await _context.WorkOrderContacts
.Where(wc => wc.WorkorderId == workOrderId)
.ToListAsync();
}
public async Task<IEnumerable<WorkOrderCategories>> GetWorkOrderCategoriesAsync(int workOrderId)
{
return await _context.workOrderCategories
.Where(wc => wc.WorkorderId == workOrderId)
.ToListAsync();
}
public async Task<IEnumerable<WorkOrderAttachments>> GetWorkOrderAttachmentsAsync(int workOrderId)
{
return await _context.workOrderAttachments
.Where(wa => wa.WorkorderId == workOrderId)
.ToListAsync();
}
public async Task<IEnumerable<Comments>> GetWorkOrderCommentsAsync(int workOrderId)
{
return await _context.Comments
.Where(c => c.WorkerOrderId == workOrderId)
.ToListAsync();
}
public async Task<bool> ExistsAsync(int id)
{
return await _context.workOrders.AnyAsync(w => w.Id == id);
}
public async Task<bool> InternalNumberExistsAsync(string internalNumber, int? excludeId = null)
{
return await _context.workOrders.AnyAsync(w =>
w.InternalWONumber == internalNumber &&
(excludeId == null || w.Id != excludeId));
}
public async Task<string> GenerateNextInternalWONumberAsync()
{
return await Task.FromResult($"WO-{DateTime.Now:yyyyMMdd}-{new Random().Next(1000, 9999)}");
}
public async Task<int> CountAsync()
{
return await _context.workOrders.CountAsync();
}
public async Task<int> CountByStatusAsync(string status)
{
return await _context.workOrders.CountAsync(w => w.Status == status);
}
public async Task<int> CountByLocationAsync(int locationId)
{
return await _context.workOrders.CountAsync(w => w.LocationId == locationId);
}
}
}

View file

@ -0,0 +1,17 @@
using Data.SeaHavenIndustries;
namespace SeaHaven.DataServices.Interfaces
{
public interface IAccountDataService
{
Task<Accounts?> GetByIdAsync(int id);
Task<Accounts?> GetByIdWithDetailsAsync(int id);
Task<IEnumerable<Accounts>> GetAllAsync();
Task<(IEnumerable<Accounts> Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null);
Task<Accounts> AddAsync(Accounts account);
Task UpdateAsync(Accounts account);
Task DeleteAsync(int id);
Task<bool> ExistsAsync(int id);
Task<int> CountAsync();
}
}

View file

@ -0,0 +1,18 @@
using Data.SeaHavenIndustries;
namespace SeaHaven.DataServices.Interfaces
{
public interface IAssetDataService
{
Task<Assets?> GetByIdAsync(int id);
Task<Assets?> GetByIdWithDetailsAsync(int id);
Task<IEnumerable<Assets>> GetAllAsync();
Task<IEnumerable<Assets>> GetByLocationIdAsync(int locationId);
Task<(IEnumerable<Assets> Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null);
Task<Assets> AddAsync(Assets asset);
Task UpdateAsync(Assets asset);
Task DeleteAsync(int id);
Task<bool> ExistsAsync(int id);
Task<int> CountAsync();
}
}

View file

@ -0,0 +1,14 @@
using Data.SeaHavenIndustries;
namespace SeaHaven.DataServices.Interfaces
{
public interface ICategoryDataService
{
Task<Category?> GetByIdAsync(int id);
Task<IEnumerable<Category>> GetAllAsync();
Task<Category> AddAsync(Category category);
Task UpdateAsync(Category category);
Task DeleteAsync(int id);
Task<bool> ExistsAsync(int id);
}
}

View file

@ -0,0 +1,42 @@
using Data.SeaHavenIndustries;
namespace SeaHaven.DataServices.Interfaces
{
/// <summary>
/// Data service interface for Contact entity
/// Handles all data access operations for Contacts
/// </summary>
public interface IContactDataService
{
// Query operations
Task<Contacts?> GetByIdAsync(int id);
Task<Contacts?> GetByIdWithDetailsAsync(int id);
Task<IEnumerable<Contacts>> GetAllAsync();
Task<IEnumerable<Contacts>> GetByAccountIdAsync(int accountId);
Task<IEnumerable<Contacts>> GetByTypeAsync(string type);
Task<Contacts?> GetByEmailAsync(string email);
Task<Contacts?> GetByPhoneAsync(string phone);
// Paged results
Task<(IEnumerable<Contacts> Items, int TotalCount)> GetPagedAsync(
int page,
int pageSize,
string? search = null,
string? type = null);
// Command operations
Task<Contacts> AddAsync(Contacts contact);
Task UpdateAsync(Contacts contact);
Task DeleteAsync(int id);
// Validation operations
Task<bool> ExistsAsync(int id);
Task<bool> EmailExistsAsync(string email, int? excludeId = null);
Task<bool> PhoneExistsAsync(string phone, int? excludeId = null);
// Count operations
Task<int> CountAsync();
Task<int> CountByAccountIdAsync(int accountId);
Task<int> CountByTypeAsync(string type);
}
}

View file

@ -0,0 +1,20 @@
using Data.SeaHavenIndustries;
namespace SeaHaven.DataServices.Interfaces
{
public interface IDispatchDataService
{
Task<Dispatch?> GetByIdAsync(int id);
Task<Dispatch?> GetByIdWithDetailsAsync(int id);
Task<IEnumerable<Dispatch>> GetAllAsync();
Task<IEnumerable<Dispatch>> GetByVendorIdAsync(int vendorId);
Task<IEnumerable<Dispatch>> GetByStatusAsync(string status);
Task<(IEnumerable<Dispatch> Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null, string? status = null);
Task<Dispatch> AddAsync(Dispatch dispatch);
Task UpdateAsync(Dispatch dispatch);
Task DeleteAsync(int id);
Task<bool> ExistsAsync(int id);
Task<string> GenerateNextDispatchNumberAsync();
Task<int> CountAsync();
}
}

View file

@ -0,0 +1,17 @@
using Data.SeaHavenIndustries;
namespace SeaHaven.DataServices.Interfaces
{
public interface IEmployeeDataService
{
Task<Employee?> GetByIdAsync(int id);
Task<Employee?> GetByIdWithDetailsAsync(int id);
Task<IEnumerable<Employee>> GetAllAsync();
Task<(IEnumerable<Employee> Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null);
Task<Employee> AddAsync(Employee employee);
Task UpdateAsync(Employee employee);
Task DeleteAsync(int id);
Task<bool> ExistsAsync(int id);
Task<int> CountAsync();
}
}

View file

@ -0,0 +1,19 @@
using Data.SeaHavenIndustries;
namespace SeaHaven.DataServices.Interfaces
{
public interface IFollowUpDataService
{
Task<FollowUps?> GetByIdAsync(int id);
Task<FollowUps?> GetByIdWithDetailsAsync(int id);
Task<IEnumerable<FollowUps>> GetAllAsync();
Task<IEnumerable<FollowUps>> GetByWorkOrderIdAsync(int workOrderId);
Task<IEnumerable<FollowUps>> GetByUserIdAsync(string userId);
Task<(IEnumerable<FollowUps> Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null, string? status = null);
Task<FollowUps> AddAsync(FollowUps followUp);
Task UpdateAsync(FollowUps followUp);
Task DeleteAsync(int id);
Task<bool> ExistsAsync(int id);
Task<int> CountAsync();
}
}

View file

@ -0,0 +1,18 @@
using Data.SeaHavenIndustries;
namespace SeaHaven.DataServices.Interfaces
{
public interface ILocationDataService
{
Task<Locations?> GetByIdAsync(int id);
Task<Locations?> GetByIdWithDetailsAsync(int id);
Task<IEnumerable<Locations>> GetAllAsync();
Task<IEnumerable<Locations>> GetByAccountIdAsync(int accountId);
Task<(IEnumerable<Locations> Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null);
Task<Locations> AddAsync(Locations location);
Task UpdateAsync(Locations location);
Task DeleteAsync(int id);
Task<bool> ExistsAsync(int id);
Task<int> CountAsync();
}
}

View file

@ -0,0 +1,18 @@
using Data.SeaHavenIndustries;
namespace SeaHaven.DataServices.Interfaces
{
public interface IPMScheduleDataService
{
Task<PMSchedules?> GetByIdAsync(int id);
Task<PMSchedules?> GetByIdWithDetailsAsync(int id);
Task<IEnumerable<PMSchedules>> GetAllAsync();
Task<IEnumerable<PMSchedules>> GetByLocationIdAsync(int locationId);
Task<(IEnumerable<PMSchedules> Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null);
Task<PMSchedules> AddAsync(PMSchedules pmSchedule);
Task UpdateAsync(PMSchedules pmSchedule);
Task DeleteAsync(int id);
Task<bool> ExistsAsync(int id);
Task<int> CountAsync();
}
}

View file

@ -0,0 +1,19 @@
using Data.SeaHavenIndustries;
namespace SeaHaven.DataServices.Interfaces
{
public interface IVendorDataService
{
Task<Vendor?> GetByIdAsync(int id);
Task<Vendor?> GetByIdWithDetailsAsync(int id);
Task<IEnumerable<Vendor>> GetAllAsync();
Task<(IEnumerable<Vendor> Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null);
Task<Vendor> AddAsync(Vendor vendor);
Task UpdateAsync(Vendor vendor);
Task DeleteAsync(int id);
Task<bool> ExistsAsync(int id);
Task<VendorAccessToken?> GetActiveTokenAsync(int vendorId);
Task<VendorAccessToken?> GetTokenByStringAsync(string token);
Task<int> CountAsync();
}
}

View file

@ -0,0 +1,48 @@
using Data.SeaHavenIndustries;
namespace SeaHaven.DataServices.Interfaces
{
public interface IWorkOrderDataService
{
// Query operations
Task<WorkOrder?> GetByIdAsync(int id);
Task<WorkOrder?> GetByIdWithDetailsAsync(int id);
Task<WorkOrder?> GetByInternalNumberAsync(string internalNumber);
Task<IEnumerable<WorkOrder>> GetAllAsync();
Task<IEnumerable<WorkOrder>> GetByLocationIdAsync(int locationId);
Task<IEnumerable<WorkOrder>> GetByAssigneeAsync(string assigneeId);
Task<IEnumerable<WorkOrder>> GetByStatusAsync(string status);
Task<IEnumerable<WorkOrder>> GetByPriorityAsync(string priority);
// Paged results
Task<(IEnumerable<WorkOrder> Items, int TotalCount)> GetPagedAsync(
int page,
int pageSize,
string? search = null,
string? status = null,
string? priority = null);
// Command operations
Task<WorkOrder> AddAsync(WorkOrder workOrder);
Task UpdateAsync(WorkOrder workOrder);
Task DeleteAsync(int id);
// Related entities
Task<IEnumerable<WorkOrderContacts>> GetWorkOrderContactsAsync(int workOrderId);
Task<IEnumerable<WorkOrderCategories>> GetWorkOrderCategoriesAsync(int workOrderId);
Task<IEnumerable<WorkOrderAttachments>> GetWorkOrderAttachmentsAsync(int workOrderId);
Task<IEnumerable<Comments>> GetWorkOrderCommentsAsync(int workOrderId);
// Validation
Task<bool> ExistsAsync(int id);
Task<bool> InternalNumberExistsAsync(string internalNumber, int? excludeId = null);
// Internal number generation
Task<string> GenerateNextInternalWONumberAsync();
// Statistics
Task<int> CountAsync();
Task<int> CountByStatusAsync(string status);
Task<int> CountByLocationAsync(int locationId);
}
}

View file

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Data.SeaHavenIndustries\Data.SeaHavenIndustries.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,29 @@
namespace SeaHaven.Services.DTOs
{
public class AccountDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Phone { get; set; }
public string? Email { get; set; }
public string? Website { get; set; }
public DateTime? CreatedDate { get; set; }
public string? CreatedBy { get; set; }
}
public class CreateAccountDTO
{
public required string Name { get; set; }
public string? Phone { get; set; }
public string? Email { get; set; }
public string? Website { get; set; }
}
public class UpdateAccountDTO
{
public string? Name { get; set; }
public string? Phone { get; set; }
public string? Email { get; set; }
public string? Website { get; set; }
}
}

View file

@ -0,0 +1,29 @@
namespace SeaHaven.Services.DTOs
{
public class AssetDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Type { get; set; }
public string? SerialNumber { get; set; }
public int? LocationId { get; set; }
public string? LocationName { get; set; }
public DateTime? CreatedDate { get; set; }
}
public class CreateAssetDTO
{
public required string Name { get; set; }
public string? Type { get; set; }
public string? SerialNumber { get; set; }
public int? LocationId { get; set; }
}
public class UpdateAssetDTO
{
public string? Name { get; set; }
public string? Type { get; set; }
public string? SerialNumber { get; set; }
public int? LocationId { get; set; }
}
}

View file

@ -0,0 +1,67 @@
namespace SeaHaven.Services.DTOs
{
/// <summary>
/// Contact DTO for responses
/// </summary>
public class ContactDTO
{
public int Id { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? Email { get; set; }
public string? Phone { get; set; }
public string? Mobile { get; set; }
public string? Title { get; set; }
public string? Type { get; set; }
public int? AccountId { get; set; }
public string? AccountName { get; set; }
public DateTime? CreatedDate { get; set; }
public string? CreatedBy { get; set; }
public DateTime? ModifiedDate { get; set; }
public string? ModifiedBy { get; set; }
}
/// <summary>
/// DTO for creating a new contact
/// </summary>
public class CreateContactDTO
{
public required string FirstName { get; set; }
public required string LastName { get; set; }
public required string Email { get; set; }
public string? Phone { get; set; }
public string? Mobile { get; set; }
public string? Title { get; set; }
public string? Type { get; set; }
public int? AccountId { get; set; }
}
/// <summary>
/// DTO for updating an existing contact
/// </summary>
public class UpdateContactDTO
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? Email { get; set; }
public string? Phone { get; set; }
public string? Mobile { get; set; }
public string? Title { get; set; }
public string? Type { get; set; }
public int? AccountId { get; set; }
}
/// <summary>
/// Paged result wrapper
/// </summary>
public class PagedResult<T>
{
public IEnumerable<T> Items { get; set; } = new List<T>();
public int TotalCount { get; set; }
public int Page { get; set; }
public int PageSize { get; set; }
public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
public bool HasPrevious => Page > 1;
public bool HasNext => Page < TotalPages;
}
}

View file

@ -0,0 +1,30 @@
namespace SeaHaven.Services.DTOs
{
public class DispatchDTO
{
public int Id { get; set; }
public string? DispatchNumber { get; set; }
public string? Status { get; set; }
public int? VendorId { get; set; }
public string? VendorName { get; set; }
public DateTime? ScheduledDate { get; set; }
public DateTime? CompletedDate { get; set; }
public DateTime? CreatedDate { get; set; }
public string? CreatedBy { get; set; }
}
public class CreateDispatchDTO
{
public required int VendorId { get; set; }
public DateTime? ScheduledDate { get; set; }
public string? Status { get; set; }
}
public class UpdateDispatchDTO
{
public int? VendorId { get; set; }
public DateTime? ScheduledDate { get; set; }
public DateTime? CompletedDate { get; set; }
public string? Status { get; set; }
}
}

View file

@ -0,0 +1,40 @@
namespace SeaHaven.Services.DTOs
{
public class EmployeeDTO
{
public int Id { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? Email { get; set; }
public string? Phone { get; set; }
public int? DepartmentId { get; set; }
public string? DepartmentName { get; set; }
public int? JobTitleId { get; set; }
public string? JobTitleName { get; set; }
public int? RegionId { get; set; }
public string? RegionName { get; set; }
public DateTime? CreatedDate { get; set; }
}
public class CreateEmployeeDTO
{
public required string FirstName { get; set; }
public required string LastName { get; set; }
public string? Email { get; set; }
public string? Phone { get; set; }
public int? DepartmentId { get; set; }
public int? JobTitleId { get; set; }
public int? RegionId { get; set; }
}
public class UpdateEmployeeDTO
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? Email { get; set; }
public string? Phone { get; set; }
public int? DepartmentId { get; set; }
public int? JobTitleId { get; set; }
public int? RegionId { get; set; }
}
}

View file

@ -0,0 +1,34 @@
namespace SeaHaven.Services.DTOs
{
public class FollowUpDTO
{
public int Id { get; set; }
public string? Subject { get; set; }
public string? Description { get; set; }
public string? Status { get; set; }
public int? WorkOrderId { get; set; }
public string? AssignedTo { get; set; }
public DateTime? DueDate { get; set; }
public DateTime? CreatedDate { get; set; }
public string? CreatedBy { get; set; }
}
public class CreateFollowUpDTO
{
public required string Subject { get; set; }
public string? Description { get; set; }
public string? Status { get; set; }
public int? WorkOrderId { get; set; }
public string? AssignedTo { get; set; }
public DateTime? DueDate { get; set; }
}
public class UpdateFollowUpDTO
{
public string? Subject { get; set; }
public string? Description { get; set; }
public string? Status { get; set; }
public string? AssignedTo { get; set; }
public DateTime? DueDate { get; set; }
}
}

View file

@ -0,0 +1,36 @@
namespace SeaHaven.Services.DTOs
{
public class LocationDTO
{
public int Id { get; set; }
public string? LocationName { get; set; }
public string? Address { get; set; }
public string? City { get; set; }
public string? State { get; set; }
public string? Zipcode { get; set; }
public int? AccountId { get; set; }
public string? AccountName { get; set; }
public DateTime? CreatedDate { get; set; }
public string? CreatedBy { get; set; }
}
public class CreateLocationDTO
{
public required string LocationName { get; set; }
public string? Address { get; set; }
public string? City { get; set; }
public string? State { get; set; }
public string? Zipcode { get; set; }
public int? AccountId { get; set; }
}
public class UpdateLocationDTO
{
public string? LocationName { get; set; }
public string? Address { get; set; }
public string? City { get; set; }
public string? State { get; set; }
public string? Zipcode { get; set; }
public int? AccountId { get; set; }
}
}

View file

@ -0,0 +1,32 @@
namespace SeaHaven.Services.DTOs
{
public class PMScheduleDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public int? LocationId { get; set; }
public string? LocationName { get; set; }
public string? Frequency { get; set; }
public DateTime? NextDueDate { get; set; }
public DateTime? CreatedDate { get; set; }
}
public class CreatePMScheduleDTO
{
public required string Name { get; set; }
public string? Description { get; set; }
public int? LocationId { get; set; }
public string? Frequency { get; set; }
public DateTime? NextDueDate { get; set; }
}
public class UpdatePMScheduleDTO
{
public string? Name { get; set; }
public string? Description { get; set; }
public int? LocationId { get; set; }
public string? Frequency { get; set; }
public DateTime? NextDueDate { get; set; }
}
}

View file

@ -0,0 +1,36 @@
namespace SeaHaven.Services.DTOs
{
public class VendorDTO
{
public int Id { get; set; }
public string? Name { 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? Zipcode { get; set; }
}
public class CreateVendorDTO
{
public required string Name { 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? Zipcode { get; set; }
}
public class UpdateVendorDTO
{
public string? Name { 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? Zipcode { get; set; }
}
}

View file

@ -0,0 +1,44 @@
namespace SeaHaven.Services.DTOs
{
public class WorkOrderDTO
{
public int Id { get; set; }
public string? InternalWONumber { get; set; }
public string? WorkerOrderTitle { get; set; }
public string? Description { get; set; }
public string? Status { get; set; }
public string? Priority { get; set; }
public int? LocationId { get; set; }
public string? LocationName { get; set; }
public string? AssignTo { get; set; }
public DateTime? DueDate { get; set; }
public DateTime? CompletedDate { get; set; }
public DateTime? CreatedDate { get; set; }
public string? CreatedBy { get; set; }
public DateTime? ModifiedDate { get; set; }
public string? ModifiedBy { get; set; }
}
public class CreateWorkOrderDTO
{
public required string WorkerOrderTitle { get; set; }
public string? Description { get; set; }
public string? Status { get; set; }
public string? Priority { get; set; }
public int? LocationId { get; set; }
public string? AssignTo { get; set; }
public DateTime? DueDate { get; set; }
}
public class UpdateWorkOrderDTO
{
public string? WorkerOrderTitle { get; set; }
public string? Description { get; set; }
public string? Status { get; set; }
public string? Priority { get; set; }
public int? LocationId { get; set; }
public string? AssignTo { get; set; }
public DateTime? DueDate { get; set; }
public DateTime? CompletedDate { get; set; }
}
}

View file

@ -0,0 +1,220 @@
using Data.SeaHavenIndustries;
using SeaHaven.DataServices.Interfaces;
using SeaHaven.Services.DTOs;
using SeaHaven.Services.Interfaces;
namespace SeaHaven.Services.Implementation
{
/// <summary>
/// Contact service implementation
/// Contains business logic, validation, and orchestration for Contact operations
/// </summary>
public class ContactService : IContactService
{
private readonly IContactDataService _contactDataService;
public ContactService(IContactDataService contactDataService)
{
_contactDataService = contactDataService;
}
// Query operations
public async Task<ContactDTO?> GetContactByIdAsync(int id)
{
var contact = await _contactDataService.GetByIdAsync(id);
return contact == null ? null : MapToDTO(contact);
}
public async Task<ContactDTO?> GetContactByIdWithDetailsAsync(int id)
{
var contact = await _contactDataService.GetByIdWithDetailsAsync(id);
return contact == null ? null : MapToDTO(contact);
}
public async Task<IEnumerable<ContactDTO>> GetAllContactsAsync()
{
var contacts = await _contactDataService.GetAllAsync();
return contacts.Select(MapToDTO);
}
public async Task<IEnumerable<ContactDTO>> GetContactsByAccountIdAsync(int accountId)
{
var contacts = await _contactDataService.GetByAccountIdAsync(accountId);
return contacts.Select(MapToDTO);
}
public async Task<IEnumerable<ContactDTO>> GetContactsByTypeAsync(string type)
{
var contacts = await _contactDataService.GetByTypeAsync(type);
return contacts.Select(MapToDTO);
}
public async Task<ContactDTO?> GetContactByEmailAsync(string email)
{
var contact = await _contactDataService.GetByEmailAsync(email);
return contact == null ? null : MapToDTO(contact);
}
// Paged results
public async Task<PagedResult<ContactDTO>> GetContactsPagedAsync(
int page,
int pageSize,
string? search = null,
string? type = null)
{
var (items, totalCount) = await _contactDataService.GetPagedAsync(page, pageSize, search, type);
return new PagedResult<ContactDTO>
{
Items = items.Select(MapToDTO),
TotalCount = totalCount,
Page = page,
PageSize = pageSize
};
}
// Command operations
public async Task<ContactDTO> CreateContactAsync(CreateContactDTO dto, string userId)
{
// Validation
if (string.IsNullOrWhiteSpace(dto.Email))
throw new ArgumentException("Email is required", nameof(dto.Email));
if (await _contactDataService.EmailExistsAsync(dto.Email))
throw new InvalidOperationException($"Email '{dto.Email}' already exists");
// Map DTO to entity
var contact = new Contacts
{
FirstName = dto.FirstName,
LastName = dto.LastName,
Email = dto.Email,
PhoneNumber = dto.Phone ?? dto.Mobile, // Contacts only has PhoneNumber, not separate Mobile
Title = dto.Title,
ContactType = dto.Type,
AccountId = dto.AccountId,
createdby = userId,
CreatedDate = DateTime.Now
};
// Save
var savedContact = await _contactDataService.AddAsync(contact);
return MapToDTO(savedContact);
}
public async Task<ContactDTO> UpdateContactAsync(int id, UpdateContactDTO dto, string userId)
{
// Check if exists
var existingContact = await _contactDataService.GetByIdAsync(id);
if (existingContact == null)
throw new KeyNotFoundException($"Contact with ID {id} not found");
// Validate email uniqueness if changed
if (!string.IsNullOrWhiteSpace(dto.Email) && dto.Email != existingContact.Email)
{
if (await _contactDataService.EmailExistsAsync(dto.Email, id))
throw new InvalidOperationException($"Email '{dto.Email}' already exists");
}
// Update fields
if (!string.IsNullOrWhiteSpace(dto.FirstName))
existingContact.FirstName = dto.FirstName;
if (!string.IsNullOrWhiteSpace(dto.LastName))
existingContact.LastName = dto.LastName;
if (!string.IsNullOrWhiteSpace(dto.Email))
existingContact.Email = dto.Email;
if (dto.Phone != null || dto.Mobile != null)
existingContact.PhoneNumber = dto.Phone ?? dto.Mobile; // Contacts only has PhoneNumber
if (dto.Title != null)
existingContact.Title = dto.Title;
if (dto.Type != null)
existingContact.ContactType = dto.Type;
if (dto.AccountId.HasValue)
existingContact.AccountId = dto.AccountId;
existingContact.LastModificationTime = DateTime.Now;
// Save
await _contactDataService.UpdateAsync(existingContact);
return MapToDTO(existingContact);
}
public async Task DeleteContactAsync(int id, string userId)
{
// Check if exists
if (!await _contactDataService.ExistsAsync(id))
throw new KeyNotFoundException($"Contact with ID {id} not found");
// Business logic: Check if contact is being used (add your logic here)
// For example, check if contact is linked to work orders, etc.
await _contactDataService.DeleteAsync(id);
}
// Validation operations
public async Task<bool> ContactExistsAsync(int id)
{
return await _contactDataService.ExistsAsync(id);
}
public async Task<bool> EmailExistsAsync(string email, int? excludeId = null)
{
return await _contactDataService.EmailExistsAsync(email, excludeId);
}
public async Task<bool> PhoneExistsAsync(string phone, int? excludeId = null)
{
return await _contactDataService.PhoneExistsAsync(phone, excludeId);
}
// Statistics
public async Task<int> GetTotalContactCountAsync()
{
return await _contactDataService.CountAsync();
}
public async Task<int> GetContactCountByAccountAsync(int accountId)
{
return await _contactDataService.CountByAccountIdAsync(accountId);
}
public async Task<Dictionary<string, int>> GetContactCountByTypeAsync()
{
var contacts = await _contactDataService.GetAllAsync();
return contacts
.Where(c => !string.IsNullOrWhiteSpace(c.ContactType))
.GroupBy(c => c.ContactType!)
.ToDictionary(g => g.Key, g => g.Count());
}
// Private mapping methods
private static ContactDTO MapToDTO(Contacts contact)
{
return new ContactDTO
{
Id = contact.Id,
FirstName = contact.FirstName,
LastName = contact.LastName,
Email = contact.Email,
Phone = contact.PhoneNumber,
Mobile = contact.PhoneNumber, // Contacts only has one PhoneNumber field
Title = contact.Title,
Type = contact.ContactType,
AccountId = contact.AccountId,
AccountName = contact.Account?.Name,
CreatedDate = contact.CreatedDate,
CreatedBy = contact.createdby,
ModifiedDate = contact.LastModificationTime,
ModifiedBy = null // Not tracked in FullAuditEntity
};
}
}
}

View file

@ -0,0 +1,142 @@
using Data.SeaHavenIndustries;
using SeaHaven.DataServices.Interfaces;
using SeaHaven.Services.DTOs;
using SeaHaven.Services.Interfaces;
namespace SeaHaven.Services.Implementation
{
public class WorkOrderService : IWorkOrderService
{
private readonly IWorkOrderDataService _workOrderDataService;
public WorkOrderService(IWorkOrderDataService workOrderDataService)
{
_workOrderDataService = workOrderDataService;
}
public async Task<WorkOrderDTO?> GetWorkOrderByIdAsync(int id)
{
var workOrder = await _workOrderDataService.GetByIdAsync(id);
return workOrder == null ? null : MapToDTO(workOrder);
}
public async Task<WorkOrderDTO?> GetWorkOrderByIdWithDetailsAsync(int id)
{
var workOrder = await _workOrderDataService.GetByIdWithDetailsAsync(id);
return workOrder == null ? null : MapToDTO(workOrder);
}
public async Task<IEnumerable<WorkOrderDTO>> GetAllWorkOrdersAsync()
{
var workOrders = await _workOrderDataService.GetAllAsync();
return workOrders.Select(MapToDTO);
}
public async Task<PagedResult<WorkOrderDTO>> GetWorkOrdersPagedAsync(int page, int pageSize, string? search = null, string? status = null, string? priority = null)
{
var (items, totalCount) = await _workOrderDataService.GetPagedAsync(page, pageSize, search, status, priority);
return new PagedResult<WorkOrderDTO>
{
Items = items.Select(MapToDTO),
TotalCount = totalCount,
Page = page,
PageSize = pageSize
};
}
public async Task<WorkOrderDTO> CreateWorkOrderAsync(CreateWorkOrderDTO dto, string userId)
{
var workOrder = new WorkOrder
{
WorkerOrderTitle = dto.WorkerOrderTitle,
Description = dto.Description,
Status = dto.Status ?? "Open",
Priority = dto.Priority,
LocationId = dto.LocationId,
AssignTo = dto.AssignTo,
DueDate = dto.DueDate,
createdby = userId,
CreatedDate = DateTime.Now
};
// Generate internal number
workOrder.InternalWONumber = await _workOrderDataService.GenerateNextInternalWONumberAsync();
var savedWorkOrder = await _workOrderDataService.AddAsync(workOrder);
return MapToDTO(savedWorkOrder);
}
public async Task<WorkOrderDTO> UpdateWorkOrderAsync(int id, UpdateWorkOrderDTO dto, string userId)
{
var existingWorkOrder = await _workOrderDataService.GetByIdAsync(id);
if (existingWorkOrder == null)
throw new KeyNotFoundException($"WorkOrder with ID {id} not found");
if (!string.IsNullOrWhiteSpace(dto.WorkerOrderTitle))
existingWorkOrder.WorkerOrderTitle = dto.WorkerOrderTitle;
if (dto.Description != null)
existingWorkOrder.Description = dto.Description;
if (dto.Status != null)
existingWorkOrder.Status = dto.Status;
if (dto.Priority != null)
existingWorkOrder.Priority = dto.Priority;
if (dto.LocationId.HasValue)
existingWorkOrder.LocationId = dto.LocationId;
if (dto.AssignTo != null)
existingWorkOrder.AssignTo = dto.AssignTo;
if (dto.DueDate.HasValue)
existingWorkOrder.DueDate = dto.DueDate;
if (dto.CompletedDate.HasValue)
existingWorkOrder.CompletedDate = dto.CompletedDate;
existingWorkOrder.LastModificationTime = DateTime.Now;
await _workOrderDataService.UpdateAsync(existingWorkOrder);
return MapToDTO(existingWorkOrder);
}
public async Task DeleteWorkOrderAsync(int id, string userId)
{
if (!await _workOrderDataService.ExistsAsync(id))
throw new KeyNotFoundException($"WorkOrder with ID {id} not found");
await _workOrderDataService.DeleteAsync(id);
}
public async Task<bool> WorkOrderExistsAsync(int id)
{
return await _workOrderDataService.ExistsAsync(id);
}
public async Task<string> GenerateNextInternalWONumberAsync()
{
return await _workOrderDataService.GenerateNextInternalWONumberAsync();
}
public async Task<int> GetTotalWorkOrderCountAsync()
{
return await _workOrderDataService.CountAsync();
}
private static WorkOrderDTO MapToDTO(WorkOrder workOrder)
{
return new WorkOrderDTO
{
Id = workOrder.Id,
InternalWONumber = workOrder.InternalWONumber,
WorkerOrderTitle = workOrder.WorkerOrderTitle,
Description = workOrder.Description,
Status = workOrder.Status,
Priority = workOrder.Priority,
LocationId = workOrder.LocationId,
LocationName = workOrder.Locations?.Name,
AssignTo = workOrder.AssignTo,
DueDate = workOrder.DueDate,
CompletedDate = workOrder.CompletedDate,
CreatedDate = workOrder.CreatedDate,
CreatedBy = workOrder.createdby,
ModifiedDate = workOrder.LastModificationTime,
ModifiedBy = null // Not tracked in FullAuditEntity
};
}
}
}

View file

@ -0,0 +1,17 @@
using SeaHaven.Services.DTOs;
namespace SeaHaven.Services.Interfaces
{
public interface IAccountService
{
Task<AccountDTO?> GetAccountByIdAsync(int id);
Task<AccountDTO?> GetAccountByIdWithDetailsAsync(int id);
Task<IEnumerable<AccountDTO>> GetAllAccountsAsync();
Task<PagedResult<AccountDTO>> GetAccountsPagedAsync(int page, int pageSize, string? search = null);
Task<AccountDTO> CreateAccountAsync(CreateAccountDTO dto, string userId);
Task<AccountDTO> UpdateAccountAsync(int id, UpdateAccountDTO dto, string userId);
Task DeleteAccountAsync(int id, string userId);
Task<bool> AccountExistsAsync(int id);
Task<int> GetTotalAccountCountAsync();
}
}

View file

@ -0,0 +1,18 @@
using SeaHaven.Services.DTOs;
namespace SeaHaven.Services.Interfaces
{
public interface IAssetService
{
Task<AssetDTO?> GetAssetByIdAsync(int id);
Task<AssetDTO?> GetAssetByIdWithDetailsAsync(int id);
Task<IEnumerable<AssetDTO>> GetAllAssetsAsync();
Task<IEnumerable<AssetDTO>> GetAssetsByLocationIdAsync(int locationId);
Task<PagedResult<AssetDTO>> GetAssetsPagedAsync(int page, int pageSize, string? search = null);
Task<AssetDTO> CreateAssetAsync(CreateAssetDTO dto, string userId);
Task<AssetDTO> UpdateAssetAsync(int id, UpdateAssetDTO dto, string userId);
Task DeleteAssetAsync(int id, string userId);
Task<bool> AssetExistsAsync(int id);
Task<int> GetTotalAssetCountAsync();
}
}

View file

@ -0,0 +1,41 @@
using SeaHaven.Services.DTOs;
namespace SeaHaven.Services.Interfaces
{
/// <summary>
/// Contact business service interface
/// Handles business logic, validation, and orchestration for Contact operations
/// </summary>
public interface IContactService
{
// Query operations
Task<ContactDTO?> GetContactByIdAsync(int id);
Task<ContactDTO?> GetContactByIdWithDetailsAsync(int id);
Task<IEnumerable<ContactDTO>> GetAllContactsAsync();
Task<IEnumerable<ContactDTO>> GetContactsByAccountIdAsync(int accountId);
Task<IEnumerable<ContactDTO>> GetContactsByTypeAsync(string type);
Task<ContactDTO?> GetContactByEmailAsync(string email);
// Paged results
Task<PagedResult<ContactDTO>> GetContactsPagedAsync(
int page,
int pageSize,
string? search = null,
string? type = null);
// Command operations
Task<ContactDTO> CreateContactAsync(CreateContactDTO dto, string userId);
Task<ContactDTO> UpdateContactAsync(int id, UpdateContactDTO dto, string userId);
Task DeleteContactAsync(int id, string userId);
// Validation operations
Task<bool> ContactExistsAsync(int id);
Task<bool> EmailExistsAsync(string email, int? excludeId = null);
Task<bool> PhoneExistsAsync(string phone, int? excludeId = null);
// Statistics
Task<int> GetTotalContactCountAsync();
Task<int> GetContactCountByAccountAsync(int accountId);
Task<Dictionary<string, int>> GetContactCountByTypeAsync();
}
}

View file

@ -0,0 +1,19 @@
using SeaHaven.Services.DTOs;
namespace SeaHaven.Services.Interfaces
{
public interface IDispatchService
{
Task<DispatchDTO?> GetDispatchByIdAsync(int id);
Task<DispatchDTO?> GetDispatchByIdWithDetailsAsync(int id);
Task<IEnumerable<DispatchDTO>> GetAllDispatchesAsync();
Task<IEnumerable<DispatchDTO>> GetDispatchesByVendorIdAsync(int vendorId);
Task<PagedResult<DispatchDTO>> GetDispatchesPagedAsync(int page, int pageSize, string? search = null, string? status = null);
Task<DispatchDTO> CreateDispatchAsync(CreateDispatchDTO dto, string userId);
Task<DispatchDTO> UpdateDispatchAsync(int id, UpdateDispatchDTO dto, string userId);
Task DeleteDispatchAsync(int id, string userId);
Task<bool> DispatchExistsAsync(int id);
Task<string> GenerateNextDispatchNumberAsync();
Task<int> GetTotalDispatchCountAsync();
}
}

View file

@ -0,0 +1,17 @@
using SeaHaven.Services.DTOs;
namespace SeaHaven.Services.Interfaces
{
public interface IEmployeeService
{
Task<EmployeeDTO?> GetEmployeeByIdAsync(int id);
Task<EmployeeDTO?> GetEmployeeByIdWithDetailsAsync(int id);
Task<IEnumerable<EmployeeDTO>> GetAllEmployeesAsync();
Task<PagedResult<EmployeeDTO>> GetEmployeesPagedAsync(int page, int pageSize, string? search = null);
Task<EmployeeDTO> CreateEmployeeAsync(CreateEmployeeDTO dto, string userId);
Task<EmployeeDTO> UpdateEmployeeAsync(int id, UpdateEmployeeDTO dto, string userId);
Task DeleteEmployeeAsync(int id, string userId);
Task<bool> EmployeeExistsAsync(int id);
Task<int> GetTotalEmployeeCountAsync();
}
}

View file

@ -0,0 +1,18 @@
using SeaHaven.Services.DTOs;
namespace SeaHaven.Services.Interfaces
{
public interface IFollowUpService
{
Task<FollowUpDTO?> GetFollowUpByIdAsync(int id);
Task<FollowUpDTO?> GetFollowUpByIdWithDetailsAsync(int id);
Task<IEnumerable<FollowUpDTO>> GetAllFollowUpsAsync();
Task<IEnumerable<FollowUpDTO>> GetFollowUpsByWorkOrderIdAsync(int workOrderId);
Task<PagedResult<FollowUpDTO>> GetFollowUpsPagedAsync(int page, int pageSize, string? search = null, string? status = null);
Task<FollowUpDTO> CreateFollowUpAsync(CreateFollowUpDTO dto, string userId);
Task<FollowUpDTO> UpdateFollowUpAsync(int id, UpdateFollowUpDTO dto, string userId);
Task DeleteFollowUpAsync(int id, string userId);
Task<bool> FollowUpExistsAsync(int id);
Task<int> GetTotalFollowUpCountAsync();
}
}

View file

@ -0,0 +1,18 @@
using SeaHaven.Services.DTOs;
namespace SeaHaven.Services.Interfaces
{
public interface ILocationService
{
Task<LocationDTO?> GetLocationByIdAsync(int id);
Task<LocationDTO?> GetLocationByIdWithDetailsAsync(int id);
Task<IEnumerable<LocationDTO>> GetAllLocationsAsync();
Task<IEnumerable<LocationDTO>> GetLocationsByAccountIdAsync(int accountId);
Task<PagedResult<LocationDTO>> GetLocationsPagedAsync(int page, int pageSize, string? search = null);
Task<LocationDTO> CreateLocationAsync(CreateLocationDTO dto, string userId);
Task<LocationDTO> UpdateLocationAsync(int id, UpdateLocationDTO dto, string userId);
Task DeleteLocationAsync(int id, string userId);
Task<bool> LocationExistsAsync(int id);
Task<int> GetTotalLocationCountAsync();
}
}

View file

@ -0,0 +1,18 @@
using SeaHaven.Services.DTOs;
namespace SeaHaven.Services.Interfaces
{
public interface IPMScheduleService
{
Task<PMScheduleDTO?> GetPMScheduleByIdAsync(int id);
Task<PMScheduleDTO?> GetPMScheduleByIdWithDetailsAsync(int id);
Task<IEnumerable<PMScheduleDTO>> GetAllPMSchedulesAsync();
Task<IEnumerable<PMScheduleDTO>> GetPMSchedulesByLocationIdAsync(int locationId);
Task<PagedResult<PMScheduleDTO>> GetPMSchedulesPagedAsync(int page, int pageSize, string? search = null);
Task<PMScheduleDTO> CreatePMScheduleAsync(CreatePMScheduleDTO dto, string userId);
Task<PMScheduleDTO> UpdatePMScheduleAsync(int id, UpdatePMScheduleDTO dto, string userId);
Task DeletePMScheduleAsync(int id, string userId);
Task<bool> PMScheduleExistsAsync(int id);
Task<int> GetTotalPMScheduleCountAsync();
}
}

View file

@ -0,0 +1,17 @@
using SeaHaven.Services.DTOs;
namespace SeaHaven.Services.Interfaces
{
public interface IVendorService
{
Task<VendorDTO?> GetVendorByIdAsync(int id);
Task<VendorDTO?> GetVendorByIdWithDetailsAsync(int id);
Task<IEnumerable<VendorDTO>> GetAllVendorsAsync();
Task<PagedResult<VendorDTO>> GetVendorsPagedAsync(int page, int pageSize, string? search = null);
Task<VendorDTO> CreateVendorAsync(CreateVendorDTO dto, string userId);
Task<VendorDTO> UpdateVendorAsync(int id, UpdateVendorDTO dto, string userId);
Task DeleteVendorAsync(int id, string userId);
Task<bool> VendorExistsAsync(int id);
Task<int> GetTotalVendorCountAsync();
}
}

View file

@ -0,0 +1,18 @@
using SeaHaven.Services.DTOs;
namespace SeaHaven.Services.Interfaces
{
public interface IWorkOrderService
{
Task<WorkOrderDTO?> GetWorkOrderByIdAsync(int id);
Task<WorkOrderDTO?> GetWorkOrderByIdWithDetailsAsync(int id);
Task<IEnumerable<WorkOrderDTO>> GetAllWorkOrdersAsync();
Task<PagedResult<WorkOrderDTO>> GetWorkOrdersPagedAsync(int page, int pageSize, string? search = null, string? status = null, string? priority = null);
Task<WorkOrderDTO> CreateWorkOrderAsync(CreateWorkOrderDTO dto, string userId);
Task<WorkOrderDTO> UpdateWorkOrderAsync(int id, UpdateWorkOrderDTO dto, string userId);
Task DeleteWorkOrderAsync(int id, string userId);
Task<bool> WorkOrderExistsAsync(int id);
Task<string> GenerateNextInternalWONumberAsync();
Task<int> GetTotalWorkOrderCountAsync();
}
}

View file

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\SeaHaven.DataServices\SeaHaven.DataServices.csproj" />
<ProjectReference Include="..\Data.SeaHavenIndustries\Data.SeaHavenIndustries.csproj" />
</ItemGroup>
</Project>

View file

@ -9,6 +9,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Data.SeaHavenIndustries", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Api.SeaHavenIndustries", "Api.SeaHavenIndustries\Api.SeaHavenIndustries.csproj", "{68A98A85-CD53-44BE-8736-B1CD7A8C5193}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeaHaven.Services", "SeaHaven.Services\SeaHaven.Services.csproj", "{DFF11D84-FEA1-42D1-99FF-1113133160CA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeaHaven.DataServices", "SeaHaven.DataServices\SeaHaven.DataServices.csproj", "{60DF3943-9BC3-4444-8E7A-EEC34DDC7806}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -27,6 +31,14 @@ Global
{68A98A85-CD53-44BE-8736-B1CD7A8C5193}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68A98A85-CD53-44BE-8736-B1CD7A8C5193}.Release|Any CPU.ActiveCfg = Release|Any CPU
{68A98A85-CD53-44BE-8736-B1CD7A8C5193}.Release|Any CPU.Build.0 = Release|Any CPU
{DFF11D84-FEA1-42D1-99FF-1113133160CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DFF11D84-FEA1-42D1-99FF-1113133160CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DFF11D84-FEA1-42D1-99FF-1113133160CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DFF11D84-FEA1-42D1-99FF-1113133160CA}.Release|Any CPU.Build.0 = Release|Any CPU
{60DF3943-9BC3-4444-8E7A-EEC34DDC7806}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{60DF3943-9BC3-4444-8E7A-EEC34DDC7806}.Debug|Any CPU.Build.0 = Debug|Any CPU
{60DF3943-9BC3-4444-8E7A-EEC34DDC7806}.Release|Any CPU.ActiveCfg = Release|Any CPU
{60DF3943-9BC3-4444-8E7A-EEC34DDC7806}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE