Compare commits

...

11 commits

Author SHA1 Message Date
Alexandre Brandizzi
f67bf6c118
Merge bb41bfd8ed into 77e54e64e3 2026-09-25 16:42:53 +00:00
Alexandre Brandizzi
bb41bfd8ed Merge remote-tracking branch 'origin/main' into HEAD 2026-09-25 13:40:06 -03:00
Alexandre Brandizzi
77e54e64e3
Merge pull request #194 from Sea-Haven-Industries/feat/ab/sh-389-rejected-uplift-queue
Some checks are pending
Backend CI / Build and test (push) Waiting to run
Backend CI / architecture (push) Waiting to run
Backend CI / review (push) Waiting to run
Backend CI / ci-complete (push) Blocked by required conditions
feat(uplifts): order the rejected queue by decision time (SH-389)
2026-09-25 16:29:12 +00:00
Alexandre Brandizzi
a8d05776a8
Merge pull request #186 from Sea-Haven-Industries/feat/ab/sh-386-password-policy
feat(auth): enforce one password policy on every password-setting path
2026-09-25 16:27:36 +00:00
Alexandre Brandizzi
5353899418 feat(uplifts): order the rejected queue by decision time and include legacy denied rows 2026-09-25 13:18:06 -03:00
Alexandre Brandizzi
e9a1d8f53f
Merge pull request #190 from Sea-Haven-Industries/fix/ab/wo-created-utc
fix(data): stamp audit times in UTC and keep a work order's creation time
2026-09-25 15:39:33 +00:00
Alexandre Brandizzi
0088cffd47 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	SeaHaven.DataServices/Implementation/LocationDataService.cs
2026-09-25 12:33:37 -03:00
Alexandre Brandizzi
1c8da7f344 docs(readme): record that stored audit times are UTC and need no backfill
Review asked whether rows written by the old DateTime.Now stamps need a
backfill. They do not: the API has only run on Linux Elastic Beanstalk
hosts at their UTC default, and nothing in Terraform, .ebextensions or
.platform sets a time zone, so DateTime.Now already equalled UTC there.
Record that next to the hosting table so the decision is findable.
2026-09-25 12:20:45 -03:00
Alexandre Brandizzi
227691269d style(data): trim trailing whitespace in touched data services 2026-09-25 12:07:46 -03:00
Alexandre Brandizzi
27c21ec32e fix(services): stamp user, contact, calendar and location audit times in UTC
User creation, contact, calendar event and site-contact create/modify/delete stamps used local server time. Validation rules comparing user-entered dates and the JWT expiry are unchanged.
2026-09-25 12:06:36 -03:00
Alexandre Brandizzi
6bfb56f349 fix(data): stamp audit times in UTC and keep a caller-set work order creation time
WorkOrderDataService.AddAsync overwrote the UTC CreatedDate set by WorkOrderService with local server time, offsetting the SLA response clock on any host not running in UTC. Data services now stamp CreatedDate, LastModificationTime and DeletionTime with DateTime.UtcNow, and a work order keeps the creation time its caller set.
2026-09-25 12:04:40 -03:00
19 changed files with 217 additions and 47 deletions

View file

@ -44,7 +44,7 @@ public class CalendarServiceTests
StartDate = startDate,
EndDate = startDate,
IsDeleted = isDeleted,
CreatedDate = DateTime.Now
CreatedDate = DateTime.UtcNow
};
ctx.Events.Add(ev);
ctx.SaveChanges();
@ -64,6 +64,7 @@ public class CalendarServiceTests
saved.Title.Should().Be("Standup");
saved.IsDeleted.Should().Be(false);
saved.CreatedDate.Should().NotBeNull();
saved.CreatedDate!.Value.Kind.Should().Be(DateTimeKind.Utc);
}
[Fact]
@ -97,6 +98,7 @@ public class CalendarServiceTests
var updated = ctx.Events.Single();
updated.Title.Should().Be("New");
updated.LastModificationTime.Should().NotBeNull();
updated.LastModificationTime!.Value.Kind.Should().Be(DateTimeKind.Utc);
}
[Fact]
@ -134,6 +136,7 @@ public class CalendarServiceTests
var row = ctx.Events.Single();
row.IsDeleted.Should().Be(true);
row.DeletionTime.Should().NotBeNull();
row.DeletionTime!.Value.Kind.Should().Be(DateTimeKind.Utc);
}
[Fact]

View file

