feat(journal): edit a placed bet
Adds an edit flow to the bet journal — an Edit button per row repurposes the inline entry form (edit mode), saving via a new UpdatePlacedBetUseCase that preserves the original placed-at and re-grades the outcome against the result (so a changed selection/event re-settles). A cancel affordance + an editing banner make the mode obvious; the submit button switches to "Save changes". - UpdatePlacedBetUseCase (loads existing for PlacedAt, validates the event, re-grades) + IBetJournalService.UpdateAsync + service impl; Journal page edit-mode state + per-row Edit button; en/ru resx. - 3 tests: unknown-bet, unknown-event, and preserve-PlacedAt + re-grade.
This commit is contained in:
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Edits an existing <see cref="PlacedBet"/> in the journal — selection, stake, or
|
||||
/// notes. The original <see cref="PlacedBet.PlacedAt"/> is preserved; the outcome is
|
||||
/// re-graded from scratch (so changing the selection or event re-settles correctly).
|
||||
/// </summary>
|
||||
public sealed class UpdatePlacedBetUseCase
|
||||
{
|
||||
private readonly IPlacedBetRepository _bets;
|
||||
private readonly IEventRepository _events;
|
||||
private readonly IResultRepository _results;
|
||||
private readonly ILogger<UpdatePlacedBetUseCase> _logger;
|
||||
|
||||
public UpdatePlacedBetUseCase(
|
||||
IPlacedBetRepository bets,
|
||||
IEventRepository events,
|
||||
IResultRepository results,
|
||||
ILogger<UpdatePlacedBetUseCase> 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));
|
||||
}
|
||||
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// The bet id is unknown, or the (possibly changed) event isn't in the store.
|
||||
/// </exception>
|
||||
public async Task<PlacedBet> 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user