Add domestic vendor push infrastructure

This commit is contained in:
2026-07-26 01:45:59 +08:00
parent 7cca34b331
commit 0738953e6d
77 changed files with 6470 additions and 855 deletions
@@ -13,7 +13,10 @@ namespace MiaoJiZhang.Api.Controllers;
[ApiController]
[Authorize]
[Route("api/transactions")]
public class TransactionsController(AppDbContext db, LedgerResolver ledgers) : ControllerBase
public class TransactionsController(
AppDbContext db,
LedgerResolver ledgers,
BudgetPushService budgetPush) : ControllerBase
{
private long Uid => long.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub")!);
@@ -68,13 +71,22 @@ public class TransactionsController(AppDbContext db, LedgerResolver ledgers) : C
UpdatedAt = DateTime.UtcNow,
};
db.Transactions.Add(tx);
await using var writeScope = await db.Database.BeginTransactionAsync();
try
{
await db.SaveChangesAsync();
if (tx.Type == TransactionType.Expense)
{
await budgetPush.EvaluateAsync(Uid,
[new BudgetExpenseChange(tx.LedgerId, tx.CategoryId, tx.OccurredAt, tx.Amount)]);
await db.SaveChangesAsync();
}
await writeScope.CommitAsync();
return Ok(ToDto(tx, cat));
}
catch (DbUpdateException) when (clientRequestId is not null)
{
await writeScope.RollbackAsync();
// Another channel may have committed the same recognition candidate
// after the initial lookup. Resolve the unique-key race as idempotent success.
db.Entry(tx).State = EntityState.Detached;
@@ -161,6 +173,17 @@ public class TransactionsController(AppDbContext db, LedgerResolver ledgers) : C
}
try
{
await db.SaveChangesAsync(ct);
var expenseChanges = mapped
.Where(item => !existing.ContainsKey(item.Transaction.ClientRequestId ?? "") &&
item.Transaction.Type == TransactionType.Expense)
.Select(item => new BudgetExpenseChange(
item.Transaction.LedgerId,
item.Transaction.CategoryId,
item.Transaction.OccurredAt,
item.Transaction.Amount))
.ToList();
await budgetPush.EvaluateAsync(Uid, expenseChanges, ct);
await db.SaveChangesAsync(ct);
await transactionScope.CommitAsync(ct);
return Ok(mapped.Select(item => new RecognitionBatchTransactionDto(
@@ -223,6 +246,10 @@ public class TransactionsController(AppDbContext db, LedgerResolver ledgers) : C
message = "账单已在其他设备修改,请选择保留本地或云端版本",
server = ToDto(tx, await db.Categories.FindAsync(tx.CategoryId) ?? category),
});
var expenseChanges = new List<BudgetExpenseChange>();
if (tx.Type == TransactionType.Expense)
expenseChanges.Add(new BudgetExpenseChange(
tx.LedgerId, tx.CategoryId, tx.OccurredAt, -tx.Amount));
tx.LedgerId = ledgerId.Value;
tx.CategoryId = category.Id;
tx.Category = category;
@@ -232,7 +259,14 @@ public class TransactionsController(AppDbContext db, LedgerResolver ledgers) : C
tx.PaymentMethod = req.PaymentMethod?.Trim();
tx.OccurredAt = NormalizeOccurredAt(req.OccurredAt);
tx.UpdatedAt = DateTime.UtcNow;
if (tx.Type == TransactionType.Expense)
expenseChanges.Add(new BudgetExpenseChange(
tx.LedgerId, tx.CategoryId, tx.OccurredAt, tx.Amount));
await using var writeScope = await db.Database.BeginTransactionAsync();
await db.SaveChangesAsync();
await budgetPush.EvaluateAsync(Uid, expenseChanges);
await db.SaveChangesAsync();
await writeScope.CommitAsync();
return Ok(ToDto(tx, category));
}
@@ -291,7 +325,15 @@ public class TransactionsController(AppDbContext db, LedgerResolver ledgers) : C
}); tx.IsDeleted = false;
tx.DeletedAt = null;
tx.UpdatedAt = DateTime.UtcNow;
await using var writeScope = await db.Database.BeginTransactionAsync();
await db.SaveChangesAsync();
if (tx.Type == TransactionType.Expense)
{
await budgetPush.EvaluateAsync(Uid,
[new BudgetExpenseChange(tx.LedgerId, tx.CategoryId, tx.OccurredAt, tx.Amount)]);
await db.SaveChangesAsync();
}
await writeScope.CommitAsync();
return Ok(ToDto(tx, tx.Category));
}