mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-04 18:22:08 +00:00
Merge remote-tracking branch 'origin/main' into HEAD
This commit is contained in:
commit
bb41bfd8ed
19 changed files with 217 additions and 47 deletions
|
|
@ -44,7 +44,7 @@ public class CalendarServiceTests
|
||||||
StartDate = startDate,
|
StartDate = startDate,
|
||||||
EndDate = startDate,
|
EndDate = startDate,
|
||||||
IsDeleted = isDeleted,
|
IsDeleted = isDeleted,
|
||||||
CreatedDate = DateTime.Now
|
CreatedDate = DateTime.UtcNow
|
||||||
};
|
};
|
||||||
ctx.Events.Add(ev);
|
ctx.Events.Add(ev);
|
||||||
ctx.SaveChanges();
|
ctx.SaveChanges();
|
||||||
|
|
@ -64,6 +64,7 @@ public class CalendarServiceTests
|
||||||
saved.Title.Should().Be("Standup");
|
saved.Title.Should().Be("Standup");
|
||||||
saved.IsDeleted.Should().Be(false);
|
saved.IsDeleted.Should().Be(false);
|
||||||
saved.CreatedDate.Should().NotBeNull();
|
saved.CreatedDate.Should().NotBeNull();
|
||||||
|
saved.CreatedDate!.Value.Kind.Should().Be(DateTimeKind.Utc);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -97,6 +98,7 @@ public class CalendarServiceTests
|
||||||
var updated = ctx.Events.Single();
|
var updated = ctx.Events.Single();
|
||||||
updated.Title.Should().Be("New");
|
updated.Title.Should().Be("New");
|
||||||
updated.LastModificationTime.Should().NotBeNull();
|
updated.LastModificationTime.Should().NotBeNull();
|
||||||
|
updated.LastModificationTime!.Value.Kind.Should().Be(DateTimeKind.Utc);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -134,6 +136,7 @@ public class CalendarServiceTests
|
||||||
var row = ctx.Events.Single();
|
var row = ctx.Events.Single();
|
||||||
row.IsDeleted.Should().Be(true);
|
row.IsDeleted.Should().Be(true);
|
||||||
row.DeletionTime.Should().NotBeNull();
|
row.DeletionTime.Should().NotBeNull();
|
||||||
|
row.DeletionTime!.Value.Kind.Should().Be(DateTimeKind.Utc);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,59 @@ public sealed class UpliftQueueReadTests
|
||||||
new DateTime(2026, 3, 10));
|
new DateTime(2026, 3, 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task List_RejectedStatus_OrdersMostRecentlyRejectedFirst()
|
||||||
|
{
|
||||||
|
using var context = NewContext();
|
||||||
|
var vendor = new Vendor { CompanyName = "Gateway", IsActive = true };
|
||||||
|
var workOrder = new WorkOrder { WorkerOrderTitle = "Repair" };
|
||||||
|
context.AddRange(vendor, workOrder);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
var dispatch = await SeedDispatchAsync(context, vendor, workOrder, "DIS-1");
|
||||||
|
// Request order (3/1, 3/2, 3/3) deliberately disagrees with decision order.
|
||||||
|
context.DispatchUpliftRequests.AddRange(
|
||||||
|
Request(dispatch, "Rejected", new DateTime(2026, 3, 1), decided: new DateTime(2026, 3, 10)),
|
||||||
|
Request(dispatch, "Rejected", new DateTime(2026, 3, 2), decided: new DateTime(2026, 3, 20)),
|
||||||
|
Request(dispatch, "Rejected", new DateTime(2026, 3, 3), decided: new DateTime(2026, 3, 15)));
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
var service = NewService(context);
|
||||||
|
|
||||||
|
var result = await service.ListAsync(UserWithRoles("Approver"), "Rejected", null, 1, 25, CancellationToken.None);
|
||||||
|
|
||||||
|
result.Items.Select(i => i.DecidedAt).Should().Equal(
|
||||||
|
new DateTime(2026, 3, 20),
|
||||||
|
new DateTime(2026, 3, 15),
|
||||||
|
new DateTime(2026, 3, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task List_RejectedStatus_ReturnsOnlyRejectedRequests_IncludingLegacyDenied()
|
||||||
|
{
|
||||||
|
using var context = NewContext();
|
||||||
|
var vendor = new Vendor { CompanyName = "Gateway", IsActive = true };
|
||||||
|
var workOrder = new WorkOrder { WorkerOrderTitle = "Repair" };
|
||||||
|
context.AddRange(vendor, workOrder);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
var dispatch = await SeedDispatchAsync(context, vendor, workOrder, "DIS-1");
|
||||||
|
context.DispatchUpliftRequests.AddRange(
|
||||||
|
Request(dispatch, "Pending", new DateTime(2026, 3, 1)),
|
||||||
|
Request(dispatch, "Approved", new DateTime(2026, 3, 2), decided: new DateTime(2026, 3, 3)),
|
||||||
|
Request(dispatch, "Revoked", new DateTime(2026, 3, 4), decided: new DateTime(2026, 3, 5)),
|
||||||
|
Request(dispatch, "Withdrawn", new DateTime(2026, 3, 6)),
|
||||||
|
Request(dispatch, "Rejected", new DateTime(2026, 3, 7), decided: new DateTime(2026, 3, 8)),
|
||||||
|
Request(dispatch, "Denied", new DateTime(2026, 2, 1), decided: new DateTime(2026, 2, 2)));
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
var service = NewService(context);
|
||||||
|
|
||||||
|
var result = await service.ListAsync(UserWithRoles("Approver"), "Rejected", null, 1, 25, CancellationToken.None);
|
||||||
|
|
||||||
|
result.Total.Should().Be(2);
|
||||||
|
result.Items.Select(i => i.Status).Should().Equal("Rejected", "Rejected");
|
||||||
|
result.Items.Select(i => i.DecidedAt).Should().Equal(
|
||||||
|
new DateTime(2026, 3, 8),
|
||||||
|
new DateTime(2026, 2, 2));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task List_WithoutStatusFilter_KeepsHistoricalNewestRequestFirstOrder()
|
public async Task List_WithoutStatusFilter_KeepsHistoricalNewestRequestFirstOrder()
|
||||||
{
|
{
|
||||||
|
|
@ -558,6 +611,40 @@ public sealed class UpliftQueueReadTests
|
||||||
result.Items.Single().DecidedByName.Should().Be("Grace Hopper");
|
result.Items.Single().DecidedByName.Should().Be("Grace Hopper");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task List_RejectedRow_CarriesRejecterRequesterDecisionTimeAndReason()
|
||||||
|
{
|
||||||
|
using var context = NewContext();
|
||||||
|
var (vendor, workOrder) = await SeedWorkOrderAsync(context, "WO-R", "SITE-R", "Plumbing");
|
||||||
|
var dispatch = await SeedDispatchAsync(context, vendor, workOrder, "DIS-R");
|
||||||
|
context.Users.Add(new ApplicationUser { Id = "user-7", FirstName = "Ada", LastName = "Lovelace" });
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
context.DispatchUpliftRequests.Add(new DispatchUpliftRequest
|
||||||
|
{
|
||||||
|
DispatchId = dispatch.Id,
|
||||||
|
Status = "Rejected",
|
||||||
|
CreatedDate = new DateTime(2026, 3, 1),
|
||||||
|
DecidedAt = new DateTime(2026, 3, 2, 16, 40, 0),
|
||||||
|
DecidedByUserId = "user-7",
|
||||||
|
DecisionNote = "Outside this work order's scope",
|
||||||
|
RequestedByVendorName = "Gateway",
|
||||||
|
RequiredTier = 1,
|
||||||
|
RequestedNTE = 250m,
|
||||||
|
NotificationStatus = "Pending"
|
||||||
|
});
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
var service = NewService(context);
|
||||||
|
|
||||||
|
var result = await service.ListAsync(UserWithRoles("Approver"), "Rejected", null, 1, 25, CancellationToken.None);
|
||||||
|
|
||||||
|
var row = result.Items.Single();
|
||||||
|
row.DecidedByName.Should().Be("Ada Lovelace");
|
||||||
|
row.RequestedByName.Should().Be("Gateway");
|
||||||
|
row.DecidedAt.Should().Be(new DateTime(2026, 3, 2, 16, 40, 0));
|
||||||
|
row.DecisionNote.Should().Be("Outside this work order's scope");
|
||||||
|
row.WorkOrderNumber.Should().Be("WO-R");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task List_PendingExposureTotal_SumsEachPendingRequestsIncreaseAcrossTheQueue()
|
public async Task List_PendingExposureTotal_SumsEachPendingRequestsIncreaseAcrossTheQueue()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
using Data.SeaHavenIndustries;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using SeaHaven.DataServices.Implementation;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Api.SeaHavenIndustries.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The SLA clock and every "created"/"modified" display read these stamps as UTC, so the data layer
|
||||||
|
/// must never write local server time into them.
|
||||||
|
/// </summary>
|
||||||
|
public class WorkOrderDataServiceTimestampTests
|
||||||
|
{
|
||||||
|
private static ApplicationDbContext NewContext()
|
||||||
|
{
|
||||||
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||||
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||||
|
.Options;
|
||||||
|
return new ApplicationDbContext(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static WorkOrder NewWorkOrder() =>
|
||||||
|
new() { InternalWONumber = "WO-1", WorkerOrderTitle = "Leak", LocationId = 100 };
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AddAsync_KeepsTheCreationTimeTheCallerSet()
|
||||||
|
{
|
||||||
|
await using var context = NewContext();
|
||||||
|
var createdAt = new DateTime(2026, 9, 25, 14, 0, 0, DateTimeKind.Utc);
|
||||||
|
var workOrder = NewWorkOrder();
|
||||||
|
workOrder.CreatedDate = createdAt;
|
||||||
|
|
||||||
|
var saved = await new WorkOrderDataService(context).AddAsync(workOrder);
|
||||||
|
|
||||||
|
saved.CreatedDate.Should().Be(createdAt);
|
||||||
|
saved.CreatedDate!.Value.Kind.Should().Be(DateTimeKind.Utc);
|
||||||
|
(await context.workOrders.SingleAsync()).CreatedDate.Should().Be(createdAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AddAsync_StampsUtcWhenTheCallerSetNoCreationTime()
|
||||||
|
{
|
||||||
|
await using var context = NewContext();
|
||||||
|
var before = DateTime.UtcNow;
|
||||||
|
|
||||||
|
var saved = await new WorkOrderDataService(context).AddAsync(NewWorkOrder());
|
||||||
|
|
||||||
|
saved.CreatedDate.Should().NotBeNull();
|
||||||
|
saved.CreatedDate!.Value.Kind.Should().Be(DateTimeKind.Utc);
|
||||||
|
saved.CreatedDate.Value.Should().BeOnOrAfter(before).And.BeOnOrBefore(DateTime.UtcNow);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateAsync_StampsTheModificationTimeInUtc()
|
||||||
|
{
|
||||||
|
await using var context = NewContext();
|
||||||
|
var service = new WorkOrderDataService(context);
|
||||||
|
var saved = await service.AddAsync(NewWorkOrder());
|
||||||
|
var before = DateTime.UtcNow;
|
||||||
|
|
||||||
|
await service.UpdateAsync(saved);
|
||||||
|
|
||||||
|
saved.LastModificationTime.Should().NotBeNull();
|
||||||
|
saved.LastModificationTime!.Value.Kind.Should().Be(DateTimeKind.Utc);
|
||||||
|
saved.LastModificationTime.Value.Should().BeOnOrAfter(before).And.BeOnOrBefore(DateTime.UtcNow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,12 @@ Both run in `us-east-1` on the .NET 8 Amazon Linux 2023 platform. Terraform in
|
||||||
`terraform/live/` owns the environments; GitHub Actions owns the application
|
`terraform/live/` owns the environments; GitHub Actions owns the application
|
||||||
versions. There is no production environment yet.
|
versions. There is no production environment yet.
|
||||||
|
|
||||||
|
Stored created, modified and deletion times are UTC: the API stamps them with
|
||||||
|
`DateTime.UtcNow`, whatever the host's time zone. The API has only ever run on
|
||||||
|
Linux Elastic Beanstalk hosts left at their UTC default (nothing in Terraform,
|
||||||
|
`.ebextensions` or `.platform` sets a time zone), so rows written before the
|
||||||
|
switch from `DateTime.Now` are already UTC and need no backfill.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task<Accounts> AddAsync(Accounts account)
|
public async Task<Accounts> AddAsync(Accounts account)
|
||||||
{
|
{
|
||||||
account.CreatedDate = DateTime.Now;
|
account.CreatedDate = DateTime.UtcNow;
|
||||||
await _context.Accounts.AddAsync(account);
|
await _context.Accounts.AddAsync(account);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
return account;
|
return account;
|
||||||
|
|
@ -72,7 +72,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task UpdateAsync(Accounts account)
|
public async Task UpdateAsync(Accounts account)
|
||||||
{
|
{
|
||||||
account.LastModificationTime = DateTime.Now;
|
account.LastModificationTime = DateTime.UtcNow;
|
||||||
_context.Accounts.Update(account);
|
_context.Accounts.Update(account);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task<Assets> AddAsync(Assets asset)
|
public async Task<Assets> AddAsync(Assets asset)
|
||||||
{
|
{
|
||||||
asset.CreatedDate = DateTime.Now;
|
asset.CreatedDate = DateTime.UtcNow;
|
||||||
await _context.Assets.AddAsync(asset);
|
await _context.Assets.AddAsync(asset);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
return asset;
|
return asset;
|
||||||
|
|
@ -80,7 +80,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task UpdateAsync(Assets asset)
|
public async Task UpdateAsync(Assets asset)
|
||||||
{
|
{
|
||||||
asset.LastModificationTime = DateTime.Now;
|
asset.LastModificationTime = DateTime.UtcNow;
|
||||||
_context.Assets.Update(asset);
|
_context.Assets.Update(asset);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task<Category> AddAsync(Category category)
|
public async Task<Category> AddAsync(Category category)
|
||||||
{
|
{
|
||||||
category.CreatedDate = DateTime.Now;
|
category.CreatedDate = DateTime.UtcNow;
|
||||||
await _context.Categories.AddAsync(category);
|
await _context.Categories.AddAsync(category);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
return category;
|
return category;
|
||||||
|
|
@ -33,7 +33,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task UpdateAsync(Category category)
|
public async Task UpdateAsync(Category category)
|
||||||
{
|
{
|
||||||
category.LastModificationTime = DateTime.Now;
|
category.LastModificationTime = DateTime.UtcNow;
|
||||||
_context.Categories.Update(category);
|
_context.Categories.Update(category);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task<Contacts> AddAsync(Contacts contact)
|
public async Task<Contacts> AddAsync(Contacts contact)
|
||||||
{
|
{
|
||||||
contact.CreatedDate = DateTime.Now;
|
contact.CreatedDate = DateTime.UtcNow;
|
||||||
await _context.Contacts.AddAsync(contact);
|
await _context.Contacts.AddAsync(contact);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
return contact;
|
return contact;
|
||||||
|
|
@ -108,7 +108,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task UpdateAsync(Contacts contact)
|
public async Task UpdateAsync(Contacts contact)
|
||||||
{
|
{
|
||||||
contact.LastModificationTime = DateTime.Now;
|
contact.LastModificationTime = DateTime.UtcNow;
|
||||||
_context.Contacts.Update(contact);
|
_context.Contacts.Update(contact);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task<Dispatch> AddAsync(Dispatch dispatch)
|
public async Task<Dispatch> AddAsync(Dispatch dispatch)
|
||||||
{
|
{
|
||||||
dispatch.CreatedDate = DateTime.Now;
|
dispatch.CreatedDate = DateTime.UtcNow;
|
||||||
await _context.Dispatches.AddAsync(dispatch);
|
await _context.Dispatches.AddAsync(dispatch);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
return dispatch;
|
return dispatch;
|
||||||
|
|
@ -93,7 +93,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task UpdateAsync(Dispatch dispatch)
|
public async Task UpdateAsync(Dispatch dispatch)
|
||||||
{
|
{
|
||||||
dispatch.LastModificationTime = DateTime.Now;
|
dispatch.LastModificationTime = DateTime.UtcNow;
|
||||||
_context.Dispatches.Update(dispatch);
|
_context.Dispatches.Update(dispatch);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task<Employee> AddAsync(Employee employee)
|
public async Task<Employee> AddAsync(Employee employee)
|
||||||
{
|
{
|
||||||
employee.CreatedDate = DateTime.Now;
|
employee.CreatedDate = DateTime.UtcNow;
|
||||||
await _context.Employees.AddAsync(employee);
|
await _context.Employees.AddAsync(employee);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
return employee;
|
return employee;
|
||||||
|
|
@ -80,7 +80,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task UpdateAsync(Employee employee)
|
public async Task UpdateAsync(Employee employee)
|
||||||
{
|
{
|
||||||
employee.LastModificationTime = DateTime.Now;
|
employee.LastModificationTime = DateTime.UtcNow;
|
||||||
_context.Employees.Update(employee);
|
_context.Employees.Update(employee);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task<FollowUps> AddAsync(FollowUps followUp)
|
public async Task<FollowUps> AddAsync(FollowUps followUp)
|
||||||
{
|
{
|
||||||
followUp.CreatedDate = DateTime.Now;
|
followUp.CreatedDate = DateTime.UtcNow;
|
||||||
await _context.FollowUps.AddAsync(followUp);
|
await _context.FollowUps.AddAsync(followUp);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
return followUp;
|
return followUp;
|
||||||
|
|
@ -96,7 +96,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task UpdateAsync(FollowUps followUp)
|
public async Task UpdateAsync(FollowUps followUp)
|
||||||
{
|
{
|
||||||
followUp.LastModificationTime = DateTime.Now;
|
followUp.LastModificationTime = DateTime.UtcNow;
|
||||||
_context.FollowUps.Update(followUp);
|
_context.FollowUps.Update(followUp);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
@ -218,7 +218,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task<FollowUps> AddAsync(FollowUps followUp, CancellationToken cancellationToken)
|
public async Task<FollowUps> AddAsync(FollowUps followUp, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
followUp.CreatedDate = DateTime.Now;
|
followUp.CreatedDate = DateTime.UtcNow;
|
||||||
await _context.FollowUps.AddAsync(followUp, cancellationToken);
|
await _context.FollowUps.AddAsync(followUp, cancellationToken);
|
||||||
await _context.SaveChangesAsync(cancellationToken);
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
return followUp;
|
return followUp;
|
||||||
|
|
@ -226,7 +226,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task UpdateAsync(FollowUps followUp, CancellationToken cancellationToken)
|
public async Task UpdateAsync(FollowUps followUp, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
followUp.LastModificationTime = DateTime.Now;
|
followUp.LastModificationTime = DateTime.UtcNow;
|
||||||
_context.FollowUps.Update(followUp);
|
_context.FollowUps.Update(followUp);
|
||||||
await _context.SaveChangesAsync(cancellationToken);
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
@ -238,7 +238,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
entity.Status = status;
|
entity.Status = status;
|
||||||
entity.LastModificationTime = DateTime.Now;
|
entity.LastModificationTime = DateTime.UtcNow;
|
||||||
await _context.SaveChangesAsync(cancellationToken);
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task<Locations> AddAsync(Locations location)
|
public async Task<Locations> AddAsync(Locations location)
|
||||||
{
|
{
|
||||||
location.CreatedDate = DateTime.Now;
|
location.CreatedDate = DateTime.UtcNow;
|
||||||
await _context.Locations.AddAsync(location);
|
await _context.Locations.AddAsync(location);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
return location;
|
return location;
|
||||||
|
|
@ -108,7 +108,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task UpdateAsync(Locations location)
|
public async Task UpdateAsync(Locations location)
|
||||||
{
|
{
|
||||||
location.LastModificationTime = DateTime.Now;
|
location.LastModificationTime = DateTime.UtcNow;
|
||||||
_context.Locations.Update(location);
|
_context.Locations.Update(location);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
@ -232,7 +232,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task<Locations> AddAsync(Locations location, CancellationToken cancellationToken)
|
public async Task<Locations> AddAsync(Locations location, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
location.CreatedDate = DateTime.Now;
|
location.CreatedDate = DateTime.UtcNow;
|
||||||
await _context.Locations.AddAsync(location, cancellationToken);
|
await _context.Locations.AddAsync(location, cancellationToken);
|
||||||
await _context.SaveChangesAsync(cancellationToken);
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
return location;
|
return location;
|
||||||
|
|
@ -240,7 +240,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task UpdateAsync(Locations location, CancellationToken cancellationToken)
|
public async Task UpdateAsync(Locations location, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
location.LastModificationTime = DateTime.Now;
|
location.LastModificationTime = DateTime.UtcNow;
|
||||||
_context.Locations.Update(location);
|
_context.Locations.Update(location);
|
||||||
await _context.SaveChangesAsync(cancellationToken);
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task<PMSchedules> AddAsync(PMSchedules schedule)
|
public async Task<PMSchedules> AddAsync(PMSchedules schedule)
|
||||||
{
|
{
|
||||||
schedule.CreatedDate = DateTime.Now;
|
schedule.CreatedDate = DateTime.UtcNow;
|
||||||
await _context.PMSchedules.AddAsync(schedule);
|
await _context.PMSchedules.AddAsync(schedule);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
return schedule;
|
return schedule;
|
||||||
|
|
@ -72,7 +72,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task UpdateAsync(PMSchedules schedule)
|
public async Task UpdateAsync(PMSchedules schedule)
|
||||||
{
|
{
|
||||||
schedule.LastModificationTime = DateTime.Now;
|
schedule.LastModificationTime = DateTime.UtcNow;
|
||||||
_context.PMSchedules.Update(schedule);
|
_context.PMSchedules.Update(schedule);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,9 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
public class UpliftDataService : IUpliftDataService
|
public class UpliftDataService : IUpliftDataService
|
||||||
{
|
{
|
||||||
private static readonly ConcurrentDictionary<int, SemaphoreSlim> WorkOrderGates = new();
|
private static readonly ConcurrentDictionary<int, SemaphoreSlim> WorkOrderGates = new();
|
||||||
|
|
||||||
|
// The Rejected queue also surfaces the legacy "Denied" spelling, which reads as Rejected.
|
||||||
|
private static readonly string[] RejectedStatuses = { "Rejected", "Denied" };
|
||||||
private readonly ApplicationDbContext _context;
|
private readonly ApplicationDbContext _context;
|
||||||
|
|
||||||
public UpliftDataService(ApplicationDbContext context)
|
public UpliftDataService(ApplicationDbContext context)
|
||||||
|
|
@ -46,7 +49,9 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
&& (d.IsDeleted == null || d.IsDeleted == false)
|
&& (d.IsDeleted == null || d.IsDeleted == false)
|
||||||
select new { u, d, v, ev, effectiveWorkOrderId, workOrder, reqUser, decUser };
|
select new { u, d, v, ev, effectiveWorkOrderId, workOrder, reqUser, decUser };
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(status))
|
if (string.Equals(status, "Rejected", StringComparison.Ordinal))
|
||||||
|
query = query.Where(x => RejectedStatuses.Contains(x.u.Status));
|
||||||
|
else if (!string.IsNullOrWhiteSpace(status))
|
||||||
query = query.Where(x => x.u.Status == status);
|
query = query.Where(x => x.u.Status == status);
|
||||||
if (tier.HasValue)
|
if (tier.HasValue)
|
||||||
query = query.Where(x => x.u.RequiredTier == tier.Value);
|
query = query.Where(x => x.u.RequiredTier == tier.Value);
|
||||||
|
|
@ -54,12 +59,13 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
var total = await query.CountAsync(cancellationToken);
|
var total = await query.CountAsync(cancellationToken);
|
||||||
|
|
||||||
// Approval queue read contract: the actionable queue (Pending) surfaces the
|
// Approval queue read contract: the actionable queue (Pending) surfaces the
|
||||||
// oldest request first; the decision log (Approved) surfaces the most
|
// oldest request first; the decision logs (Approved, Rejected) surface the
|
||||||
// recently decided first. Every other read keeps the historical
|
// most recently decided first. Every other read keeps the historical
|
||||||
// newest-request-first order. Id is the deterministic tiebreaker.
|
// newest-request-first order. Id is the deterministic tiebreaker.
|
||||||
if (string.Equals(status, "Pending", StringComparison.Ordinal))
|
if (string.Equals(status, "Pending", StringComparison.Ordinal))
|
||||||
query = query.OrderBy(x => x.u.CreatedDate).ThenBy(x => x.u.Id);
|
query = query.OrderBy(x => x.u.CreatedDate).ThenBy(x => x.u.Id);
|
||||||
else if (string.Equals(status, "Approved", StringComparison.Ordinal))
|
else if (string.Equals(status, "Approved", StringComparison.Ordinal)
|
||||||
|
|| string.Equals(status, "Rejected", StringComparison.Ordinal))
|
||||||
query = query.OrderByDescending(x => x.u.DecidedAt).ThenByDescending(x => x.u.Id);
|
query = query.OrderByDescending(x => x.u.DecidedAt).ThenByDescending(x => x.u.Id);
|
||||||
else
|
else
|
||||||
query = query.OrderByDescending(x => x.u.CreatedDate).ThenByDescending(x => x.u.Id);
|
query = query.OrderByDescending(x => x.u.CreatedDate).ThenByDescending(x => x.u.Id);
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task<WorkOrder> AddAsync(WorkOrder workOrder)
|
public async Task<WorkOrder> AddAsync(WorkOrder workOrder)
|
||||||
{
|
{
|
||||||
workOrder.CreatedDate = DateTime.Now;
|
workOrder.CreatedDate ??= DateTime.UtcNow;
|
||||||
await _context.workOrders.AddAsync(workOrder);
|
await _context.workOrders.AddAsync(workOrder);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
return workOrder;
|
return workOrder;
|
||||||
|
|
@ -201,7 +201,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
||||||
public async Task UpdateAsync(WorkOrder workOrder)
|
public async Task UpdateAsync(WorkOrder workOrder)
|
||||||
{
|
{
|
||||||
workOrder.LastModificationTime = DateTime.Now;
|
workOrder.LastModificationTime = DateTime.UtcNow;
|
||||||
_context.workOrders.Update(workOrder);
|
_context.workOrders.Update(workOrder);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
EndDate = input.EndDate,
|
EndDate = input.EndDate,
|
||||||
EndTime = input.EndTime,
|
EndTime = input.EndTime,
|
||||||
AllDay = input.AllDay,
|
AllDay = input.AllDay,
|
||||||
CreatedDate = DateTime.Now,
|
CreatedDate = DateTime.UtcNow,
|
||||||
IsDeleted = false
|
IsDeleted = false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -57,7 +57,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
model.EndDate = input.EndDate;
|
model.EndDate = input.EndDate;
|
||||||
model.EndTime = input.EndTime;
|
model.EndTime = input.EndTime;
|
||||||
model.AllDay = input.AllDay;
|
model.AllDay = input.AllDay;
|
||||||
model.LastModificationTime = DateTime.Now;
|
model.LastModificationTime = DateTime.UtcNow;
|
||||||
|
|
||||||
await _dataService.UpdateEventAsync(model, cancellationToken);
|
await _dataService.UpdateEventAsync(model, cancellationToken);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -76,7 +76,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
model.IsDeleted = true;
|
model.IsDeleted = true;
|
||||||
model.DeletionTime = DateTime.Now;
|
model.DeletionTime = DateTime.UtcNow;
|
||||||
|
|
||||||
await _dataService.UpdateEventAsync(model, cancellationToken);
|
await _dataService.UpdateEventAsync(model, cancellationToken);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
ContactType = dto.Type,
|
ContactType = dto.Type,
|
||||||
AccountId = dto.AccountId,
|
AccountId = dto.AccountId,
|
||||||
createdby = userId,
|
createdby = userId,
|
||||||
CreatedDate = DateTime.Now
|
CreatedDate = DateTime.UtcNow
|
||||||
};
|
};
|
||||||
|
|
||||||
var savedContact = await _contactDataService.AddAsync(contact);
|
var savedContact = await _contactDataService.AddAsync(contact);
|
||||||
|
|
@ -151,7 +151,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
if (dto.AccountId.HasValue)
|
if (dto.AccountId.HasValue)
|
||||||
existingContact.AccountId = dto.AccountId;
|
existingContact.AccountId = dto.AccountId;
|
||||||
|
|
||||||
existingContact.LastModificationTime = DateTime.Now;
|
existingContact.LastModificationTime = DateTime.UtcNow;
|
||||||
|
|
||||||
await _contactDataService.UpdateAsync(existingContact);
|
await _contactDataService.UpdateAsync(existingContact);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -579,7 +579,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
{
|
{
|
||||||
ThrowOnInvalidContacts(contacts);
|
ThrowOnInvalidContacts(contacts);
|
||||||
|
|
||||||
var now = DateTime.Now;
|
var now = DateTime.UtcNow;
|
||||||
var siteContacts = contacts
|
var siteContacts = contacts
|
||||||
.Select((row, index) => new Contacts
|
.Select((row, index) => new Contacts
|
||||||
{
|
{
|
||||||
|
|
@ -634,7 +634,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
existing.PhoneNumber = phone;
|
existing.PhoneNumber = phone;
|
||||||
existing.SiteContactOrder = i;
|
existing.SiteContactOrder = i;
|
||||||
existing.AccountId = location.AccountId;
|
existing.AccountId = location.AccountId;
|
||||||
existing.LastModificationTime = DateTime.Now;
|
existing.LastModificationTime = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -647,7 +647,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
PhoneNumber = phone,
|
PhoneNumber = phone,
|
||||||
SiteContactOrder = i,
|
SiteContactOrder = i,
|
||||||
AccountId = location.AccountId,
|
AccountId = location.AccountId,
|
||||||
CreatedDate = DateTime.Now,
|
CreatedDate = DateTime.UtcNow,
|
||||||
createdby = actorId
|
createdby = actorId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -655,7 +655,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
|
|
||||||
// Rows omitted from the request are soft deleted so historical
|
// Rows omitted from the request are soft deleted so historical
|
||||||
// WorkOrderContacts keep rendering.
|
// WorkOrderContacts keep rendering.
|
||||||
var now = DateTime.Now;
|
var now = DateTime.UtcNow;
|
||||||
foreach (var existing in existingById.Values)
|
foreach (var existing in existingById.Values)
|
||||||
{
|
{
|
||||||
if (keptIds.Contains(existing.Id) || existing.IsDeleted == true)
|
if (keptIds.Contains(existing.Id) || existing.IsDeleted == true)
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
{
|
{
|
||||||
model.EmailConfirmed = true;
|
model.EmailConfirmed = true;
|
||||||
model.UserName = model.Email;
|
model.UserName = model.Email;
|
||||||
model.CreatedDate = DateTime.Now;
|
model.CreatedDate = DateTime.UtcNow;
|
||||||
model.UniqueName = "Active";
|
model.UniqueName = "Active";
|
||||||
model.PhoneNumber = dto.Role;
|
model.PhoneNumber = dto.Role;
|
||||||
|
|
||||||
|
|
@ -152,7 +152,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
exist.Email = dto.Email;
|
exist.Email = dto.Email;
|
||||||
exist.NormalizedEmail = dto.Email.ToUpperInvariant();
|
exist.NormalizedEmail = dto.Email.ToUpperInvariant();
|
||||||
exist.NormalizedUserName = dto.Email.ToUpperInvariant();
|
exist.NormalizedUserName = dto.Email.ToUpperInvariant();
|
||||||
exist.CreatedDate = DateTime.Now;
|
exist.CreatedDate = DateTime.UtcNow;
|
||||||
exist.UniqueName = "Active";
|
exist.UniqueName = "Active";
|
||||||
exist.PhoneNumber = dto.Role;
|
exist.PhoneNumber = dto.Role;
|
||||||
exist.AccountId = dto.AccountId;
|
exist.AccountId = dto.AccountId;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue