diff --git a/src/Marathon.Application/ApplicationModule.cs b/src/Marathon.Application/ApplicationModule.cs index 9e95b3a..d6180ee 100644 --- a/src/Marathon.Application/ApplicationModule.cs +++ b/src/Marathon.Application/ApplicationModule.cs @@ -37,6 +37,7 @@ public static class ApplicationModule services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/Marathon.Application/UseCases/UpdatePlacedBetUseCase.cs b/src/Marathon.Application/UseCases/UpdatePlacedBetUseCase.cs new file mode 100644 index 0000000..593bb95 --- /dev/null +++ b/src/Marathon.Application/UseCases/UpdatePlacedBetUseCase.cs @@ -0,0 +1,86 @@ +using Marathon.Application.Abstractions; +using Marathon.Domain.Entities; +using Marathon.Domain.Enums; +using Microsoft.Extensions.Logging; +using DomainEventId = Marathon.Domain.ValueObjects.EventId; + +namespace Marathon.Application.UseCases; + +/// +/// Edits an existing in the journal — selection, stake, or +/// notes. The original is preserved; the outcome is +/// re-graded from scratch (so changing the selection or event re-settles correctly). +/// +public sealed class UpdatePlacedBetUseCase +{ + private readonly IPlacedBetRepository _bets; + private readonly IEventRepository _events; + private readonly IResultRepository _results; + private readonly ILogger _logger; + + public UpdatePlacedBetUseCase( + IPlacedBetRepository bets, + IEventRepository events, + IResultRepository results, + ILogger logger) + { + _bets = bets ?? throw new ArgumentNullException(nameof(bets)); + _events = events ?? throw new ArgumentNullException(nameof(events)); + _results = results ?? throw new ArgumentNullException(nameof(results)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + /// The bet id is unknown, or the (possibly changed) event isn't in the store. + /// + public async Task ExecuteAsync( + Guid id, + DomainEventId eventId, + Bet selection, + decimal stake, + string? notes, + CancellationToken ct = default) + { + ArgumentNullException.ThrowIfNull(eventId); + ArgumentNullException.ThrowIfNull(selection); + + var existing = await _bets.GetAsync(id, ct).ConfigureAwait(false) + ?? throw new InvalidOperationException($"Cannot update unknown bet '{id}'."); + + var ev = await _events.GetAsync(eventId, ct).ConfigureAwait(false); + if (ev is null) + { + throw new InvalidOperationException( + $"Cannot point a bet at unknown event '{eventId.Value}'. " + + "The event must already be present in the scrape store."); + } + + // Preserve the original entry time; re-grade from Pending so a changed + // selection/event settles against the current result. + var toPersist = new PlacedBet( + Id: id, + EventId: eventId, + Selection: selection, + Stake: stake, + PlacedAt: existing.PlacedAt, + Outcome: BetOutcome.Pending, + Notes: notes); + + var result = await _results.GetAsync(eventId, ct).ConfigureAwait(false); + if (result is not null) + { + var graded = Marathon.Domain.Betting.BetOutcomeResolver.Resolve(toPersist.Selection, result); + if (graded is not null) + toPersist = toPersist.WithOutcome(graded.Value); + } + + await _bets.UpdateAsync(toPersist, ct).ConfigureAwait(false); + await _bets.SaveChangesAsync(ct).ConfigureAwait(false); + + _logger.LogInformation( + "UpdatePlacedBetUseCase: updated bet {BetId} on event {EventId} stake={Stake} outcome={Outcome}", + id, eventId.Value, stake, toPersist.Outcome); + + return toPersist; + } +} diff --git a/src/Marathon.UI/Pages/MyBets/Journal.razor b/src/Marathon.UI/Pages/MyBets/Journal.razor index e56e6d7..45dbd6b 100644 --- a/src/Marathon.UI/Pages/MyBets/Journal.razor +++ b/src/Marathon.UI/Pages/MyBets/Journal.razor @@ -259,14 +259,28 @@

@_formError

} + @if (_editingId is not null) + { +

@L["Journal.Editing"]

+ } +
+ @if (_editingId is not null) + { + + }
@@ -359,6 +373,14 @@ } else { +