@ -164,6 +164,59 @@ public sealed class UpliftQueueReadTests
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]
public async Task List_WithoutStatusFilter_KeepsHistoricalNewestRequestFirstOrder()
{
@ -558,6 +611,40 @@ public sealed class UpliftQueueReadTests
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]
public async Task List_PendingExposureTotal_SumsEachPendingRequestsIncreaseAcrossTheQueue()
{

View file

@ -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);
}
}

View file

@ -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
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
```text

View file

@ -64,7 +64,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task<Accounts> AddAsync(Accounts account)
{
account.CreatedDate = DateTime.Now;
account.CreatedDate = DateTime.UtcNow;
await _context.Accounts.AddAsync(account);
await _context.SaveChangesAsync();
return account;
@ -72,7 +72,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task UpdateAsync(Accounts account)
{
account.LastModificationTime = DateTime.Now;
account.LastModificationTime = DateTime.UtcNow;
_context.Accounts.Update(account);
await _context.SaveChangesAsync();
}

View file

@ -72,7 +72,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task<Assets> AddAsync(Assets asset)
{
asset.CreatedDate = DateTime.Now;
asset.CreatedDate = DateTime.UtcNow;
await _context.Assets.AddAsync(asset);
await _context.SaveChangesAsync();
return asset;
@ -80,7 +80,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task UpdateAsync(Assets asset)
{
asset.LastModificationTime = DateTime.Now;
asset.LastModificationTime = DateTime.UtcNow;
_context.Assets.Update(asset);
await _context.SaveChangesAsync();
}

View file

@ -25,7 +25,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task<Category> AddAsync(Category category)
{
category.CreatedDate = DateTime.Now;
category.CreatedDate = DateTime.UtcNow;
await _context.Categories.AddAsync(category);
await _context.SaveChangesAsync();
return category;
@ -33,7 +33,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task UpdateAsync(Category category)
{
category.LastModificationTime = DateTime.Now;
category.LastModificationTime = DateTime.UtcNow;
_context.Categories.Update(category);
await _context.SaveChangesAsync();
}

View file

@ -100,7 +100,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task<Contacts> AddAsync(Contacts contact)
{
contact.CreatedDate = DateTime.Now;
contact.CreatedDate = DateTime.UtcNow;
await _context.Contacts.AddAsync(contact);
await _context.SaveChangesAsync();
return contact;
@ -108,7 +108,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task UpdateAsync(Contacts contact)
{
contact.LastModificationTime = DateTime.Now;
contact.LastModificationTime = DateTime.UtcNow;
_context.Contacts.Update(contact);
await _context.SaveChangesAsync();
}
@ -130,15 +130,15 @@ namespace SeaHaven.DataServices.Implementation
public async Task<bool> EmailExistsAsync(string email, int? excludeId = null)
{
return await _context.Contacts.AnyAsync(c =>
c.Email == email &&
return await _context.Contacts.AnyAsync(c =>
c.Email == email &&
(excludeId == null || c.Id != excludeId));
}
public async Task<bool> PhoneExistsAsync(string phone, int? excludeId = null)
{
return await _context.Contacts.AnyAsync(c =>
c.PhoneNumber == phone &&
return await _context.Contacts.AnyAsync(c =>
c.PhoneNumber == phone &&
(excludeId == null || c.Id != excludeId));
}

View file

@ -85,7 +85,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task<Dispatch> AddAsync(Dispatch dispatch)
{
dispatch.CreatedDate = DateTime.Now;
dispatch.CreatedDate = DateTime.UtcNow;
await _context.Dispatches.AddAsync(dispatch);
await _context.SaveChangesAsync();
return dispatch;
@ -93,7 +93,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task UpdateAsync(Dispatch dispatch)
{
dispatch.LastModificationTime = DateTime.Now;
dispatch.LastModificationTime = DateTime.UtcNow;
_context.Dispatches.Update(dispatch);
await _context.SaveChangesAsync();
}

View file

@ -72,7 +72,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task<Employee> AddAsync(Employee employee)
{
employee.CreatedDate = DateTime.Now;
employee.CreatedDate = DateTime.UtcNow;
await _context.Employees.AddAsync(employee);
await _context.SaveChangesAsync();
return employee;
@ -80,7 +80,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task UpdateAsync(Employee employee)
{
employee.LastModificationTime = DateTime.Now;
employee.LastModificationTime = DateTime.UtcNow;
_context.Employees.Update(employee);
await _context.SaveChangesAsync();
}

View file

@ -88,7 +88,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task<FollowUps> AddAsync(FollowUps followUp)
{
followUp.CreatedDate = DateTime.Now;
followUp.CreatedDate = DateTime.UtcNow;
await _context.FollowUps.AddAsync(followUp);
await _context.SaveChangesAsync();
return followUp;
@ -96,7 +96,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task UpdateAsync(FollowUps followUp)
{
followUp.LastModificationTime = DateTime.Now;
followUp.LastModificationTime = DateTime.UtcNow;
_context.FollowUps.Update(followUp);
await _context.SaveChangesAsync();
}
@ -218,7 +218,7 @@ namespace SeaHaven.DataServices.Implementation
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.SaveChangesAsync(cancellationToken);
return followUp;
@ -226,7 +226,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task UpdateAsync(FollowUps followUp, CancellationToken cancellationToken)
{
followUp.LastModificationTime = DateTime.Now;
followUp.LastModificationTime = DateTime.UtcNow;
_context.FollowUps.Update(followUp);
await _context.SaveChangesAsync(cancellationToken);
}
@ -238,7 +238,7 @@ namespace SeaHaven.DataServices.Implementation
return false;
entity.Status = status;
entity.LastModificationTime = DateTime.Now;
entity.LastModificationTime = DateTime.UtcNow;
await _context.SaveChangesAsync(cancellationToken);
return true;
}

View file

@ -100,7 +100,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task<Locations> AddAsync(Locations location)
{
location.CreatedDate = DateTime.Now;
location.CreatedDate = DateTime.UtcNow;
await _context.Locations.AddAsync(location);
await _context.SaveChangesAsync();
return location;
@ -108,7 +108,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task UpdateAsync(Locations location)
{
location.LastModificationTime = DateTime.Now;
location.LastModificationTime = DateTime.UtcNow;
_context.Locations.Update(location);
await _context.SaveChangesAsync();
}
@ -232,7 +232,7 @@ namespace SeaHaven.DataServices.Implementation
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.SaveChangesAsync(cancellationToken);
return location;
@ -240,7 +240,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task UpdateAsync(Locations location, CancellationToken cancellationToken)
{
location.LastModificationTime = DateTime.Now;
location.LastModificationTime = DateTime.UtcNow;
_context.Locations.Update(location);
await _context.SaveChangesAsync(cancellationToken);
}

View file

@ -46,8 +46,8 @@ namespace SeaHaven.DataServices.Implementation
}
public async Task<(IEnumerable<PMSchedules> Items, int TotalCount)> GetPagedAsync(
int page,
int pageSize,
int page,
int pageSize,
string? search = null)
{
var query = _context.PMSchedules.AsQueryable();
@ -64,7 +64,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task<PMSchedules> AddAsync(PMSchedules schedule)
{
schedule.CreatedDate = DateTime.Now;
schedule.CreatedDate = DateTime.UtcNow;
await _context.PMSchedules.AddAsync(schedule);
await _context.SaveChangesAsync();
return schedule;
@ -72,7 +72,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task UpdateAsync(PMSchedules schedule)
{
schedule.LastModificationTime = DateTime.Now;
schedule.LastModificationTime = DateTime.UtcNow;
_context.PMSchedules.Update(schedule);
await _context.SaveChangesAsync();
}

View file

@ -11,6 +11,9 @@ namespace SeaHaven.DataServices.Implementation
public class UpliftDataService : IUpliftDataService
{
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;
public UpliftDataService(ApplicationDbContext context)
@ -46,7 +49,9 @@ namespace SeaHaven.DataServices.Implementation
&& (d.IsDeleted == null || d.IsDeleted == false)
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);
if (tier.HasValue)
query = query.Where(x => x.u.RequiredTier == tier.Value);
@ -54,12 +59,13 @@ namespace SeaHaven.DataServices.Implementation
var total = await query.CountAsync(cancellationToken);
// Approval queue read contract: the actionable queue (Pending) surfaces the
// oldest request first; the decision log (Approved) surfaces the most
// recently decided first. Every other read keeps the historical
// oldest request first; the decision logs (Approved, Rejected) surface the
// most recently decided first. Every other read keeps the historical
// newest-request-first order. Id is the deterministic tiebreaker.
if (string.Equals(status, "Pending", StringComparison.Ordinal))
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);
else
query = query.OrderByDescending(x => x.u.CreatedDate).ThenByDescending(x => x.u.Id);

View file

@ -193,7 +193,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task<WorkOrder> AddAsync(WorkOrder workOrder)
{
workOrder.CreatedDate = DateTime.Now;
workOrder.CreatedDate ??= DateTime.UtcNow;
await _context.workOrders.AddAsync(workOrder);
await _context.SaveChangesAsync();
return workOrder;
@ -201,7 +201,7 @@ namespace SeaHaven.DataServices.Implementation
public async Task UpdateAsync(WorkOrder workOrder)
{
workOrder.LastModificationTime = DateTime.Now;
workOrder.LastModificationTime = DateTime.UtcNow;
_context.workOrders.Update(workOrder);
await _context.SaveChangesAsync();
}

View file

@ -31,7 +31,7 @@ namespace SeaHaven.Services.Implementation
EndDate = input.EndDate,
EndTime = input.EndTime,
AllDay = input.AllDay,
CreatedDate = DateTime.Now,
CreatedDate = DateTime.UtcNow,
IsDeleted = false
};
@ -57,7 +57,7 @@ namespace SeaHaven.Services.Implementation
model.EndDate = input.EndDate;
model.EndTime = input.EndTime;
model.AllDay = input.AllDay;
model.LastModificationTime = DateTime.Now;
model.LastModificationTime = DateTime.UtcNow;
await _dataService.UpdateEventAsync(model, cancellationToken);
return true;
@ -76,7 +76,7 @@ namespace SeaHaven.Services.Implementation
return false;
model.IsDeleted = true;
model.DeletionTime = DateTime.Now;
model.DeletionTime = DateTime.UtcNow;
await _dataService.UpdateEventAsync(model, cancellationToken);
return true;

View file

@ -104,7 +104,7 @@ namespace SeaHaven.Services.Implementation
ContactType = dto.Type,
AccountId = dto.AccountId,
createdby = userId,
CreatedDate = DateTime.Now
CreatedDate = DateTime.UtcNow
};
var savedContact = await _contactDataService.AddAsync(contact);
@ -151,7 +151,7 @@ namespace SeaHaven.Services.Implementation
if (dto.AccountId.HasValue)
existingContact.AccountId = dto.AccountId;
existingContact.LastModificationTime = DateTime.Now;
existingContact.LastModificationTime = DateTime.UtcNow;
await _contactDataService.UpdateAsync(existingContact);

View file

@ -579,7 +579,7 @@ namespace SeaHaven.Services.Implementation
{
ThrowOnInvalidContacts(contacts);
var now = DateTime.Now;
var now = DateTime.UtcNow;
var siteContacts = contacts
.Select((row, index) => new Contacts
{
@ -634,7 +634,7 @@ namespace SeaHaven.Services.Implementation
existing.PhoneNumber = phone;
existing.SiteContactOrder = i;
existing.AccountId = location.AccountId;
existing.LastModificationTime = DateTime.Now;
existing.LastModificationTime = DateTime.UtcNow;
}
else
{
@ -647,7 +647,7 @@ namespace SeaHaven.Services.Implementation
PhoneNumber = phone,
SiteContactOrder = i,
AccountId = location.AccountId,
CreatedDate = DateTime.Now,
CreatedDate = DateTime.UtcNow,
createdby = actorId
});
}
@ -655,7 +655,7 @@ namespace SeaHaven.Services.Implementation
// Rows omitted from the request are soft deleted so historical
// WorkOrderContacts keep rendering.
var now = DateTime.Now;
var now = DateTime.UtcNow;
foreach (var existing in existingById.Values)
{
if (keptIds.Contains(existing.Id) || existing.IsDeleted == true)

View file

@ -72,7 +72,7 @@ namespace SeaHaven.Services.Implementation
{
model.EmailConfirmed = true;
model.UserName = model.Email;
model.CreatedDate = DateTime.Now;
model.CreatedDate = DateTime.UtcNow;
model.UniqueName = "Active";
model.PhoneNumber = dto.Role;
@ -152,7 +152,7 @@ namespace SeaHaven.Services.Implementation
exist.Email = dto.Email;
exist.NormalizedEmail = dto.Email.ToUpperInvariant();
exist.NormalizedUserName = dto.Email.ToUpperInvariant();
exist.CreatedDate = DateTime.Now;
exist.CreatedDate = DateTime.UtcNow;
exist.UniqueName = "Active";
exist.PhoneNumber = dto.Role;
exist.AccountId = dto.AccountId;