refactor(inventory): consolidate IsCurrency, skip num=0 grants in history
- Drop IsWalletCurrency (duplicate of IsCurrency); use IsCurrency in WriteAcquireHistory. - Add comment on first SaveChangesAsync in CommitAsync explaining the two-phase flush. - Guard WriteAcquireHistory loop with grant.Num == 0 check so synthetic DebitItem post-state ops do not produce history rows. - Add InventoryHistoryTests.Commit_writes_no_history_row_for_item_debit to lock in the fix. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -276,6 +276,7 @@ internal sealed class InventoryTransaction : IInventoryTransaction
|
|||||||
{
|
{
|
||||||
ThrowIfCommitted();
|
ThrowIfCommitted();
|
||||||
|
|
||||||
|
// Flush entity mutations first so audit-history rows are staged on top of post-commit state.
|
||||||
await _db.SaveChangesAsync(ct);
|
await _db.SaveChangesAsync(ct);
|
||||||
|
|
||||||
WriteAcquireHistory();
|
WriteAcquireHistory();
|
||||||
@@ -298,10 +299,11 @@ internal sealed class InventoryTransaction : IInventoryTransaction
|
|||||||
foreach (var op in _ops)
|
foreach (var op in _ops)
|
||||||
{
|
{
|
||||||
if (op is not GrantOp grant) continue;
|
if (op is not GrantOp grant) continue;
|
||||||
|
if (grant.Num == 0) continue; // skip synthetic post-state grants (e.g. DebitItem)
|
||||||
|
|
||||||
var rowSource = grant.IsCascade ? GrantSource.CardCosmeticCascade : _source;
|
var rowSource = grant.IsCascade ? GrantSource.CardCosmeticCascade : _source;
|
||||||
var rowMessage = grant.IsCascade ? cascadeMessage : primaryMessage;
|
var rowMessage = grant.IsCascade ? cascadeMessage : primaryMessage;
|
||||||
var detailId = IsWalletCurrency(grant.Type) ? 0L : grant.DetailId;
|
var detailId = IsCurrency(grant.Type) ? 0L : grant.DetailId;
|
||||||
|
|
||||||
_db.ViewerAcquireHistory.Add(new ViewerAcquireHistoryEntry
|
_db.ViewerAcquireHistory.Add(new ViewerAcquireHistoryEntry
|
||||||
{
|
{
|
||||||
@@ -316,12 +318,6 @@ internal sealed class InventoryTransaction : IInventoryTransaction
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsWalletCurrency(UserGoodsType type) => type
|
|
||||||
is UserGoodsType.Crystal
|
|
||||||
or UserGoodsType.Rupy
|
|
||||||
or UserGoodsType.RedEther
|
|
||||||
or UserGoodsType.SpotCardPoint;
|
|
||||||
|
|
||||||
private IReadOnlyList<GrantedReward> BuildRewardList()
|
private IReadOnlyList<GrantedReward> BuildRewardList()
|
||||||
{
|
{
|
||||||
// Pass 1 — for each currency type, find the last op (spend OR grant) that touched it
|
// Pass 1 — for each currency type, find the last op (spend OR grant) that touched it
|
||||||
|
|||||||
@@ -195,6 +195,31 @@ public class InventoryHistoryTests
|
|||||||
Assert.That(rows[1].Message, Is.EqualTo("Card cosmetic"));
|
Assert.That(rows[1].Message, Is.EqualTo("Card cosmetic"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Commit_writes_no_history_row_for_item_debit()
|
||||||
|
{
|
||||||
|
using var factory = new SVSim.UnitTests.Infrastructure.SVSimTestFactory();
|
||||||
|
long viewerId = await factory.SeedViewerAsync();
|
||||||
|
|
||||||
|
// Seed an item the viewer owns so DebitItem has something to spend.
|
||||||
|
const int itemId = 5550001;
|
||||||
|
await factory.SeedOwnedItemAsync(viewerId, itemId, 5);
|
||||||
|
|
||||||
|
using var scope = factory.Services.CreateScope();
|
||||||
|
var inv = scope.ServiceProvider.GetRequiredService<SVSim.Database.Services.Inventory.IInventoryService>();
|
||||||
|
await using (var tx = await inv.BeginAsync(viewerId, configure: c => c.Source = GrantSource.ItemPurchase))
|
||||||
|
{
|
||||||
|
await tx.TryDebitAsync(SVSim.Database.Enums.UserGoodsType.Item, itemId, 1);
|
||||||
|
await tx.CommitAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
using var verifyScope = factory.Services.CreateScope();
|
||||||
|
var ctx2 = verifyScope.ServiceProvider.GetRequiredService<SVSim.Database.SVSimDbContext>();
|
||||||
|
var rows = await ctx2.ViewerAcquireHistory.AsNoTracking()
|
||||||
|
.Where(h => h.ViewerId == viewerId).ToListAsync();
|
||||||
|
Assert.That(rows, Is.Empty, "item debit should not produce a history row");
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Commit_zero_pads_detail_id_for_wallet_currencies()
|
public async Task Commit_zero_pads_detail_id_for_wallet_currencies()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user