fix(giving): map duplicate-date race to 409 + return zelle/paypal refs in session detail
This commit is contained in:
@@ -129,4 +129,27 @@ public class OfferingSessionServiceTests
|
|||||||
Assert.Equal(200m, after.SystemTotal);
|
Assert.Equal(200m, after.SystemTotal);
|
||||||
Assert.Equal(1, await db.Givings.CountAsync(g => g.OfferingSessionId == id));
|
Assert.Equal(1, await db.Givings.CountAsync(g => g.OfferingSessionId == id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetByIdAsync_ReturnsCheckZelleAndPayPalRefs()
|
||||||
|
{
|
||||||
|
using var db = BuildDb();
|
||||||
|
var catId = await SeedCategoryAsync(db);
|
||||||
|
var svc = new OfferingSessionService(db, BuildAccessor());
|
||||||
|
var req = new CreateOfferingSessionRequest
|
||||||
|
{
|
||||||
|
SessionDate = new DateOnly(2026, 6, 7), CashTotal = 0m, CheckTotal = 100m,
|
||||||
|
Givings = [ new() { GivingCategoryId = catId, Amount = 100m, PaymentMethod = "Zelle",
|
||||||
|
ZelleReferenceCode = "Z-123", PayPalTransactionId = "PP-456", CheckNumber = "C-789" } ],
|
||||||
|
};
|
||||||
|
var id = await svc.CreateAsync(req);
|
||||||
|
|
||||||
|
var dto = await svc.GetByIdAsync(id);
|
||||||
|
|
||||||
|
Assert.NotNull(dto);
|
||||||
|
var line = Assert.Single(dto!.Givings);
|
||||||
|
Assert.Equal("Z-123", line.ZelleReferenceCode);
|
||||||
|
Assert.Equal("PP-456", line.PayPalTransactionId);
|
||||||
|
Assert.Equal("C-789", line.CheckNumber);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ public class OfferingGivingLineDto
|
|||||||
public decimal Amount { get; set; }
|
public decimal Amount { get; set; }
|
||||||
public string PaymentMethod { get; set; } = "";
|
public string PaymentMethod { get; set; } = "";
|
||||||
public string? CheckNumber { get; set; }
|
public string? CheckNumber { get; set; }
|
||||||
|
public string? ZelleReferenceCode { get; set; }
|
||||||
|
public string? PayPalTransactionId { get; set; }
|
||||||
public bool IsAnonymous { get; set; }
|
public bool IsAnonymous { get; set; }
|
||||||
public string? Notes { get; set; }
|
public string? Notes { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ public class OfferingSessionService : IOfferingSessionService
|
|||||||
public async Task<bool> DateExistsAsync(DateOnly date)
|
public async Task<bool> DateExistsAsync(DateOnly date)
|
||||||
=> await _db.OfferingSessions.AnyAsync(s => s.SessionDate == date);
|
=> await _db.OfferingSessions.AnyAsync(s => s.SessionDate == date);
|
||||||
|
|
||||||
|
// Distinguishes a unique-index collision on SessionDate (concurrent insert) from other DB errors.
|
||||||
|
private async Task<bool> DateExistsConcurrentlyAsync(DateOnly date, int excludeId)
|
||||||
|
=> await _db.OfferingSessions.AsNoTracking().AnyAsync(s => s.SessionDate == date && s.Id != excludeId);
|
||||||
|
|
||||||
public async Task<OfferingSessionDto?> GetByIdAsync(int id)
|
public async Task<OfferingSessionDto?> GetByIdAsync(int id)
|
||||||
{
|
{
|
||||||
var s = await _db.OfferingSessions.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id);
|
var s = await _db.OfferingSessions.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id);
|
||||||
@@ -84,7 +88,10 @@ public class OfferingSessionService : IOfferingSessionService
|
|||||||
GivingCategoryId = l.GivingCategoryId,
|
GivingCategoryId = l.GivingCategoryId,
|
||||||
CategoryName = catNames.TryGetValue(l.GivingCategoryId, out var cn) ? cn : "",
|
CategoryName = catNames.TryGetValue(l.GivingCategoryId, out var cn) ? cn : "",
|
||||||
Amount = l.Amount, PaymentMethod = l.PaymentMethod,
|
Amount = l.Amount, PaymentMethod = l.PaymentMethod,
|
||||||
CheckNumber = l.CheckNumber, IsAnonymous = l.IsAnonymous, Notes = l.Notes,
|
CheckNumber = l.CheckNumber,
|
||||||
|
ZelleReferenceCode = l.ZelleReferenceCode,
|
||||||
|
PayPalTransactionId = l.PayPalTransactionId,
|
||||||
|
IsAnonymous = l.IsAnonymous, Notes = l.Notes,
|
||||||
}).ToList(),
|
}).ToList(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -106,7 +113,16 @@ public class OfferingSessionService : IOfferingSessionService
|
|||||||
Givings = r.Givings.Select(line => MapLine(line, r.SessionDate)).ToList(),
|
Givings = r.Givings.Select(line => MapLine(line, r.SessionDate)).ToList(),
|
||||||
};
|
};
|
||||||
_db.OfferingSessions.Add(session);
|
_db.OfferingSessions.Add(session);
|
||||||
|
try
|
||||||
|
{
|
||||||
await _db.SaveChangesAsync();
|
await _db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
catch (DbUpdateException)
|
||||||
|
{
|
||||||
|
if (await DateExistsConcurrentlyAsync(r.SessionDate, session.Id))
|
||||||
|
throw new InvalidOperationException($"An offering session for {r.SessionDate:yyyy-MM-dd} already exists.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
return session.Id;
|
return session.Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user