Skip to content

Commit 3137ec1

Browse files
committed
feat: Create product catalog and order system
- Added migrations for `Product`, `ProductCategory`, `Order`, and `OrderItem` entities. - Introduced relationships, indexes, and constraints to support the product catalog and order management. - Removed obsolete `CompanyIsNullException` as it is no longer required.
1 parent 7323ac9 commit 3137ec1

29 files changed

+4267
-27
lines changed

src/SnackFlow.Application/Events/Handlers/CompanyWelcomeEmailRequestedEventHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
namespace SnackFlow.Application.Events.Handlers;
99

10-
public sealed class CompanyWelcomeEmailRequestedEventHandler(
10+
internal sealed class CompanyWelcomeEmailRequestedEventHandler(
1111
IEmailScheduler emailScheduler)
1212
: IDomainEventHandler<CompanyWelcomeEmailRequestedEvent>
1313
{

src/SnackFlow.Application/Features/Auths/Commands/Logout/LogoutCommandHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public async ValueTask<Result> Handle(LogoutCommand command, CancellationToken c
1818
try
1919
{
2020
var userId = currentUserService.GetUserId()?.ToString()
21-
?? throw new BadRequestException(ErrorMessage.Invalid.IdIsNull);
21+
?? throw new BadRequestException(ErrorMessage.Invalid.UserId);
2222

2323
await authService.UpdateSecurityStampAndGetUserAsync(userId);
2424
return Result.Success();

src/SnackFlow.Application/Features/Companies/Queries/GetCompanyById/GetCompanyByIdQueryValidator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ public GetCompanyByIdQueryValidator()
99
{
1010
RuleFor(x => x.CompanyId)
1111
.NotEmpty()
12-
.WithMessage(ErrorMessage.Invalid.IdIsNull);
12+
.WithMessage(ErrorMessage.Invalid.CompanyId);
1313
}
1414
}

src/SnackFlow.Application/Features/Users/Commands/CreateUser/CreateUserCommandValidator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public CreateUserCommandValidator()
2727
{
2828
RuleFor(x => x.CompanyId)
2929
.NotEqual(Guid.Empty)
30-
.WithMessage(ErrorMessage.Invalid.IdIsNull);
30+
.WithMessage(ErrorMessage.Invalid.CompanyId);
3131
});
3232

3333
RuleFor(x => x.UserName)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using SnackFlow.Domain.Abstractions;
2+
using SnackFlow.Domain.Abstractions.ValueObjects;
3+
using SnackFlow.Domain.ValueObjects.RandomToken;
4+
5+
namespace SnackFlow.Domain.Entities;
6+
7+
public sealed class Order
8+
: BaseEntity, IAggregateRoot, IHasRandomToken
9+
{
10+
#region Properties
11+
12+
public RandomToken Token { get; private set; }
13+
public Guid CompanyId { get; private set; }
14+
public Guid AttendantId { get; private set; }
15+
private readonly List<OrderItem> _items = [];
16+
public decimal SubTotal => _items.Sum(x => x.SubTotal);
17+
public decimal TotalDiscounts => _items.Sum(x => x.DiscountAmount);
18+
public decimal TotalAmount => SubTotal - TotalDiscounts;
19+
20+
#endregion
21+
22+
#region Navigation Properties
23+
24+
public Company Company { get; private set; } = null!;
25+
public User Attendant { get; private set; } = null!;
26+
public IReadOnlyCollection<OrderItem> ItemsReadOnly => _items.AsReadOnly();
27+
28+
#endregion
29+
30+
#region Constructors
31+
32+
private Order() : base(Guid.Empty)
33+
{
34+
Token = null!;
35+
CompanyId = Guid.Empty;
36+
AttendantId = Guid.Empty;
37+
}
38+
39+
private Order(Guid companyId, Guid attendantId)
40+
: base(Guid.CreateVersion7())
41+
{
42+
CompanyId = companyId;
43+
AttendantId = attendantId;
44+
Token = RandomToken.Create();
45+
}
46+
47+
#endregion
48+
49+
#region Factories
50+
51+
public static Order Create(Guid companyId, Guid attendantId)
52+
=> new(companyId, attendantId);
53+
54+
#endregion
55+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using SnackFlow.Domain.Abstractions.ValueObjects;
2+
using SnackFlow.Domain.Constants;
3+
using SnackFlow.Domain.ValueObjects.Discount;
4+
using SnackFlow.Domain.ValueObjects.Discount.Exceptions;
5+
using SnackFlow.Domain.ValueObjects.Money;
6+
using SnackFlow.Domain.ValueObjects.Quantity;
7+
8+
namespace SnackFlow.Domain.Entities;
9+
10+
public sealed class OrderItem
11+
: BaseEntity, IHasDiscount, IHasQuantity, IHasUnitPrice
12+
{
13+
#region Properties
14+
15+
public Guid OrderId { get; private set; }
16+
public Guid ProductId { get; private set; }
17+
public string ProductName { get; private set; }
18+
public Money UnitPrice { get; private set; }
19+
public Quantity Quantity { get; private set; }
20+
public Discount? ItemDiscount { get; private set; }
21+
public decimal SubTotal => UnitPrice.Multiply(Quantity);
22+
public decimal DiscountAmount => ItemDiscount?.CalculateDiscount(SubTotal) ?? 0;
23+
public decimal TotalPrice => SubTotal - DiscountAmount;
24+
25+
#endregion
26+
27+
#region Navigation Properties
28+
29+
public Order Order { get; private set; } = null!;
30+
public Product Product { get; private set; } = null!;
31+
32+
#endregion
33+
34+
#region Constructors
35+
36+
private OrderItem() : base(Guid.Empty)
37+
{
38+
OrderId = Guid.Empty;
39+
ProductId = Guid.Empty;
40+
ProductName = string.Empty;
41+
UnitPrice = Money.Zero();
42+
Quantity = Quantity.Zero();
43+
}
44+
45+
private OrderItem(
46+
Guid orderId,
47+
Guid productId,
48+
string productName,
49+
decimal unitPrice,
50+
int quantity
51+
) : base(Guid.CreateVersion7())
52+
{
53+
OrderId = orderId;
54+
ProductId = productId;
55+
ProductName = productName;
56+
UnitPrice = Money.Create(unitPrice);
57+
Quantity = Quantity.Create(quantity);
58+
}
59+
60+
#endregion
61+
62+
#region Factories
63+
64+
public static OrderItem Create(
65+
Guid orderId,
66+
Guid productId,
67+
string productName,
68+
decimal unitPrice,
69+
int quantity
70+
) => new(orderId, productId, productName, unitPrice, quantity);
71+
72+
#endregion
73+
74+
#region Methods
75+
76+
public void ApplyDiscount(Discount discount)
77+
{
78+
if (!discount.HasDiscount())
79+
throw new InvalidDiscountException(ErrorMessage.Discount.LessThanOrEqualToZero);
80+
81+
var calculatedDiscount = discount.CalculateDiscount(SubTotal);
82+
if (calculatedDiscount > SubTotal)
83+
throw new InvalidDiscountException(ErrorMessage.Discount.GreaterThanSubtotal);
84+
85+
ItemDiscount = discount;
86+
}
87+
88+
public void RemoveDiscount()
89+
{
90+
if (ItemDiscount != null)
91+
ItemDiscount = null;
92+
}
93+
94+
public bool HasDiscount() => ItemDiscount?.HasDiscount() == true;
95+
96+
#endregion
97+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
using SnackFlow.Domain.Abstractions;
2+
using SnackFlow.Domain.Abstractions.ValueObjects;
3+
using SnackFlow.Domain.Constants;
4+
using SnackFlow.Domain.Exceptions;
5+
using SnackFlow.Domain.ValueObjects.Money;
6+
7+
namespace SnackFlow.Domain.Entities;
8+
9+
public sealed class Product
10+
: BaseEntity, IAggregateRoot, IHasUnitPrice
11+
{
12+
#region Properties
13+
14+
public string Name { get; private set; }
15+
public string Description { get; private set; }
16+
public Money UnitPrice { get; private set; }
17+
public Guid CategoryId { get; private set; }
18+
public Guid CompanyId { get; private set; }
19+
public bool IsActive { get; private set; }
20+
public string? ImageUrl { get; private set; }
21+
22+
#endregion
23+
24+
#region Navigation Properties
25+
26+
public Company Company { get; private set; } = null!;
27+
public ProductCategory Category { get; private set; } = null!;
28+
29+
#endregion
30+
31+
#region Constructors
32+
33+
private Product() : base(Guid.Empty)
34+
{
35+
Name = string.Empty;
36+
Description = string.Empty;
37+
UnitPrice = null!;
38+
CategoryId = Guid.Empty;
39+
IsActive = false;
40+
CompanyId = Guid.Empty;
41+
}
42+
43+
private Product(
44+
string name,
45+
string description,
46+
decimal price,
47+
Guid categoryId,
48+
Guid companyId,
49+
string? imageUrl = null) : base(Guid.CreateVersion7())
50+
{
51+
Name = name;
52+
Description = description;
53+
UnitPrice = Money.CreatePrice(price);
54+
CategoryId = categoryId;
55+
CompanyId = companyId;
56+
ImageUrl = imageUrl;
57+
IsActive = true;
58+
}
59+
60+
#endregion
61+
62+
#region Factories
63+
64+
public static Product Create(
65+
string name,
66+
string description,
67+
decimal price,
68+
Guid categoryId,
69+
Guid companyId,
70+
string? imageUrl = null)
71+
{
72+
if (string.IsNullOrWhiteSpace(name))
73+
throw new ProductNameNullException(ErrorMessage.Product.NameIsNull);
74+
75+
if (string.IsNullOrWhiteSpace(description))
76+
throw new ProductDescriptionNullException(ErrorMessage.Product.DescriptionIsNull);
77+
78+
if (categoryId == Guid.Empty)
79+
throw new IdentifierNullException(ErrorMessage.Invalid.CategoryId);
80+
81+
if (companyId == Guid.Empty)
82+
throw new IdentifierNullException(ErrorMessage.Invalid.CompanyId);
83+
84+
return new Product(name, description, price, categoryId, companyId, imageUrl);
85+
}
86+
87+
#endregion
88+
89+
#region Business Methods
90+
91+
public void UpdateDetails(string name, string description, decimal price)
92+
{
93+
if (string.IsNullOrWhiteSpace(name))
94+
throw new ProductNameNullException(ErrorMessage.Product.NameIsNull);
95+
96+
if (string.IsNullOrWhiteSpace(description))
97+
throw new ProductDescriptionNullException(ErrorMessage.Product.DescriptionIsNull);
98+
99+
Name = name;
100+
Description = description;
101+
UnitPrice = Money.CreatePrice(price);
102+
}
103+
104+
public void ChangeCategory(Guid categoryId)
105+
{
106+
if (categoryId == Guid.Empty)
107+
throw new IdentifierNullException(ErrorMessage.Invalid.CategoryId);
108+
109+
CategoryId = categoryId;
110+
}
111+
112+
public void UpdateImage(string? imageUrl) => ImageUrl = imageUrl;
113+
public void Activate() => IsActive = true;
114+
public void Deactivate() => IsActive = false;
115+
116+
#endregion
117+
}

0 commit comments

Comments
 (0)