Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove item #2

Merged
merged 2 commits into from
May 26, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.8.1" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.8.1" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.8.1" />
<PackageVersion Include="AntDesign" Version="0.15.5" />
<PackageVersion Include="AntDesign" Version="0.19.0" />
<PackageVersion Include="Ardalis.GuardClauses" Version="4.5.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="coverlet.msbuild" Version="6.0.2" />
Expand Down
7 changes: 7 additions & 0 deletions src/Cashier/NCafe.Cashier.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@
})
.WithName("AddItemToOrder");

app.MapPost("/orders/remove-item", async (IMediator mediator, RemoveItemFromOrder command) =>
{
await mediator.Send(command);
return Results.Accepted();
})
.WithName("RemoveItemFromOrder");

app.MapPost("/orders/place", async (IMediator mediator, PlaceOrder command) =>
{
await mediator.Send(command);
Expand Down
106 changes: 106 additions & 0 deletions src/Cashier/NCafe.Cashier.Domain.Tests/Commands/RemoveItemTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using NCafe.Cashier.Domain.Commands;
using NCafe.Cashier.Domain.Entities;
using NCafe.Cashier.Domain.Exceptions;
using NCafe.Cashier.Domain.ValueObjects;
using NCafe.Core.Repositories;
using System.Threading;

namespace NCafe.Cashier.Domain.Tests.Commands;

public class RemoveItemTests
{
private readonly RemoveItemFromOrderHandler _sut;
private readonly IRepository _repository;

public RemoveItemTests()
{
_repository = A.Fake<IRepository>();
_sut = new RemoveItemFromOrderHandler(_repository);
}

[Fact]
public async Task ShouldSaveOrder()
{
// Arrange
var orderId = Guid.NewGuid();
var productId = Guid.NewGuid();
var quantity = 1;
aforesti marked this conversation as resolved.
Show resolved Hide resolved
var command = new RemoveItemFromOrder(orderId, productId, quantity);
var order = new Order(orderId, "cashier-1", DateTimeOffset.UtcNow);
order.AddItem(new OrderItem(productId, "product1", 1, 5));

A.CallTo(() => _repository.GetById<Order>(orderId))
.Returns(Task.FromResult(order));

// Act
await _sut.Handle(command, CancellationToken.None);

// Assert
A.CallTo(() => _repository.Save(
A<Order>.That.Matches(o => o.Id == orderId &&
o.Items.All(i => i.ProductId != productId))))
.MustHaveHappenedOnceExactly();
}

[Fact]
public async Task GivenOrderNotFound_ShouldThrow()
{
// Arrange
var orderId = Guid.NewGuid();
var productId = Guid.NewGuid();
var quantity = 1;
var command = new RemoveItemFromOrder(orderId, productId, quantity);

// Act
var exception = await Record.ExceptionAsync(() => _sut.Handle(command, CancellationToken.None));

// Assert
exception.ShouldBeOfType<OrderNotFoundException>();
}

[Fact]
public async Task GivenRemovingMoreItemsThanOrdered_ShouldThrow()
{
// Arrange
var orderId = Guid.NewGuid();
var productId = Guid.NewGuid();
var quantity = 2;
var command = new RemoveItemFromOrder(orderId, productId, quantity);
var order = new Order(orderId, "cashier-1", DateTimeOffset.UtcNow);
order.AddItem(new OrderItem(productId, "product1", 1, 5));

A.CallTo(() => _repository.GetById<Order>(orderId))
.Returns(Task.FromResult(order));

// Act
var exception = await Record.ExceptionAsync(() => _sut.Handle(command, CancellationToken.None));

// Assert
exception.ShouldBeOfType<CannotRemoveMoreItemsThanOrderedException>();
}

[Fact]
public async Task GivenRemovingLessItemsThanOrdered_ShouldSaveOrder()
{
// Arrange
var orderId = Guid.NewGuid();
var productId = Guid.NewGuid();
var quantity = 1;
var command = new RemoveItemFromOrder(orderId, productId, quantity);
var order = new Order(orderId, "cashier-1", DateTimeOffset.UtcNow);
order.AddItem(new OrderItem(productId, "product1", 2, 5));

A.CallTo(() => _repository.GetById<Order>(orderId))
.Returns(Task.FromResult(order));

// Act
await _sut.Handle(command, CancellationToken.None);

// Assert
A.CallTo(() => _repository.Save(
A<Order>.That.Matches(o => o.Id == orderId &&
o.Items.Any(i => i.ProductId == productId && i.Quantity == 1))))
.MustHaveHappenedOnceExactly();
}

}
25 changes: 25 additions & 0 deletions src/Cashier/NCafe.Cashier.Domain/Commands/RemoveItemFromOrder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using MediatR;
using NCafe.Cashier.Domain.Entities;
using NCafe.Cashier.Domain.Exceptions;
using NCafe.Cashier.Domain.ReadModels;
using NCafe.Cashier.Domain.ValueObjects;
using NCafe.Core.ReadModels;
using NCafe.Core.Repositories;

namespace NCafe.Cashier.Domain.Commands;

public record RemoveItemFromOrder(Guid OrderId, Guid ProductId, int Quantity) : IRequest;

internal sealed class RemoveItemFromOrderHandler(IRepository repository) : IRequestHandler<RemoveItemFromOrder>
{
private readonly IRepository _repository = repository;

public async Task Handle(RemoveItemFromOrder command, CancellationToken cancellationToken)
{
var order = await _repository.GetById<Order>(command.OrderId) ?? throw new OrderNotFoundException(command.OrderId);

order.RemoveItem(command.ProductId, command.Quantity);

await _repository.Save(order);
}
}
45 changes: 44 additions & 1 deletion src/Cashier/NCafe.Cashier.Domain/Entities/Order.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,28 @@ public void AddItem(OrderItem orderItem)
RaiseEvent(new OrderItemAdded(Id, orderItem.ProductId, orderItem.Quantity, orderItem.Name, orderItem.Price));
}

public void RemoveItem(Guid productId, int quantity)
{
if (Status != OrderStatus.New)
aforesti marked this conversation as resolved.
Show resolved Hide resolved
{
throw new CannotRemoveItemFromOrderException(Id, productId);
}

var item = Items.FirstOrDefault(i => i.ProductId == productId);

if (item is null)
{
throw new OrderItemNotFoundException(Id, productId);
}

if (item.Quantity < quantity)
{
throw new CannotRemoveMoreItemsThanOrderedException(Id, productId, quantity);
}

RaiseEvent(new OrderItemRemoved(Id, productId, quantity));
}

public void PlaceOrder(Customer customer, DateTimeOffset placedAt)
{
Guard.Against.Null(customer);
Expand Down Expand Up @@ -79,8 +101,29 @@ private void Apply(OrderCreated @event)

private void Apply(OrderItemAdded @event)
{
var item = _items.FirstOrDefault(i => i.ProductId == @event.ProductId);
if (item is not null)
{
item.IncreaseQuantity(@event.Quantity);
Total = Items.Sum(i => i.Total);
return;
}

_items.Add(new OrderItem(@event.ProductId, @event.Name, @event.Quantity, @event.Price));
Total = Items.Sum(i => i.Price);
Total = Items.Sum(i => i.Total);
}

private void Apply(OrderItemRemoved @event)
{
var item = _items.First(i => i.ProductId == @event.ProductId);
if (item.Quantity == @event.Quantity)
{
_items.Remove(item);
Total = Items.Sum(i => i.Total);
return;
}
item.DecreaseQuantity(@event.Quantity);
Total = Items.Sum(i => i.Total);
}

private void Apply(OrderPlaced @event)
Expand Down
16 changes: 16 additions & 0 deletions src/Cashier/NCafe.Cashier.Domain/Events/OrderItemRemoved.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using NCafe.Core.Domain;

namespace NCafe.Cashier.Domain.Events;

public sealed record OrderItemRemoved : Event
{
public OrderItemRemoved(Guid id, Guid productId, int quantity)
{
Id = id;
ProductId = productId;
Quantity = quantity;
}

public Guid ProductId { get; }
public int Quantity { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using NCafe.Core.Exceptions;

namespace NCafe.Cashier.Domain.Exceptions;

public class CannotRemoveItemFromOrderException(Guid orderId, Guid productId)
: DomainException($"Cannot remove item '{productId}' from order '{orderId}' when status is not New.")
{
public Guid OrderId { get; } = orderId;
public Guid ProductId { get; } = productId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using NCafe.Core.Exceptions;

namespace NCafe.Cashier.Domain.Exceptions;

public class CannotRemoveMoreItemsThanOrderedException(Guid orderId, Guid productId, int quantity)
: DomainException($"Cannot remove more items ({quantity}) than existing in the order '{orderId}'.")
{
public Guid OrderId { get; } = orderId;
public Guid ProductId { get; } = productId;
public int Quantity { get; } = quantity;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using NCafe.Core.Exceptions;

namespace NCafe.Cashier.Domain.Exceptions;

public class OrderItemNotFoundException(Guid orderId, Guid productId)
: DomainException($"Order item {productId} was not found in order '{orderId}'.")
{
public Guid OrderId { get; } = orderId;
public Guid ProductId { get; } = productId;
}
15 changes: 14 additions & 1 deletion src/Cashier/NCafe.Cashier.Domain/ValueObjects/OrderItem.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Ardalis.GuardClauses;
using NCafe.Cashier.Domain.Exceptions;

namespace NCafe.Cashier.Domain.ValueObjects;

Expand All @@ -14,6 +15,18 @@ public OrderItem(Guid productId, string name, int quantity, decimal price)

public Guid ProductId { get; }
public string Name { get; }
public int Quantity { get; }
public int Quantity { get; private set; }
public decimal Price { get; }

public decimal Total => Quantity * Price;

public void IncreaseQuantity(int quantity)
{
Quantity += quantity;
}

public void DecreaseQuantity(int quantity)
{
Quantity -= quantity;
}
}
5 changes: 4 additions & 1 deletion src/UI/NCafe.Web/Models/Order.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
namespace NCafe.Web.Models;
using System.ComponentModel.DataAnnotations;

namespace NCafe.Web.Models;

public class Order
{
public Guid Id { get; set; }
[Required]
public string CustomerName { get; set; }
public List<OrderItem> Items { get; set; } = new();
public decimal Total => Items.Sum(x => x.Total);
Expand Down
Loading