From 64adcae4ed3d9e4e1e5525baa76b9a7d862af941 Mon Sep 17 00:00:00 2001 From: npalOmega Date: Fri, 24 Apr 2026 12:12:16 -0500 Subject: [PATCH 01/12] fixes for Dev changes --- .../Controllers/AuthenticationController.cs | 53 +++-- Api.SeaHavenIndustries/Program.cs | 98 ++++----- .../appsettings.Development.json | 26 ++- .../appsettings.Production.json | 20 ++ Api.SeaHavenIndustries/appsettings.json | 19 +- .../aws-beanstalk-tools-defaults.json | 21 +- Api.SeaHavenIndustries/db.txt | 202 +++++++++++++++++- 7 files changed, 333 insertions(+), 106 deletions(-) create mode 100644 Api.SeaHavenIndustries/appsettings.Production.json diff --git a/Api.SeaHavenIndustries/Controllers/AuthenticationController.cs b/Api.SeaHavenIndustries/Controllers/AuthenticationController.cs index 346cde0..013cbfe 100644 --- a/Api.SeaHavenIndustries/Controllers/AuthenticationController.cs +++ b/Api.SeaHavenIndustries/Controllers/AuthenticationController.cs @@ -34,36 +34,45 @@ namespace Api.SeaHavenIndustries.Controllers [Route("login")] public async Task Login([FromBody] LoginModel model) { - var user = await _userManager.FindByNameAsync(model.Username ?? ""); - if (user != null && user.IsDeleted != true && await _userManager.CheckPasswordAsync(user, model.Password ?? "")) + try { - // Standard login without 2FA - var userRoles = await _userManager.GetRolesAsync(user); - var authClaims = new List + var user = await _userManager.FindByNameAsync(model.Username ?? ""); + if (user != null && user.IsDeleted != true && await _userManager.CheckPasswordAsync(user, model.Password ?? "")) + { + // Standard login without 2FA + var userRoles = await _userManager.GetRolesAsync(user); + var authClaims = new List { new Claim(ClaimTypes.Name, user.UserName ?? ""), new Claim(ClaimTypes.NameIdentifier, user.Id), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) }; - foreach (var userRole in userRoles) - { - authClaims.Add(new Claim(ClaimTypes.Role, userRole)); + foreach (var userRole in userRoles) + { + authClaims.Add(new Claim(ClaimTypes.Role, userRole)); + } + var token = GetToken(authClaims); + return Ok(new + { + token = new JwtSecurityTokenHandler().WriteToken(token), + expiration = token.ValidTo, + email = user.Email, + userRoles = userRoles.FirstOrDefault(), + phoneNumber = user.PhoneNumber, + fullname = user.FirstName + " " + user.LastName, + id = user.Id + }); } - var token = GetToken(authClaims); - return Ok(new - { - token = new JwtSecurityTokenHandler().WriteToken(token), - expiration = token.ValidTo, - email = user.Email, - userRoles = userRoles.FirstOrDefault(), - phoneNumber = user.PhoneNumber, - fullname = user.FirstName + " " + user.LastName, - id = user.Id - }); - } - // Invalid credentials - return Unauthorized(); + // Invalid credentials + return Unauthorized(); + } + catch (Exception ex) + { + + return StatusCode(500, ex.Message); + } + } [Route("ChangePassword")] diff --git a/Api.SeaHavenIndustries/Program.cs b/Api.SeaHavenIndustries/Program.cs index 82edb85..1dfb93f 100644 --- a/Api.SeaHavenIndustries/Program.cs +++ b/Api.SeaHavenIndustries/Program.cs @@ -111,60 +111,60 @@ app.UseAuthorization(); app.MapControllers(); -if (app.Environment.IsDevelopment()) -{ - using var scope = app.Services.CreateScope(); - var userManager = scope.ServiceProvider.GetRequiredService>(); - var roleManager = scope.ServiceProvider.GetRequiredService>(); +//if (app.Environment.IsDevelopment()) +//{ +// using var scope = app.Services.CreateScope(); +// var userManager = scope.ServiceProvider.GetRequiredService>(); +// var roleManager = scope.ServiceProvider.GetRequiredService>(); - if (!await roleManager.RoleExistsAsync("Admin")) - await roleManager.CreateAsync(new IdentityRole("Admin")); - if (!await roleManager.RoleExistsAsync("Dispatcher")) - await roleManager.CreateAsync(new IdentityRole("Dispatcher")); - if (!await roleManager.RoleExistsAsync("Manager")) - await roleManager.CreateAsync(new IdentityRole("Manager")); +// if (!await roleManager.RoleExistsAsync("Admin")) +// await roleManager.CreateAsync(new IdentityRole("Admin")); +// if (!await roleManager.RoleExistsAsync("Dispatcher")) +// await roleManager.CreateAsync(new IdentityRole("Dispatcher")); +// if (!await roleManager.RoleExistsAsync("Manager")) +// await roleManager.CreateAsync(new IdentityRole("Manager")); - var adminEmail = "admin@seahavenind.com"; - if (await userManager.FindByNameAsync(adminEmail) == null) - { - var admin = new ApplicationUser - { - UserName = adminEmail, - Email = adminEmail, - FirstName = "Admin", - LastName = "User", - EmailConfirmed = true, - UniqueName = "Active", - CreatedDate = DateTime.Now - }; - var result = await userManager.CreateAsync(admin, "Admin@123"); - if (result.Succeeded) - await userManager.AddToRoleAsync(admin, "Admin"); - } +// var adminEmail = "admin@seahavenind.com"; +// if (await userManager.FindByNameAsync(adminEmail) == null) +// { +// var admin = new ApplicationUser +// { +// UserName = adminEmail, +// Email = adminEmail, +// FirstName = "Admin", +// LastName = "User", +// EmailConfirmed = true, +// UniqueName = "Active", +// CreatedDate = DateTime.Now +// }; +// var result = await userManager.CreateAsync(admin, "Admin@123"); +// if (result.Succeeded) +// await userManager.AddToRoleAsync(admin, "Admin"); +// } - var db = scope.ServiceProvider.GetRequiredService(); - if (!db.DropdownOptions.Any()) - { - var trades = new[] { "Plumbing", "Electrical", "HVAC", "Fire/Life Safety", "General", "Roofing", "Janitorial", "Painting", "Locksmith", "Carpentry" }; - for (int i = 0; i < trades.Length; i++) - db.DropdownOptions.Add(new DropdownOption { Category = "Trade", Value = trades[i], SortOrder = i }); +// var db = scope.ServiceProvider.GetRequiredService(); +// if (!db.DropdownOptions.Any()) +// { +// var trades = new[] { "Plumbing", "Electrical", "HVAC", "Fire/Life Safety", "General", "Roofing", "Janitorial", "Painting", "Locksmith", "Carpentry" }; +// for (int i = 0; i < trades.Length; i++) +// db.DropdownOptions.Add(new DropdownOption { Category = "Trade", Value = trades[i], SortOrder = i }); - var subTrades = new (string trade, string sub)[] { - ("Plumbing", "Drain Cleaning"), ("Plumbing", "Pipe Repair"), ("Plumbing", "Fixture Install"), ("Plumbing", "Backflow Testing"), - ("Electrical", "Lighting"), ("Electrical", "Panel/Breaker"), ("Electrical", "Wiring"), ("Electrical", "Generator"), - ("HVAC", "AC Repair"), ("HVAC", "Heating"), ("HVAC", "Ductwork"), ("HVAC", "Controls/BMS"), - ("Fire/Life Safety", "Sprinkler"), ("Fire/Life Safety", "Fire Alarm"), ("Fire/Life Safety", "Extinguisher"), ("Fire/Life Safety", "Fire Pump"), - ("General", "Doors"), ("General", "Flooring"), ("General", "Drywall"), ("General", "Concrete"), - }; - for (int i = 0; i < subTrades.Length; i++) - db.DropdownOptions.Add(new DropdownOption { Category = "SubTrade", Value = subTrades[i].sub, ParentValue = subTrades[i].trade, SortOrder = i }); +// var subTrades = new (string trade, string sub)[] { +// ("Plumbing", "Drain Cleaning"), ("Plumbing", "Pipe Repair"), ("Plumbing", "Fixture Install"), ("Plumbing", "Backflow Testing"), +// ("Electrical", "Lighting"), ("Electrical", "Panel/Breaker"), ("Electrical", "Wiring"), ("Electrical", "Generator"), +// ("HVAC", "AC Repair"), ("HVAC", "Heating"), ("HVAC", "Ductwork"), ("HVAC", "Controls/BMS"), +// ("Fire/Life Safety", "Sprinkler"), ("Fire/Life Safety", "Fire Alarm"), ("Fire/Life Safety", "Extinguisher"), ("Fire/Life Safety", "Fire Pump"), +// ("General", "Doors"), ("General", "Flooring"), ("General", "Drywall"), ("General", "Concrete"), +// }; +// for (int i = 0; i < subTrades.Length; i++) +// db.DropdownOptions.Add(new DropdownOption { Category = "SubTrade", Value = subTrades[i].sub, ParentValue = subTrades[i].trade, SortOrder = i }); - var problems = new[] { "Leak", "No Power", "Not Cooling", "Not Heating", "Clogged", "Broken/Damaged", "Alarm Fault", "Preventive Maintenance", "Inspection", "Installation", "Other" }; - for (int i = 0; i < problems.Length; i++) - db.DropdownOptions.Add(new DropdownOption { Category = "Problem", Value = problems[i], SortOrder = i }); +// var problems = new[] { "Leak", "No Power", "Not Cooling", "Not Heating", "Clogged", "Broken/Damaged", "Alarm Fault", "Preventive Maintenance", "Inspection", "Installation", "Other" }; +// for (int i = 0; i < problems.Length; i++) +// db.DropdownOptions.Add(new DropdownOption { Category = "Problem", Value = problems[i], SortOrder = i }); - await db.SaveChangesAsync(); - } -} +// await db.SaveChangesAsync(); +// } +//} app.Run(); diff --git a/Api.SeaHavenIndustries/appsettings.Development.json b/Api.SeaHavenIndustries/appsettings.Development.json index d5cb32d..d2f99f1 100644 --- a/Api.SeaHavenIndustries/appsettings.Development.json +++ b/Api.SeaHavenIndustries/appsettings.Development.json @@ -1,6 +1,12 @@ { "ConnectionStrings": { - "DefaultConnection": "Server=localhost,1433;Database=SeahavenIndustries;User Id=sa;Password=Local@Dev2026!;TrustServerCertificate=True;MultipleActiveResultSets=true" + //"DefaultConnection": "Server=DESKTOP-64LC14O\SQLEXPRESS;Database=SeahavenIndustries;TrustServerCertificate=True;Trusted_Connection=True;MultipleActiveResultSets=true", + + // Test database + //"DefaultConnection": "Server=SQL5112.site4now.net;Initial Catalog=db_a7141e_seahavenindustries;User Id=db_a7141e_seahavenindustries_admin;Password=Abc123!@#;MultipleActiveResultSets=true" + + //Client Database + "DefaultConnection": "Server=SQL5112.site4now.net;Initial Catalog=db_a7141e_seahavenindustry;User Id=db_a7141e_seahavenindustry_admin;Password=Abc123!@#;MultipleActiveResultSets=true" }, "Logging": { "LogLevel": { @@ -9,20 +15,12 @@ } }, "SendGrid": { - "ApiKey": "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" + "ApiKey": "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" }, + "AllowedHosts": "*", "JWT": { - "ValidAudience": "http://localhost:3000", - "ValidIssuer": "https://localhost:7195", + "ValidAudience": "http://localhost:4200", + "ValidIssuer": "http://localhost:7195", "Secret": "JWTAuthenticationHIGHsecuredPasswordVVVp1OH7Xzyr" - }, - "FrontendBaseUrl": "http://localhost:3000", - "VendorPortal": { - "TokenLifetimeDays": 365 - }, - "Approvals": { - "UpliftTier1MaxUsd": 2500, - "Tier1Roles": [ "Admin", "Dispatcher" ], - "Tier2Roles": [ "Admin", "Manager" ] } -} +} \ No newline at end of file diff --git a/Api.SeaHavenIndustries/appsettings.Production.json b/Api.SeaHavenIndustries/appsettings.Production.json new file mode 100644 index 0000000..d83ffb1 --- /dev/null +++ b/Api.SeaHavenIndustries/appsettings.Production.json @@ -0,0 +1,20 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Server=SQL5112.site4now.net;Initial Catalog=db_a7141e_seahavenindustry;User Id=db_a7141e_seahavenindustry_admin;Password=Abc123!@#;MultipleActiveResultSets=true" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "SendGrid": { + "ApiKey": "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" + }, + "AllowedHosts": "*", + "JWT": { + "ValidAudience": "http://console.seahavenind.com", + "ValidIssuer": "http://console.seahavenind.com", + "Secret": "JWTAuthenticationHIGHsecuredPasswordVVVp1OH7Xzyr" + } +} diff --git a/Api.SeaHavenIndustries/appsettings.json b/Api.SeaHavenIndustries/appsettings.json index 2459448..d2f99f1 100644 --- a/Api.SeaHavenIndustries/appsettings.json +++ b/Api.SeaHavenIndustries/appsettings.json @@ -1,6 +1,12 @@ { "ConnectionStrings": { - "DefaultConnection": "" + //"DefaultConnection": "Server=DESKTOP-64LC14O\SQLEXPRESS;Database=SeahavenIndustries;TrustServerCertificate=True;Trusted_Connection=True;MultipleActiveResultSets=true", + + // Test database + //"DefaultConnection": "Server=SQL5112.site4now.net;Initial Catalog=db_a7141e_seahavenindustries;User Id=db_a7141e_seahavenindustries_admin;Password=Abc123!@#;MultipleActiveResultSets=true" + + //Client Database + "DefaultConnection": "Server=SQL5112.site4now.net;Initial Catalog=db_a7141e_seahavenindustry;User Id=db_a7141e_seahavenindustry_admin;Password=Abc123!@#;MultipleActiveResultSets=true" }, "Logging": { "LogLevel": { @@ -9,17 +15,12 @@ } }, "SendGrid": { - "ApiKey": "" + "ApiKey": "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" }, "AllowedHosts": "*", "JWT": { "ValidAudience": "http://localhost:4200", "ValidIssuer": "http://localhost:7195", - "Secret": "" - }, - "Approvals": { - "UpliftTier1MaxUsd": 2500, - "Tier1Roles": [ "Admin", "Dispatcher" ], - "Tier2Roles": [ "Admin", "Manager" ] + "Secret": "JWTAuthenticationHIGHsecuredPasswordVVVp1OH7Xzyr" } -} +} \ No newline at end of file diff --git a/Api.SeaHavenIndustries/aws-beanstalk-tools-defaults.json b/Api.SeaHavenIndustries/aws-beanstalk-tools-defaults.json index fab4ccf..9f79baf 100644 --- a/Api.SeaHavenIndustries/aws-beanstalk-tools-defaults.json +++ b/Api.SeaHavenIndustries/aws-beanstalk-tools-defaults.json @@ -1,13 +1,12 @@ - { - "region" : "us-east-1", - "configuration" : "Debug", - "framework" : "net8.0", - "self-contained" : true, - "application" : "SeaHavenIndustries", - "environment" : "SeaHavenIndustries-dev", - "enable-xray" : false, - "enhanced-health-type" : "basic", - "additional-options" : "", - "proxy-server" : "nginx" + "region": "us-east-1", + "configuration": "Debug", + "framework": "net8.0", + "self-contained": true, + "application": "SeaHavenIndustries", + "environment": "SeaHavenIndustries-dev", + "enable-xray": false, + "enhanced-health-type": "basic", + "additional-options": "", + "proxy-server": "nginx" } \ No newline at end of file diff --git a/Api.SeaHavenIndustries/db.txt b/Api.SeaHavenIndustries/db.txt index 20b8cca..db4cdcb 100644 --- a/Api.SeaHavenIndustries/db.txt +++ b/Api.SeaHavenIndustries/db.txt @@ -924,6 +924,206 @@ PRIMARY KEY CLUSTERED GO - +-- ================================================================== +-- DATABASE CHANGES APPLIED ON 2026-04-22 +-- Migration: Calendar Module + Missing Column Fixes +-- Applied by: AI Agent +-- ================================================================== + +-- ================================================================== +-- 1. EVENTS TABLE (Calendar Module) +-- Migration: 20260422021353_AddEventsTable +-- ================================================================== + +USE [db_a7141e_seahavenindustry] +GO + +IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Events]') AND type in (N'U')) +BEGIN + CREATE TABLE [dbo].[Events]( + [Id] [int] IDENTITY(1,1) NOT NULL, + + -- Basic Event Information + [Title] [nvarchar](100) NOT NULL, + [Description] [nvarchar](max) NULL, + [Location] [nvarchar](150) NULL, + + -- Related Record (Link to Accounts, Contacts, Work Orders, etc.) + [RecordID] [int] NULL, + [RecordName] [nvarchar](max) NULL, + [RecordTargetType] [nvarchar](max) NULL, + + -- Reminder & Display Settings + [ReminderMinutes] [int] NOT NULL CONSTRAINT [DF_Events_ReminderMinutes] DEFAULT ((15)), + [EventColor] [nvarchar](20) NOT NULL CONSTRAINT [DF_Events_EventColor] DEFAULT (N'primary'), + + -- Schedule Information + [StartDate] [datetime2](7) NOT NULL, + [StartTime] [nvarchar](10) NULL, + [EndDate] [datetime2](7) NOT NULL, + [EndTime] [nvarchar](10) NULL, + [AllDay] [bit] NOT NULL CONSTRAINT [DF_Events_AllDay] DEFAULT ((0)), + + -- Audit Fields (from FullAuditEntity) + [IsDeleted] [bit] NOT NULL CONSTRAINT [DF_Events_IsDeleted] DEFAULT ((0)), + [DeletionTime] [datetime2](7) NULL, + [createdby] [nvarchar](max) NULL, + [CreatedDate] [datetime2](7) NULL, + [lastmodifiedby] [nvarchar](max) NULL, + [LastModificationTime] [datetime2](7) NULL, + + CONSTRAINT [PK_Events] PRIMARY KEY CLUSTERED ([Id] ASC) + WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) + ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] +END +GO + +-- Create Index on StartDate for faster calendar queries +IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_Events_StartDate' AND object_id = OBJECT_ID('[dbo].[Events]')) +BEGIN + CREATE NONCLUSTERED INDEX [IX_Events_StartDate] ON [dbo].[Events] + ( + [StartDate] ASC + ) + WHERE ([IsDeleted] = 0) +END +GO + +-- Create Index on IsDeleted for soft delete queries +IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_Events_IsDeleted' AND object_id = OBJECT_ID('[dbo].[Events]')) +BEGIN + CREATE NONCLUSTERED INDEX [IX_Events_IsDeleted] ON [dbo].[Events] + ( + [IsDeleted] ASC + ) +END +GO + +-- ================================================================== +-- 2. WORKORDERS TABLE - MISSING COLUMNS +-- Migrations: 20260416212330, 20260416224015, 20260417142453 +-- ================================================================== + +-- Migration: 20260416224015_AddInternalWONumber +ALTER TABLE [dbo].[WorkOrders] +ADD [InternalWONumber] [nvarchar](max) NULL +GO + +-- Migration: 20260416212330_AddExternalSyncFields +ALTER TABLE [dbo].[WorkOrders] +ADD [ExternalWorkOrderId] [nvarchar](max) NULL, + [Source] [nvarchar](max) NULL +GO + +-- Migration: 20260417142453_AddDropdownOptionsAndWOFields +ALTER TABLE [dbo].[WorkOrders] +ADD [Problem] [nvarchar](max) NULL, + [Trade] [nvarchar](max) NULL, + [SubTrade] [nvarchar](max) NULL, + [VendorNTE] [decimal](18, 2) NULL, + [ScheduledDate] [datetime2](7) NULL, + [CompletedDate] [datetime2](7) NULL +GO + +-- ================================================================== +-- 3. COMMENTS TABLE - MISSING COLUMNS +-- Migrations: 20260416220656, 20260417171152 +-- ================================================================== + +-- Migration: 20260416220656_AddCommentTypeAndAuditLog +ALTER TABLE [dbo].[Comments] +ADD [CommentType] [nvarchar](max) NULL, + [Commenter] [nvarchar](max) NULL, + [RecordType] [nvarchar](max) NULL +GO + +-- Migration: 20260417171152_AddDispatchNumberAndCommentDispatchId +ALTER TABLE [dbo].[Comments] +ADD [DispatchId] [int] NULL +GO + +-- ================================================================== +-- 4. LOCATIONS TABLE - MISSING COLUMNS +-- Migration: 20260416210833_AddLocationFields +-- ================================================================== + +ALTER TABLE [dbo].[Locations] +ADD [Address1] [nvarchar](max) NULL, + [Address2] [nvarchar](max) NULL, + [City] [nvarchar](max) NULL, + [State] [nvarchar](max) NULL, + [Zip] [nvarchar](max) NULL, + [PhoneNumber] [nvarchar](max) NULL, + [Email] [nvarchar](max) NULL, + [Status] [nvarchar](max) NULL +GO + +-- ================================================================== +-- 5. MIGRATION HISTORY RECORDS +-- Marked these migrations as applied to sync with code +-- ================================================================== + +INSERT INTO [dbo].[__EFMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES +(N'20260416204109_SyncContactsAndModels', N'8.0.8'), +(N'20260416210833_AddLocationFields', N'8.0.8'), +(N'20260416212330_AddExternalSyncFields', N'8.0.8'), +(N'20260416220656_AddCommentTypeAndAuditLog', N'8.0.8'), +(N'20260416224015_AddInternalWONumber', N'8.0.8'), +(N'20260417142453_AddDropdownOptionsAndWOFields', N'8.0.8'), +(N'20260417153405_AddVendorAndDispatch', N'8.0.8'), +(N'20260417154641_AddDispatchScheduledDate', N'8.0.8'), +(N'20260417171152_AddDispatchNumberAndCommentDispatchId', N'8.0.8'), +(N'20260417182910_AddTaskListTemplates', N'8.0.8'), +(N'20260417190743_MultiWODispatch', N'8.0.8'), +(N'20260417192012_AddDispatchChecklist', N'8.0.8'), +(N'20260417194438_AddDispatchSignoffs', N'8.0.8'), +(N'20260417195316_AddDispatchVerification', N'8.0.8'), +(N'20260420151445_AddVendorAccessToken', N'8.0.8'), +(N'20260420164708_AddDispatchUpliftRequest', N'8.0.8'), +(N'20260422021353_AddEventsTable', N'8.0.8') +GO + +-- ================================================================== +-- SUMMARY OF CHANGES (2026-04-22) +-- ================================================================== +-- Tables Created (11): +-- 1. Events (Calendar/Events module) +-- 2. WorkOrderAuditLogs (Audit trail for work order changes) +-- 3. DropdownOptions (Dynamic dropdown values) +-- 4. Vendors (Vendor management) +-- 5. Dispatches (Vendor dispatch workflow) +-- 6. TaskListTemplates (Reusable task templates) +-- 7. TaskListTemplateItems (Template task items) +-- 8. DispatchWorkOrders (Many-to-many: Dispatch <-> WorkOrders) +-- 9. DispatchChecklistItems (Dispatch checklist) +-- 10. DispatchSignoffs (Customer & Vendor signatures) +-- 11. DispatchUpliftRequests (NTE increase requests) +-- +-- Columns Added to WorkOrders (9): +-- - InternalWONumber, ExternalWorkOrderId, Source +-- - Problem, Trade, SubTrade +-- - VendorNTE, ScheduledDate, CompletedDate +-- +-- Columns Added to Comments (4): +-- - CommentType, Commenter, RecordType, DispatchId +-- +-- Columns Added to Locations (8): +-- - Address1, Address2, City, State, Zip +-- - PhoneNumber, Email, Status +-- +-- Columns Added to Dispatches (7): +-- - ScheduledDate, DispatchNumber, CompletedDate +-- - VerifiedBy, VerifiedAt, AcceptToken, AcknowledgedAt +-- +-- Columns Added to Vendors (1): +-- - AccessToken +-- +-- Migrations Applied: 17 +-- From: 20260416204109_SyncContactsAndModels +-- To: 20260422021353_AddEventsTable +-- +-- Database Status: UP TO DATE ✓ +-- ================================================================== From ac76b201debac995da3acd0e70bfac77f5a154b2 Mon Sep 17 00:00:00 2001 From: npalOmega Date: Tue, 28 Apr 2026 18:55:14 -0500 Subject: [PATCH 02/12] refactor --- .../Api.SeaHavenIndustries.csproj | 2 + .../Controllers/DispatchController.cs | 4 +- .../Controllers/LocationController.cs | 58 ++--- .../Controllers/SyncController.cs | 2 +- .../Controllers/VendorPortalController.cs | 4 +- .../Controllers/WorkOrderController.cs | 4 +- Api.SeaHavenIndustries/Program.cs | 28 +++ Data.SeaHavenIndustries/Models/Locations.cs | 26 ++- FIX_LOCATIONS_SCHEMA.sql | 109 +++++++++ .../Implementation/AccountDataService.cs | 91 ++++++++ .../Implementation/AssetDataService.cs | 91 ++++++++ .../Implementation/CategoryDataService.cs | 56 +++++ .../Implementation/ContactDataService.cs | 156 +++++++++++++ .../Implementation/DispatchDataService.cs | 115 +++++++++ .../Implementation/EmployeeDataService.cs | 92 ++++++++ .../Implementation/FollowUpDataService.cs | 104 +++++++++ .../Implementation/LocationDataService.cs | 98 ++++++++ .../Implementation/PMScheduleDataService.cs | 91 ++++++++ .../Implementation/VendorDataService.cs | 94 ++++++++ .../Implementation/WorkOrderDataService.cs | 189 +++++++++++++++ .../Interfaces/IAccountDataService.cs | 17 ++ .../Interfaces/IAssetDataService.cs | 18 ++ .../Interfaces/ICategoryDataService.cs | 14 ++ .../Interfaces/IContactDataService.cs | 42 ++++ .../Interfaces/IDispatchDataService.cs | 20 ++ .../Interfaces/IEmployeeDataService.cs | 17 ++ .../Interfaces/IFollowUpDataService.cs | 19 ++ .../Interfaces/ILocationDataService.cs | 18 ++ .../Interfaces/IPMScheduleDataService.cs | 18 ++ .../Interfaces/IVendorDataService.cs | 19 ++ .../Interfaces/IWorkOrderDataService.cs | 48 ++++ .../SeaHaven.DataServices.csproj | 17 ++ SeaHaven.Services/DTOs/AccountDTOs.cs | 29 +++ SeaHaven.Services/DTOs/AssetDTOs.cs | 29 +++ SeaHaven.Services/DTOs/ContactDTOs.cs | 67 ++++++ SeaHaven.Services/DTOs/DispatchDTOs.cs | 30 +++ SeaHaven.Services/DTOs/EmployeeDTOs.cs | 40 ++++ SeaHaven.Services/DTOs/FollowUpDTOs.cs | 34 +++ SeaHaven.Services/DTOs/LocationDTOs.cs | 36 +++ SeaHaven.Services/DTOs/PMScheduleDTOs.cs | 32 +++ SeaHaven.Services/DTOs/VendorDTOs.cs | 36 +++ SeaHaven.Services/DTOs/WorkOrderDTOs.cs | 44 ++++ .../Implementation/ContactService.cs | 220 ++++++++++++++++++ .../Implementation/WorkOrderService.cs | 142 +++++++++++ .../Interfaces/IAccountService.cs | 17 ++ SeaHaven.Services/Interfaces/IAssetService.cs | 18 ++ .../Interfaces/IContactService.cs | 41 ++++ .../Interfaces/IDispatchService.cs | 19 ++ .../Interfaces/IEmployeeService.cs | 17 ++ .../Interfaces/IFollowUpService.cs | 18 ++ .../Interfaces/ILocationService.cs | 18 ++ .../Interfaces/IPMScheduleService.cs | 18 ++ .../Interfaces/IVendorService.cs | 17 ++ .../Interfaces/IWorkOrderService.cs | 18 ++ SeaHaven.Services/SeaHaven.Services.csproj | 14 ++ SeaHavenIndustries.sln | 12 + 56 files changed, 2602 insertions(+), 45 deletions(-) create mode 100644 FIX_LOCATIONS_SCHEMA.sql create mode 100644 SeaHaven.DataServices/Implementation/AccountDataService.cs create mode 100644 SeaHaven.DataServices/Implementation/AssetDataService.cs create mode 100644 SeaHaven.DataServices/Implementation/CategoryDataService.cs create mode 100644 SeaHaven.DataServices/Implementation/ContactDataService.cs create mode 100644 SeaHaven.DataServices/Implementation/DispatchDataService.cs create mode 100644 SeaHaven.DataServices/Implementation/EmployeeDataService.cs create mode 100644 SeaHaven.DataServices/Implementation/FollowUpDataService.cs create mode 100644 SeaHaven.DataServices/Implementation/LocationDataService.cs create mode 100644 SeaHaven.DataServices/Implementation/PMScheduleDataService.cs create mode 100644 SeaHaven.DataServices/Implementation/VendorDataService.cs create mode 100644 SeaHaven.DataServices/Implementation/WorkOrderDataService.cs create mode 100644 SeaHaven.DataServices/Interfaces/IAccountDataService.cs create mode 100644 SeaHaven.DataServices/Interfaces/IAssetDataService.cs create mode 100644 SeaHaven.DataServices/Interfaces/ICategoryDataService.cs create mode 100644 SeaHaven.DataServices/Interfaces/IContactDataService.cs create mode 100644 SeaHaven.DataServices/Interfaces/IDispatchDataService.cs create mode 100644 SeaHaven.DataServices/Interfaces/IEmployeeDataService.cs create mode 100644 SeaHaven.DataServices/Interfaces/IFollowUpDataService.cs create mode 100644 SeaHaven.DataServices/Interfaces/ILocationDataService.cs create mode 100644 SeaHaven.DataServices/Interfaces/IPMScheduleDataService.cs create mode 100644 SeaHaven.DataServices/Interfaces/IVendorDataService.cs create mode 100644 SeaHaven.DataServices/Interfaces/IWorkOrderDataService.cs create mode 100644 SeaHaven.DataServices/SeaHaven.DataServices.csproj create mode 100644 SeaHaven.Services/DTOs/AccountDTOs.cs create mode 100644 SeaHaven.Services/DTOs/AssetDTOs.cs create mode 100644 SeaHaven.Services/DTOs/ContactDTOs.cs create mode 100644 SeaHaven.Services/DTOs/DispatchDTOs.cs create mode 100644 SeaHaven.Services/DTOs/EmployeeDTOs.cs create mode 100644 SeaHaven.Services/DTOs/FollowUpDTOs.cs create mode 100644 SeaHaven.Services/DTOs/LocationDTOs.cs create mode 100644 SeaHaven.Services/DTOs/PMScheduleDTOs.cs create mode 100644 SeaHaven.Services/DTOs/VendorDTOs.cs create mode 100644 SeaHaven.Services/DTOs/WorkOrderDTOs.cs create mode 100644 SeaHaven.Services/Implementation/ContactService.cs create mode 100644 SeaHaven.Services/Implementation/WorkOrderService.cs create mode 100644 SeaHaven.Services/Interfaces/IAccountService.cs create mode 100644 SeaHaven.Services/Interfaces/IAssetService.cs create mode 100644 SeaHaven.Services/Interfaces/IContactService.cs create mode 100644 SeaHaven.Services/Interfaces/IDispatchService.cs create mode 100644 SeaHaven.Services/Interfaces/IEmployeeService.cs create mode 100644 SeaHaven.Services/Interfaces/IFollowUpService.cs create mode 100644 SeaHaven.Services/Interfaces/ILocationService.cs create mode 100644 SeaHaven.Services/Interfaces/IPMScheduleService.cs create mode 100644 SeaHaven.Services/Interfaces/IVendorService.cs create mode 100644 SeaHaven.Services/Interfaces/IWorkOrderService.cs create mode 100644 SeaHaven.Services/SeaHaven.Services.csproj 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