diff --git a/Api.SeaHavenIndustries/Api.SeaHavenIndustries.csproj b/Api.SeaHavenIndustries/Api.SeaHavenIndustries.csproj
index cd137c4..b1c8fc5 100644
--- a/Api.SeaHavenIndustries/Api.SeaHavenIndustries.csproj
+++ b/Api.SeaHavenIndustries/Api.SeaHavenIndustries.csproj
@@ -20,6 +20,8 @@
+
+
diff --git a/Api.SeaHavenIndustries/Controllers/DispatchController.cs b/Api.SeaHavenIndustries/Controllers/DispatchController.cs
index 4996a16..b265914 100644
--- a/Api.SeaHavenIndustries/Controllers/DispatchController.cs
+++ b/Api.SeaHavenIndustries/Controllers/DispatchController.cs
@@ -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,
diff --git a/Api.SeaHavenIndustries/Controllers/LocationController.cs b/Api.SeaHavenIndustries/Controllers/LocationController.cs
index 9dbb485..7843b1c 100644
--- a/Api.SeaHavenIndustries/Controllers/LocationController.cs
+++ b/Api.SeaHavenIndustries/Controllers/LocationController.cs
@@ -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" });
diff --git a/Api.SeaHavenIndustries/Controllers/SyncController.cs b/Api.SeaHavenIndustries/Controllers/SyncController.cs
index ada5e1b..5d7f778 100644
--- a/Api.SeaHavenIndustries/Controllers/SyncController.cs
+++ b/Api.SeaHavenIndustries/Controllers/SyncController.cs
@@ -375,7 +375,7 @@ namespace Api.SeaHavenIndustries.Controllers
{
Name = matchCode,
Title = building,
- Address = address,
+ Address1 = address,
Status = "Active"
};
diff --git a/Api.SeaHavenIndustries/Controllers/VendorPortalController.cs b/Api.SeaHavenIndustries/Controllers/VendorPortalController.cs
index 9e3a13b..8841861 100644
--- a/Api.SeaHavenIndustries/Controllers/VendorPortalController.cs
+++ b/Api.SeaHavenIndustries/Controllers/VendorPortalController.cs
@@ -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,
diff --git a/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs b/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs
index 9ec544c..fba8cdd 100644
--- a/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs
+++ b/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs
@@ -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
$"
{wo.DueDate?.ToString("yyyy-MM-dd") ?? "N/A"} | "));
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);
diff --git a/Api.SeaHavenIndustries/Program.cs b/Api.SeaHavenIndustries/Program.cs
index 1dfb93f..f84a1a6 100644
--- a/Api.SeaHavenIndustries/Program.cs
+++ b/Api.SeaHavenIndustries/Program.cs
@@ -31,6 +31,7 @@ builder.Services.AddResponseCompression(opts =>
});
builder.Services.AddCors(option =>
option.AddDefaultPolicy(builder => builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()));
+// Helper services
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddSingleton(sp =>
@@ -41,6 +42,33 @@ builder.Services.AddSingleton(sp
if (File.Exists(csvPath)) zipService.Load(csvPath);
return zipService;
});
+
+// ========================================================================
+// MULTI-TIER ARCHITECTURE - Dependency Injection Registration
+// ========================================================================
+
+// Data Services (Data Access Layer)
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+
+// Business Services (Business Logic Layer) - Only register services that exist
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+// 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;
diff --git a/Data.SeaHavenIndustries/Models/Locations.cs b/Data.SeaHavenIndustries/Models/Locations.cs
index f32b1de..9d5fdf3 100644
--- a/Data.SeaHavenIndustries/Models/Locations.cs
+++ b/Data.SeaHavenIndustries/Models/Locations.cs
@@ -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? Templates { get; set; }
public ICollection? workOrders { get; set; }
}
diff --git a/FIX_LOCATIONS_SCHEMA.sql b/FIX_LOCATIONS_SCHEMA.sql
new file mode 100644
index 0000000..042c7a8
--- /dev/null
+++ b/FIX_LOCATIONS_SCHEMA.sql
@@ -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
diff --git a/SeaHaven.DataServices/Implementation/AccountDataService.cs b/SeaHaven.DataServices/Implementation/AccountDataService.cs
new file mode 100644
index 0000000..29f6861
--- /dev/null
+++ b/SeaHaven.DataServices/Implementation/AccountDataService.cs
@@ -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 GetByIdAsync(int id)
+ {
+ return await _context.Accounts.FindAsync(id);
+ }
+
+ public async Task GetByIdWithDetailsAsync(int id)
+ {
+ return await _context.Accounts
+ .Include(a => a.Contacts)
+ .Include(a => a.Addresses)
+ .FirstOrDefaultAsync(a => a.Id == id);
+ }
+
+ public async Task> GetAllAsync()
+ {
+ return await _context.Accounts.ToListAsync();
+ }
+
+ public async Task<(IEnumerable 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 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 ExistsAsync(int id)
+ {
+ return await _context.Accounts.AnyAsync(a => a.Id == id);
+ }
+
+ public async Task CountAsync()
+ {
+ return await _context.Accounts.CountAsync();
+ }
+ }
+}
diff --git a/SeaHaven.DataServices/Implementation/AssetDataService.cs b/SeaHaven.DataServices/Implementation/AssetDataService.cs
new file mode 100644
index 0000000..f5e0dc9
--- /dev/null
+++ b/SeaHaven.DataServices/Implementation/AssetDataService.cs
@@ -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 GetByIdAsync(int id)
+ {
+ return await _context.Assets.FindAsync(id);
+ }
+
+ public async Task GetByIdWithDetailsAsync(int id)
+ {
+ return await _context.Assets
+ .FirstOrDefaultAsync(a => a.Id == id);
+ }
+
+ public async Task> GetAllAsync()
+ {
+ return await _context.Assets.ToListAsync();
+ }
+
+ public async Task> GetByLocationIdAsync(int locationId)
+ {
+ return await _context.Assets
+ .Where(a => a.LocationId == locationId)
+ .ToListAsync();
+ }
+
+ public async Task<(IEnumerable 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 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 ExistsAsync(int id)
+ {
+ return await _context.Assets.AnyAsync(a => a.Id == id);
+ }
+
+ public async Task CountAsync()
+ {
+ return await _context.Assets.CountAsync();
+ }
+ }
+}
diff --git a/SeaHaven.DataServices/Implementation/CategoryDataService.cs b/SeaHaven.DataServices/Implementation/CategoryDataService.cs
new file mode 100644
index 0000000..c5af096
--- /dev/null
+++ b/SeaHaven.DataServices/Implementation/CategoryDataService.cs
@@ -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 GetByIdAsync(int id)
+ {
+ return await _context.Categories.FindAsync(id);
+ }
+
+ public async Task> GetAllAsync()
+ {
+ return await _context.Categories.ToListAsync();
+ }
+
+ public async Task 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 ExistsAsync(int id)
+ {
+ return await _context.Categories.AnyAsync(c => c.Id == id);
+ }
+ }
+}
diff --git a/SeaHaven.DataServices/Implementation/ContactDataService.cs b/SeaHaven.DataServices/Implementation/ContactDataService.cs
new file mode 100644
index 0000000..5cb50eb
--- /dev/null
+++ b/SeaHaven.DataServices/Implementation/ContactDataService.cs
@@ -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 GetByIdAsync(int id)
+ {
+ return await _context.Contacts.FindAsync(id);
+ }
+
+ public async Task GetByIdWithDetailsAsync(int id)
+ {
+ return await _context.Contacts
+ .Include(c => c.Location)
+ .Include(c => c.Account)
+ .FirstOrDefaultAsync(c => c.Id == id);
+ }
+
+ public async Task> GetAllAsync()
+ {
+ return await _context.Contacts.ToListAsync();
+ }
+
+ public async Task> GetByLocationIdAsync(int locationId)
+ {
+ return await _context.Contacts
+ .Where(c => c.LocationId == locationId)
+ .ToListAsync();
+ }
+
+ public async Task> GetByAccountIdAsync(int accountId)
+ {
+ return await _context.Contacts
+ .Where(c => c.AccountId == accountId)
+ .ToListAsync();
+ }
+
+ public async Task> GetByTypeAsync(string type)
+ {
+ return await _context.Contacts
+ .Where(c => c.ContactType == type)
+ .ToListAsync();
+ }
+
+ public async Task GetByEmailAsync(string email)
+ {
+ return await _context.Contacts
+ .FirstOrDefaultAsync(c => c.Email == email);
+ }
+
+ public async Task GetByPhoneAsync(string phone)
+ {
+ return await _context.Contacts
+ .FirstOrDefaultAsync(c => c.PhoneNumber == phone);
+ }
+
+ public async Task<(IEnumerable 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 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 ExistsAsync(int id)
+ {
+ return await _context.Contacts.AnyAsync(c => c.Id == id);
+ }
+
+ public async Task EmailExistsAsync(string email, int? excludeId = null)
+ {
+ return await _context.Contacts.AnyAsync(c =>
+ c.Email == email &&
+ (excludeId == null || c.Id != excludeId));
+ }
+
+ public async Task PhoneExistsAsync(string phone, int? excludeId = null)
+ {
+ return await _context.Contacts.AnyAsync(c =>
+ c.PhoneNumber == phone &&
+ (excludeId == null || c.Id != excludeId));
+ }
+
+ public async Task CountAsync()
+ {
+ return await _context.Contacts.CountAsync();
+ }
+
+ public async Task CountByAccountIdAsync(int accountId)
+ {
+ return await _context.Contacts.CountAsync(c => c.AccountId == accountId);
+ }
+
+ public async Task CountByTypeAsync(string type)
+ {
+ return await _context.Contacts.CountAsync(c => c.ContactType == type);
+ }
+ }
+}
diff --git a/SeaHaven.DataServices/Implementation/DispatchDataService.cs b/SeaHaven.DataServices/Implementation/DispatchDataService.cs
new file mode 100644
index 0000000..b4472a8
--- /dev/null
+++ b/SeaHaven.DataServices/Implementation/DispatchDataService.cs
@@ -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 GetByIdAsync(int id)
+ {
+ return await _context.Dispatches.FindAsync(id);
+ }
+
+ public async Task GetByIdWithDetailsAsync(int id)
+ {
+ return await _context.Dispatches
+ .Include(d => d.Vendor)
+ .FirstOrDefaultAsync(d => d.Id == id);
+ }
+
+ public async Task> GetAllAsync()
+ {
+ return await _context.Dispatches.ToListAsync();
+ }
+
+ public async Task> GetByVendorIdAsync(int vendorId)
+ {
+ return await _context.Dispatches
+ .Where(d => d.VendorId == vendorId)
+ .ToListAsync();
+ }
+
+ public async Task> GetByStatusAsync(string status)
+ {
+ return await _context.Dispatches
+ .Where(d => d.Status == status)
+ .ToListAsync();
+ }
+
+ public async Task<(IEnumerable 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 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 ExistsAsync(int id)
+ {
+ return await _context.Dispatches.AnyAsync(d => d.Id == id);
+ }
+
+ public async Task GenerateNextDispatchNumberAsync()
+ {
+ return await Task.FromResult($"DISP-{DateTime.Now:yyyyMMdd}-{new Random().Next(1000, 9999)}");
+ }
+
+ public async Task CountAsync()
+ {
+ return await _context.Dispatches.CountAsync();
+ }
+ }
+}
diff --git a/SeaHaven.DataServices/Implementation/EmployeeDataService.cs b/SeaHaven.DataServices/Implementation/EmployeeDataService.cs
new file mode 100644
index 0000000..aadcd48
--- /dev/null
+++ b/SeaHaven.DataServices/Implementation/EmployeeDataService.cs
@@ -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 GetByIdAsync(int id)
+ {
+ return await _context.Employees.FindAsync(id);
+ }
+
+ public async Task 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> GetAllAsync()
+ {
+ return await _context.Employees.ToListAsync();
+ }
+
+ public async Task<(IEnumerable 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 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 ExistsAsync(int id)
+ {
+ return await _context.Employees.AnyAsync(e => e.Id == id);
+ }
+
+ public async Task CountAsync()
+ {
+ return await _context.Employees.CountAsync();
+ }
+ }
+}
diff --git a/SeaHaven.DataServices/Implementation/FollowUpDataService.cs b/SeaHaven.DataServices/Implementation/FollowUpDataService.cs
new file mode 100644
index 0000000..f07f3c5
--- /dev/null
+++ b/SeaHaven.DataServices/Implementation/FollowUpDataService.cs
@@ -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 GetByIdAsync(int id)
+ {
+ return await _context.FollowUps.FindAsync(id);
+ }
+
+ public async Task GetByIdWithDetailsAsync(int id)
+ {
+ return await _context.FollowUps
+ .FirstOrDefaultAsync(f => f.Id == id);
+ }
+
+ public async Task> GetAllAsync()
+ {
+ return await _context.FollowUps.ToListAsync();
+ }
+
+ public async Task> GetByWorkOrderIdAsync(int workOrderId)
+ {
+ return await _context.FollowUps
+ .Where(f => f.WorkOrderId == workOrderId)
+ .ToListAsync();
+ }
+
+ public async Task> GetByUserIdAsync(string userId)
+ {
+ return await _context.FollowUps
+ .Where(f => f.createdby == userId)
+ .ToListAsync();
+ }
+
+ public async Task<(IEnumerable 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 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 ExistsAsync(int id)
+ {
+ return await _context.FollowUps.AnyAsync(f => f.Id == id);
+ }
+
+ public async Task CountAsync()
+ {
+ return await _context.FollowUps.CountAsync();
+ }
+ }
+}
diff --git a/SeaHaven.DataServices/Implementation/LocationDataService.cs b/SeaHaven.DataServices/Implementation/LocationDataService.cs
new file mode 100644
index 0000000..43badbb
--- /dev/null
+++ b/SeaHaven.DataServices/Implementation/LocationDataService.cs
@@ -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 GetByIdAsync(int id)
+ {
+ return await _context.Locations.FindAsync(id);
+ }
+
+ public async Task GetByIdWithDetailsAsync(int id)
+ {
+ return await _context.Locations
+ .FirstOrDefaultAsync(l => l.Id == id);
+ }
+
+ public async Task> GetAllAsync()
+ {
+ return await _context.Locations.ToListAsync();
+ }
+
+ public async Task> 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 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 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 ExistsAsync(int id)
+ {
+ return await _context.Locations.AnyAsync(l => l.Id == id);
+ }
+
+ public async Task CountAsync()
+ {
+ return await _context.Locations.CountAsync();
+ }
+ }
+}
diff --git a/SeaHaven.DataServices/Implementation/PMScheduleDataService.cs b/SeaHaven.DataServices/Implementation/PMScheduleDataService.cs
new file mode 100644
index 0000000..90b3d27
--- /dev/null
+++ b/SeaHaven.DataServices/Implementation/PMScheduleDataService.cs
@@ -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 GetByIdAsync(int id)
+ {
+ return await _context.PMSchedules.FindAsync(id);
+ }
+
+ public async Task GetByIdWithDetailsAsync(int id)
+ {
+ return await _context.PMSchedules
+ .FirstOrDefaultAsync(p => p.Id == id);
+ }
+
+ public async Task> GetAllAsync()
+ {
+ return await _context.PMSchedules.ToListAsync();
+ }
+
+ public async Task> GetByLocationIdAsync(int locationId)
+ {
+ return await _context.PMSchedules
+ .Where(p => p.LocationId == locationId)
+ .ToListAsync();
+ }
+
+ public async Task<(IEnumerable 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 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 ExistsAsync(int id)
+ {
+ return await _context.PMSchedules.AnyAsync(p => p.Id == id);
+ }
+
+ public async Task CountAsync()
+ {
+ return await _context.PMSchedules.CountAsync();
+ }
+ }
+}
diff --git a/SeaHaven.DataServices/Implementation/VendorDataService.cs b/SeaHaven.DataServices/Implementation/VendorDataService.cs
new file mode 100644
index 0000000..416ad31
--- /dev/null
+++ b/SeaHaven.DataServices/Implementation/VendorDataService.cs
@@ -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 GetByIdAsync(int id)
+ {
+ return await _context.Vendors.FindAsync(id);
+ }
+
+ public async Task GetByIdWithDetailsAsync(int id)
+ {
+ return await _context.Vendors
+ .FirstOrDefaultAsync(v => v.Id == id);
+ }
+
+ public async Task> GetAllAsync()
+ {
+ return await _context.Vendors.ToListAsync();
+ }
+
+ public async Task<(IEnumerable 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 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 ExistsAsync(int id)
+ {
+ return await _context.Vendors.AnyAsync(v => v.Id == id);
+ }
+
+ public async Task GetActiveTokenAsync(int vendorId)
+ {
+ return await _context.VendorAccessTokens
+ .FirstOrDefaultAsync(t => t.VendorId == vendorId);
+ }
+
+ public async Task GetTokenByStringAsync(string token)
+ {
+ return await _context.VendorAccessTokens
+ .FirstOrDefaultAsync(t => t.Token == token);
+ }
+
+ public async Task CountAsync()
+ {
+ return await _context.Vendors.CountAsync();
+ }
+ }
+}
diff --git a/SeaHaven.DataServices/Implementation/WorkOrderDataService.cs b/SeaHaven.DataServices/Implementation/WorkOrderDataService.cs
new file mode 100644
index 0000000..1f6667e
--- /dev/null
+++ b/SeaHaven.DataServices/Implementation/WorkOrderDataService.cs
@@ -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 GetByIdAsync(int id)
+ {
+ return await _context.workOrders.FindAsync(id);
+ }
+
+ public async Task GetByIdWithDetailsAsync(int id)
+ {
+ return await _context.workOrders
+ .Include(w => w.Locations)
+ .Include(w => w.AssignToUser)
+ .FirstOrDefaultAsync(w => w.Id == id);
+ }
+
+ public async Task GetByInternalNumberAsync(string internalNumber)
+ {
+ return await _context.workOrders
+ .FirstOrDefaultAsync(w => w.InternalWONumber == internalNumber);
+ }
+
+ public async Task> GetAllAsync()
+ {
+ return await _context.workOrders.ToListAsync();
+ }
+
+ public async Task> GetByLocationIdAsync(int locationId)
+ {
+ return await _context.workOrders
+ .Where(w => w.LocationId == locationId)
+ .ToListAsync();
+ }
+
+ public async Task> GetByAssigneeAsync(string assignee)
+ {
+ return await _context.workOrders
+ .Where(w => w.AssignTo == assignee)
+ .ToListAsync();
+ }
+
+ public async Task> GetByStatusAsync(string status)
+ {
+ return await _context.workOrders
+ .Where(w => w.Status == status)
+ .ToListAsync();
+ }
+
+ public async Task> GetByPriorityAsync(string priority)
+ {
+ return await _context.workOrders
+ .Where(w => w.Priority == priority)
+ .ToListAsync();
+ }
+
+ public async Task<(IEnumerable 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 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> GetWorkOrderContactsAsync(int workOrderId)
+ {
+ return await _context.WorkOrderContacts
+ .Where(wc => wc.WorkorderId == workOrderId)
+ .ToListAsync();
+ }
+
+ public async Task> GetWorkOrderCategoriesAsync(int workOrderId)
+ {
+ return await _context.workOrderCategories
+ .Where(wc => wc.WorkorderId == workOrderId)
+ .ToListAsync();
+ }
+
+ public async Task> GetWorkOrderAttachmentsAsync(int workOrderId)
+ {
+ return await _context.workOrderAttachments
+ .Where(wa => wa.WorkorderId == workOrderId)
+ .ToListAsync();
+ }
+
+ public async Task> GetWorkOrderCommentsAsync(int workOrderId)
+ {
+ return await _context.Comments
+ .Where(c => c.WorkerOrderId == workOrderId)
+ .ToListAsync();
+ }
+
+ public async Task ExistsAsync(int id)
+ {
+ return await _context.workOrders.AnyAsync(w => w.Id == id);
+ }
+
+ public async Task InternalNumberExistsAsync(string internalNumber, int? excludeId = null)
+ {
+ return await _context.workOrders.AnyAsync(w =>
+ w.InternalWONumber == internalNumber &&
+ (excludeId == null || w.Id != excludeId));
+ }
+
+ public async Task GenerateNextInternalWONumberAsync()
+ {
+ return await Task.FromResult($"WO-{DateTime.Now:yyyyMMdd}-{new Random().Next(1000, 9999)}");
+ }
+
+ public async Task CountAsync()
+ {
+ return await _context.workOrders.CountAsync();
+ }
+
+ public async Task CountByStatusAsync(string status)
+ {
+ return await _context.workOrders.CountAsync(w => w.Status == status);
+ }
+
+ public async Task CountByLocationAsync(int locationId)
+ {
+ return await _context.workOrders.CountAsync(w => w.LocationId == locationId);
+ }
+ }
+}
diff --git a/SeaHaven.DataServices/Interfaces/IAccountDataService.cs b/SeaHaven.DataServices/Interfaces/IAccountDataService.cs
new file mode 100644
index 0000000..af0c8f5
--- /dev/null
+++ b/SeaHaven.DataServices/Interfaces/IAccountDataService.cs
@@ -0,0 +1,17 @@
+using Data.SeaHavenIndustries;
+
+namespace SeaHaven.DataServices.Interfaces
+{
+ public interface IAccountDataService
+ {
+ Task GetByIdAsync(int id);
+ Task GetByIdWithDetailsAsync(int id);
+ Task> GetAllAsync();
+ Task<(IEnumerable Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null);
+ Task AddAsync(Accounts account);
+ Task UpdateAsync(Accounts account);
+ Task DeleteAsync(int id);
+ Task ExistsAsync(int id);
+ Task CountAsync();
+ }
+}
diff --git a/SeaHaven.DataServices/Interfaces/IAssetDataService.cs b/SeaHaven.DataServices/Interfaces/IAssetDataService.cs
new file mode 100644
index 0000000..11df734
--- /dev/null
+++ b/SeaHaven.DataServices/Interfaces/IAssetDataService.cs
@@ -0,0 +1,18 @@
+using Data.SeaHavenIndustries;
+
+namespace SeaHaven.DataServices.Interfaces
+{
+ public interface IAssetDataService
+ {
+ Task GetByIdAsync(int id);
+ Task GetByIdWithDetailsAsync(int id);
+ Task> GetAllAsync();
+ Task> GetByLocationIdAsync(int locationId);
+ Task<(IEnumerable Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null);
+ Task AddAsync(Assets asset);
+ Task UpdateAsync(Assets asset);
+ Task DeleteAsync(int id);
+ Task ExistsAsync(int id);
+ Task CountAsync();
+ }
+}
diff --git a/SeaHaven.DataServices/Interfaces/ICategoryDataService.cs b/SeaHaven.DataServices/Interfaces/ICategoryDataService.cs
new file mode 100644
index 0000000..218cf0f
--- /dev/null
+++ b/SeaHaven.DataServices/Interfaces/ICategoryDataService.cs
@@ -0,0 +1,14 @@
+using Data.SeaHavenIndustries;
+
+namespace SeaHaven.DataServices.Interfaces
+{
+ public interface ICategoryDataService
+ {
+ Task GetByIdAsync(int id);
+ Task> GetAllAsync();
+ Task AddAsync(Category category);
+ Task UpdateAsync(Category category);
+ Task DeleteAsync(int id);
+ Task ExistsAsync(int id);
+ }
+}
diff --git a/SeaHaven.DataServices/Interfaces/IContactDataService.cs b/SeaHaven.DataServices/Interfaces/IContactDataService.cs
new file mode 100644
index 0000000..113d959
--- /dev/null
+++ b/SeaHaven.DataServices/Interfaces/IContactDataService.cs
@@ -0,0 +1,42 @@
+using Data.SeaHavenIndustries;
+
+namespace SeaHaven.DataServices.Interfaces
+{
+ ///
+ /// Data service interface for Contact entity
+ /// Handles all data access operations for Contacts
+ ///
+ public interface IContactDataService
+ {
+ // Query operations
+ Task GetByIdAsync(int id);
+ Task GetByIdWithDetailsAsync(int id);
+ Task> GetAllAsync();
+ Task> GetByAccountIdAsync(int accountId);
+ Task> GetByTypeAsync(string type);
+ Task GetByEmailAsync(string email);
+ Task GetByPhoneAsync(string phone);
+
+ // Paged results
+ Task<(IEnumerable Items, int TotalCount)> GetPagedAsync(
+ int page,
+ int pageSize,
+ string? search = null,
+ string? type = null);
+
+ // Command operations
+ Task AddAsync(Contacts contact);
+ Task UpdateAsync(Contacts contact);
+ Task DeleteAsync(int id);
+
+ // Validation operations
+ Task ExistsAsync(int id);
+ Task EmailExistsAsync(string email, int? excludeId = null);
+ Task PhoneExistsAsync(string phone, int? excludeId = null);
+
+ // Count operations
+ Task CountAsync();
+ Task CountByAccountIdAsync(int accountId);
+ Task CountByTypeAsync(string type);
+ }
+}
diff --git a/SeaHaven.DataServices/Interfaces/IDispatchDataService.cs b/SeaHaven.DataServices/Interfaces/IDispatchDataService.cs
new file mode 100644
index 0000000..32ec4e0
--- /dev/null
+++ b/SeaHaven.DataServices/Interfaces/IDispatchDataService.cs
@@ -0,0 +1,20 @@
+using Data.SeaHavenIndustries;
+
+namespace SeaHaven.DataServices.Interfaces
+{
+ public interface IDispatchDataService
+ {
+ Task GetByIdAsync(int id);
+ Task GetByIdWithDetailsAsync(int id);
+ Task> GetAllAsync();
+ Task> GetByVendorIdAsync(int vendorId);
+ Task> GetByStatusAsync(string status);
+ Task<(IEnumerable Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null, string? status = null);
+ Task AddAsync(Dispatch dispatch);
+ Task UpdateAsync(Dispatch dispatch);
+ Task DeleteAsync(int id);
+ Task ExistsAsync(int id);
+ Task GenerateNextDispatchNumberAsync();
+ Task CountAsync();
+ }
+}
diff --git a/SeaHaven.DataServices/Interfaces/IEmployeeDataService.cs b/SeaHaven.DataServices/Interfaces/IEmployeeDataService.cs
new file mode 100644
index 0000000..a743d0e
--- /dev/null
+++ b/SeaHaven.DataServices/Interfaces/IEmployeeDataService.cs
@@ -0,0 +1,17 @@
+using Data.SeaHavenIndustries;
+
+namespace SeaHaven.DataServices.Interfaces
+{
+ public interface IEmployeeDataService
+ {
+ Task GetByIdAsync(int id);
+ Task GetByIdWithDetailsAsync(int id);
+ Task> GetAllAsync();
+ Task<(IEnumerable Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null);
+ Task AddAsync(Employee employee);
+ Task UpdateAsync(Employee employee);
+ Task DeleteAsync(int id);
+ Task ExistsAsync(int id);
+ Task CountAsync();
+ }
+}
diff --git a/SeaHaven.DataServices/Interfaces/IFollowUpDataService.cs b/SeaHaven.DataServices/Interfaces/IFollowUpDataService.cs
new file mode 100644
index 0000000..c8b8df3
--- /dev/null
+++ b/SeaHaven.DataServices/Interfaces/IFollowUpDataService.cs
@@ -0,0 +1,19 @@
+using Data.SeaHavenIndustries;
+
+namespace SeaHaven.DataServices.Interfaces
+{
+ public interface IFollowUpDataService
+ {
+ Task GetByIdAsync(int id);
+ Task GetByIdWithDetailsAsync(int id);
+ Task> GetAllAsync();
+ Task> GetByWorkOrderIdAsync(int workOrderId);
+ Task> GetByUserIdAsync(string userId);
+ Task<(IEnumerable Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null, string? status = null);
+ Task AddAsync(FollowUps followUp);
+ Task UpdateAsync(FollowUps followUp);
+ Task DeleteAsync(int id);
+ Task ExistsAsync(int id);
+ Task CountAsync();
+ }
+}
diff --git a/SeaHaven.DataServices/Interfaces/ILocationDataService.cs b/SeaHaven.DataServices/Interfaces/ILocationDataService.cs
new file mode 100644
index 0000000..6f4d90a
--- /dev/null
+++ b/SeaHaven.DataServices/Interfaces/ILocationDataService.cs
@@ -0,0 +1,18 @@
+using Data.SeaHavenIndustries;
+
+namespace SeaHaven.DataServices.Interfaces
+{
+ public interface ILocationDataService
+ {
+ Task GetByIdAsync(int id);
+ Task GetByIdWithDetailsAsync(int id);
+ Task> GetAllAsync();
+ Task> GetByAccountIdAsync(int accountId);
+ Task<(IEnumerable Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null);
+ Task AddAsync(Locations location);
+ Task UpdateAsync(Locations location);
+ Task DeleteAsync(int id);
+ Task ExistsAsync(int id);
+ Task CountAsync();
+ }
+}
diff --git a/SeaHaven.DataServices/Interfaces/IPMScheduleDataService.cs b/SeaHaven.DataServices/Interfaces/IPMScheduleDataService.cs
new file mode 100644
index 0000000..f655777
--- /dev/null
+++ b/SeaHaven.DataServices/Interfaces/IPMScheduleDataService.cs
@@ -0,0 +1,18 @@
+using Data.SeaHavenIndustries;
+
+namespace SeaHaven.DataServices.Interfaces
+{
+ public interface IPMScheduleDataService
+ {
+ Task GetByIdAsync(int id);
+ Task GetByIdWithDetailsAsync(int id);
+ Task> GetAllAsync();
+ Task> GetByLocationIdAsync(int locationId);
+ Task<(IEnumerable Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null);
+ Task AddAsync(PMSchedules pmSchedule);
+ Task UpdateAsync(PMSchedules pmSchedule);
+ Task DeleteAsync(int id);
+ Task ExistsAsync(int id);
+ Task CountAsync();
+ }
+}
diff --git a/SeaHaven.DataServices/Interfaces/IVendorDataService.cs b/SeaHaven.DataServices/Interfaces/IVendorDataService.cs
new file mode 100644
index 0000000..6458b4f
--- /dev/null
+++ b/SeaHaven.DataServices/Interfaces/IVendorDataService.cs
@@ -0,0 +1,19 @@
+using Data.SeaHavenIndustries;
+
+namespace SeaHaven.DataServices.Interfaces
+{
+ public interface IVendorDataService
+ {
+ Task GetByIdAsync(int id);
+ Task GetByIdWithDetailsAsync(int id);
+ Task> GetAllAsync();
+ Task<(IEnumerable Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null);
+ Task AddAsync(Vendor vendor);
+ Task UpdateAsync(Vendor vendor);
+ Task DeleteAsync(int id);
+ Task ExistsAsync(int id);
+ Task GetActiveTokenAsync(int vendorId);
+ Task GetTokenByStringAsync(string token);
+ Task CountAsync();
+ }
+}
diff --git a/SeaHaven.DataServices/Interfaces/IWorkOrderDataService.cs b/SeaHaven.DataServices/Interfaces/IWorkOrderDataService.cs
new file mode 100644
index 0000000..1e70a27
--- /dev/null
+++ b/SeaHaven.DataServices/Interfaces/IWorkOrderDataService.cs
@@ -0,0 +1,48 @@
+using Data.SeaHavenIndustries;
+
+namespace SeaHaven.DataServices.Interfaces
+{
+ public interface IWorkOrderDataService
+ {
+ // Query operations
+ Task GetByIdAsync(int id);
+ Task GetByIdWithDetailsAsync(int id);
+ Task GetByInternalNumberAsync(string internalNumber);
+ Task> GetAllAsync();
+ Task> GetByLocationIdAsync(int locationId);
+ Task> GetByAssigneeAsync(string assigneeId);
+ Task> GetByStatusAsync(string status);
+ Task> GetByPriorityAsync(string priority);
+
+ // Paged results
+ Task<(IEnumerable Items, int TotalCount)> GetPagedAsync(
+ int page,
+ int pageSize,
+ string? search = null,
+ string? status = null,
+ string? priority = null);
+
+ // Command operations
+ Task AddAsync(WorkOrder workOrder);
+ Task UpdateAsync(WorkOrder workOrder);
+ Task DeleteAsync(int id);
+
+ // Related entities
+ Task> GetWorkOrderContactsAsync(int workOrderId);
+ Task> GetWorkOrderCategoriesAsync(int workOrderId);
+ Task> GetWorkOrderAttachmentsAsync(int workOrderId);
+ Task> GetWorkOrderCommentsAsync(int workOrderId);
+
+ // Validation
+ Task ExistsAsync(int id);
+ Task InternalNumberExistsAsync(string internalNumber, int? excludeId = null);
+
+ // Internal number generation
+ Task GenerateNextInternalWONumberAsync();
+
+ // Statistics
+ Task CountAsync();
+ Task CountByStatusAsync(string status);
+ Task CountByLocationAsync(int locationId);
+ }
+}
diff --git a/SeaHaven.DataServices/SeaHaven.DataServices.csproj b/SeaHaven.DataServices/SeaHaven.DataServices.csproj
new file mode 100644
index 0000000..37cd429
--- /dev/null
+++ b/SeaHaven.DataServices/SeaHaven.DataServices.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SeaHaven.Services/DTOs/AccountDTOs.cs b/SeaHaven.Services/DTOs/AccountDTOs.cs
new file mode 100644
index 0000000..662a9ee
--- /dev/null
+++ b/SeaHaven.Services/DTOs/AccountDTOs.cs
@@ -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; }
+ }
+}
diff --git a/SeaHaven.Services/DTOs/AssetDTOs.cs b/SeaHaven.Services/DTOs/AssetDTOs.cs
new file mode 100644
index 0000000..cd8eb76
--- /dev/null
+++ b/SeaHaven.Services/DTOs/AssetDTOs.cs
@@ -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; }
+ }
+}
diff --git a/SeaHaven.Services/DTOs/ContactDTOs.cs b/SeaHaven.Services/DTOs/ContactDTOs.cs
new file mode 100644
index 0000000..ac7bb19
--- /dev/null
+++ b/SeaHaven.Services/DTOs/ContactDTOs.cs
@@ -0,0 +1,67 @@
+namespace SeaHaven.Services.DTOs
+{
+ ///
+ /// Contact DTO for responses
+ ///
+ 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; }
+ }
+
+ ///
+ /// DTO for creating a new contact
+ ///
+ 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; }
+ }
+
+ ///
+ /// DTO for updating an existing contact
+ ///
+ 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; }
+ }
+
+ ///
+ /// Paged result wrapper
+ ///
+ public class PagedResult
+ {
+ public IEnumerable Items { get; set; } = new List();
+ 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;
+ }
+}
diff --git a/SeaHaven.Services/DTOs/DispatchDTOs.cs b/SeaHaven.Services/DTOs/DispatchDTOs.cs
new file mode 100644
index 0000000..49a4a53
--- /dev/null
+++ b/SeaHaven.Services/DTOs/DispatchDTOs.cs
@@ -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; }
+ }
+}
diff --git a/SeaHaven.Services/DTOs/EmployeeDTOs.cs b/SeaHaven.Services/DTOs/EmployeeDTOs.cs
new file mode 100644
index 0000000..ca78e85
--- /dev/null
+++ b/SeaHaven.Services/DTOs/EmployeeDTOs.cs
@@ -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; }
+ }
+}
diff --git a/SeaHaven.Services/DTOs/FollowUpDTOs.cs b/SeaHaven.Services/DTOs/FollowUpDTOs.cs
new file mode 100644
index 0000000..23668a6
--- /dev/null
+++ b/SeaHaven.Services/DTOs/FollowUpDTOs.cs
@@ -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; }
+ }
+}
diff --git a/SeaHaven.Services/DTOs/LocationDTOs.cs b/SeaHaven.Services/DTOs/LocationDTOs.cs
new file mode 100644
index 0000000..2e33eb7
--- /dev/null
+++ b/SeaHaven.Services/DTOs/LocationDTOs.cs
@@ -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; }
+ }
+}
diff --git a/SeaHaven.Services/DTOs/PMScheduleDTOs.cs b/SeaHaven.Services/DTOs/PMScheduleDTOs.cs
new file mode 100644
index 0000000..7c0daf5
--- /dev/null
+++ b/SeaHaven.Services/DTOs/PMScheduleDTOs.cs
@@ -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; }
+ }
+}
diff --git a/SeaHaven.Services/DTOs/VendorDTOs.cs b/SeaHaven.Services/DTOs/VendorDTOs.cs
new file mode 100644
index 0000000..3a58934
--- /dev/null
+++ b/SeaHaven.Services/DTOs/VendorDTOs.cs
@@ -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; }
+ }
+}
diff --git a/SeaHaven.Services/DTOs/WorkOrderDTOs.cs b/SeaHaven.Services/DTOs/WorkOrderDTOs.cs
new file mode 100644
index 0000000..c5f9a0f
--- /dev/null
+++ b/SeaHaven.Services/DTOs/WorkOrderDTOs.cs
@@ -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; }
+ }
+}
diff --git a/SeaHaven.Services/Implementation/ContactService.cs b/SeaHaven.Services/Implementation/ContactService.cs
new file mode 100644
index 0000000..dcbcc35
--- /dev/null
+++ b/SeaHaven.Services/Implementation/ContactService.cs
@@ -0,0 +1,220 @@
+using Data.SeaHavenIndustries;
+using SeaHaven.DataServices.Interfaces;
+using SeaHaven.Services.DTOs;
+using SeaHaven.Services.Interfaces;
+
+namespace SeaHaven.Services.Implementation
+{
+ ///
+ /// Contact service implementation
+ /// Contains business logic, validation, and orchestration for Contact operations
+ ///
+ public class ContactService : IContactService
+ {
+ private readonly IContactDataService _contactDataService;
+
+ public ContactService(IContactDataService contactDataService)
+ {
+ _contactDataService = contactDataService;
+ }
+
+ // Query operations
+ public async Task GetContactByIdAsync(int id)
+ {
+ var contact = await _contactDataService.GetByIdAsync(id);
+ return contact == null ? null : MapToDTO(contact);
+ }
+
+ public async Task GetContactByIdWithDetailsAsync(int id)
+ {
+ var contact = await _contactDataService.GetByIdWithDetailsAsync(id);
+ return contact == null ? null : MapToDTO(contact);
+ }
+
+ public async Task> GetAllContactsAsync()
+ {
+ var contacts = await _contactDataService.GetAllAsync();
+ return contacts.Select(MapToDTO);
+ }
+
+ public async Task> GetContactsByAccountIdAsync(int accountId)
+ {
+ var contacts = await _contactDataService.GetByAccountIdAsync(accountId);
+ return contacts.Select(MapToDTO);
+ }
+
+ public async Task> GetContactsByTypeAsync(string type)
+ {
+ var contacts = await _contactDataService.GetByTypeAsync(type);
+ return contacts.Select(MapToDTO);
+ }
+
+ public async Task GetContactByEmailAsync(string email)
+ {
+ var contact = await _contactDataService.GetByEmailAsync(email);
+ return contact == null ? null : MapToDTO(contact);
+ }
+
+ // Paged results
+ public async Task> GetContactsPagedAsync(
+ int page,
+ int pageSize,
+ string? search = null,
+ string? type = null)
+ {
+ var (items, totalCount) = await _contactDataService.GetPagedAsync(page, pageSize, search, type);
+
+ return new PagedResult
+ {
+ Items = items.Select(MapToDTO),
+ TotalCount = totalCount,
+ Page = page,
+ PageSize = pageSize
+ };
+ }
+
+ // Command operations
+ public async Task 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 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 ContactExistsAsync(int id)
+ {
+ return await _contactDataService.ExistsAsync(id);
+ }
+
+ public async Task EmailExistsAsync(string email, int? excludeId = null)
+ {
+ return await _contactDataService.EmailExistsAsync(email, excludeId);
+ }
+
+ public async Task PhoneExistsAsync(string phone, int? excludeId = null)
+ {
+ return await _contactDataService.PhoneExistsAsync(phone, excludeId);
+ }
+
+ // Statistics
+ public async Task GetTotalContactCountAsync()
+ {
+ return await _contactDataService.CountAsync();
+ }
+
+ public async Task GetContactCountByAccountAsync(int accountId)
+ {
+ return await _contactDataService.CountByAccountIdAsync(accountId);
+ }
+
+ public async Task> 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
+ };
+ }
+ }
+}
diff --git a/SeaHaven.Services/Implementation/WorkOrderService.cs b/SeaHaven.Services/Implementation/WorkOrderService.cs
new file mode 100644
index 0000000..17b5f64
--- /dev/null
+++ b/SeaHaven.Services/Implementation/WorkOrderService.cs
@@ -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 GetWorkOrderByIdAsync(int id)
+ {
+ var workOrder = await _workOrderDataService.GetByIdAsync(id);
+ return workOrder == null ? null : MapToDTO(workOrder);
+ }
+
+ public async Task GetWorkOrderByIdWithDetailsAsync(int id)
+ {
+ var workOrder = await _workOrderDataService.GetByIdWithDetailsAsync(id);
+ return workOrder == null ? null : MapToDTO(workOrder);
+ }
+
+ public async Task> GetAllWorkOrdersAsync()
+ {
+ var workOrders = await _workOrderDataService.GetAllAsync();
+ return workOrders.Select(MapToDTO);
+ }
+
+ public async Task> 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