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.
This commit is contained in:
Alexandre Brandizzi 2026-09-25 12:04:40 -03:00
parent c8073123e3
commit 6bfb56f349
11 changed files with 94 additions and 26 deletions

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -98,7 +98,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;
@ -106,7 +106,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();
} }
@ -228,7 +228,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;
@ -236,7 +236,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);
} }
@ -249,7 +249,7 @@ namespace SeaHaven.DataServices.Implementation
if (entity == null) if (entity == null)
return false; return false;
var now = DateTime.Now; var now = DateTime.UtcNow;
foreach (var contact in entity.Contacts ?? Enumerable.Empty<Contacts>()) foreach (var contact in entity.Contacts ?? Enumerable.Empty<Contacts>())
{ {
if (contact.IsDeleted != true) if (contact.IsDeleted != true)

View file

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

View file

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