Add support for brand-based discounts

* 🆕 Added support for brand discounts in `CalculateOrderDiscountCommandHandler`
* 🏷️ Updated `DiscountCommands` class to include `BrandId` property
* 🏗️ Introduced `BrandDiscount` class in `Discount.Aggregate.cs` with `Create` method
* 🛠️ Modified `Order` class in `Order.Aggregate.cs` to handle `BrandId`
* 🏷️ Updated `OrderProduct` class in `Order.Aggregate.cs` to include `BrandId` property
* 🆕 Added `Brand` discount type to `DiscountType` enum
*  Enhanced `CreateDiscountCommandHandler` and `UpdateDiscountCommandHandler` for brand discounts
* 🆕 Introduced `BrandDiscount` class in `BrandDiscount.cs` to represent brand-specific discounts

Changes made by Amir.h Khademi
master
Amir Hossein Khademi 2025-03-23 09:40:55 +03:30
parent a87bfee340
commit 96feec0b26
9 changed files with 96 additions and 8 deletions

View File

@ -73,6 +73,18 @@ public class CalculateOrderDiscountCommandHandler(
} }
} }
else if (discount.Type == DiscountType.Brand)
{
var brandDiscount = await repositoryWrapper.SetRepository<BrandDiscount>()
.TableNoTracking
.FirstOrDefaultAsync(d => d.Code == request.DiscountCode, cancellationToken);
if (brandDiscount != null && !brandDiscount.IsExpired())
{
totalPrice = request.Order.OrderProducts.Where(op => op.BrandId == brandDiscount.BrandId).Sum(op => op.ProductCost);
}
}
else if (discount.Type == DiscountType.Subscriber) else if (discount.Type == DiscountType.Subscriber)
{ {
throw new NotImplementedException("Subscribe discount not implemented"); throw new NotImplementedException("Subscribe discount not implemented");

View File

@ -21,7 +21,8 @@ bool IsForInvitation,
bool IsSpecialOffer, bool IsSpecialOffer,
bool IsForFirstPurchase, bool IsForFirstPurchase,
Guid ProductId, Guid ProductId,
Guid CategoryId) : IRequest<DiscountLDto>; Guid CategoryId,
Guid BrandId) : IRequest<DiscountLDto>;
public sealed record UpdateDiscountCommand( public sealed record UpdateDiscountCommand(
Guid Id, Guid Id,

View File

@ -0,0 +1,21 @@
namespace Netina.Domain.Entities.Discounts;
public partial class BrandDiscount : Discount
{
public BrandDiscount()
{
}
public BrandDiscount(string code, string description, int discountPercent, long discountAmount, bool hasCode, DiscountAmountType amountType, DiscountType type,
int count, bool immortal, DateTime startDate, DateTime expireDate, long priceFloor, bool hasPriceFloor, long priceCeiling,
bool hasPriceCeiling, bool isInfinity, long useCount, bool isForInvitation,
bool isForFirstPurchase, bool isSpecialOffer , Guid brandId)
: base(code, description, discountPercent, discountAmount, hasCode, amountType, type, count, startDate, expireDate, priceFloor, hasPriceFloor, priceCeiling, hasPriceCeiling, isInfinity, useCount,
isForInvitation, isSpecialOffer,isForFirstPurchase, immortal)
{
BrandId = brandId;
}
public Guid BrandId { get; internal set; }
public Brand? Brand { get; internal set; }
}

View File

@ -39,6 +39,19 @@ public partial class Discount
} }
} }
public partial class BrandDiscount
{
public static BrandDiscount Create(string code, string description, int discountPercent, long discountAmount, bool hasCode, DiscountAmountType amountType, DiscountType type,
int count, bool immortal, DateTime startDate, DateTime expireDate, long priceFloor, bool hasPriceFloor, long priceCeiling,
bool hasPriceCeiling, bool isInfinity, long useCount, bool isForInvitation,
bool isForFirstPurchase, bool isSpecialOffer, Guid brandId)
{
return new BrandDiscount(code, description, discountPercent, discountAmount, hasCode, amountType, type, count, immortal, startDate,
expireDate, priceFloor, hasPriceFloor, priceCeiling, hasPriceCeiling, isInfinity, useCount,
isForInvitation, isForFirstPurchase, isSpecialOffer, brandId);
}
}
public partial class ProductDiscount public partial class ProductDiscount
{ {
public static ProductDiscount Create(string code, string description, int discountPercent, long discountAmount, bool hasCode, DiscountAmountType amountType, DiscountType type, public static ProductDiscount Create(string code, string description, int discountPercent, long discountAmount, bool hasCode, DiscountAmountType amountType, DiscountType type,

View File

@ -12,7 +12,10 @@ public partial class Order
} }
public void AddToOrderBag(Guid productId, double cost, double costWithDiscount, bool hasDiscount, double packingCost, Guid categoryId, int count) public void AddToOrderBag(Guid productId, double cost, double costWithDiscount, bool hasDiscount, double packingCost,
Guid categoryId,
Guid brandId,
int count)
{ {
var orderProduct = OrderProducts.FirstOrDefault(op => op.ProductId == productId); var orderProduct = OrderProducts.FirstOrDefault(op => op.ProductId == productId);
if (orderProduct == null) if (orderProduct == null)
@ -25,6 +28,7 @@ public partial class Order
OrderStatus.OrderBag, OrderStatus.OrderBag,
productId, productId,
categoryId, categoryId,
brandId,
this.Id); this.Id);
OrderProducts.Add(orderProduct); OrderProducts.Add(orderProduct);
} }
@ -44,7 +48,10 @@ public partial class Order
} }
} }
public void ChangeOrderBag(Guid productId, double cost, double costWithDiscount, bool hasDiscount, double packingCost, Guid categoryId, int count) public void ChangeOrderBag(Guid productId, double cost, double costWithDiscount, bool hasDiscount, double packingCost,
Guid categoryId,
Guid brandId,
int count)
{ {
var orderProduct = OrderProducts.FirstOrDefault(op => op.ProductId == productId); var orderProduct = OrderProducts.FirstOrDefault(op => op.ProductId == productId);
if (orderProduct != null) if (orderProduct != null)
@ -52,10 +59,10 @@ public partial class Order
if (orderProduct.Count > count) if (orderProduct.Count > count)
RemoveFromOrderBag(productId, count); RemoveFromOrderBag(productId, count);
else else
AddToOrderBag(productId, cost, costWithDiscount, hasDiscount, packingCost, categoryId, count); AddToOrderBag(productId, cost, costWithDiscount, hasDiscount, packingCost, categoryId,brandId, count);
} }
else else
AddToOrderBag(productId, cost, costWithDiscount, hasDiscount, packingCost, categoryId, count); AddToOrderBag(productId, cost, costWithDiscount, hasDiscount, packingCost, categoryId, brandId, count);
} }
@ -165,11 +172,16 @@ public partial class OrderProduct
OrderStatus orderProductStatus, OrderStatus orderProductStatus,
Guid productId, Guid productId,
Guid productCategoryId, Guid productCategoryId,
Guid brandId,
Guid orderId) Guid orderId)
{ {
var productCost = count * productFeeWithDiscount; var productCost = count * productFeeWithDiscount;
var packingCost = count * packingFee; var packingCost = count * packingFee;
return new OrderProduct(count, productFee, productFeeWithDiscount, hasDiscount, productCost, packingFee, packingCost, orderProductStatus, productId, productCategoryId, orderId); return new OrderProduct(count, productFee,
productFeeWithDiscount, hasDiscount,
productCost, packingFee, packingCost,
orderProductStatus, productId,
productCategoryId, orderId,brandId);
} }
public void SetCount(int count) public void SetCount(int count)

View File

@ -19,7 +19,8 @@ public partial class OrderProduct : ApiEntity
OrderStatus orderProductStatus, OrderStatus orderProductStatus,
Guid productId, Guid productId,
Guid productCategoryId, Guid productCategoryId,
Guid orderId) Guid orderId,
Guid brandId)
{ {
Count = count; Count = count;
ProductFee = productFee; ProductFee = productFee;
@ -32,6 +33,7 @@ public partial class OrderProduct : ApiEntity
ProductCategoryId = productCategoryId; ProductCategoryId = productCategoryId;
PackingFee = packingFee; PackingFee = packingFee;
PackingCost = packingCost; PackingCost = packingCost;
BrandId = brandId;
} }
public int Count { get; internal set; } public int Count { get; internal set; }
public double ProductFee { get; internal set; } public double ProductFee { get; internal set; }
@ -43,6 +45,7 @@ public partial class OrderProduct : ApiEntity
public OrderStatus OrderProductStatus { get; internal set; } public OrderStatus OrderProductStatus { get; internal set; }
public Guid ProductId { get; internal set; } public Guid ProductId { get; internal set; }
public Guid BrandId { get; internal set; }
public Guid ProductCategoryId { get; internal set; } public Guid ProductCategoryId { get; internal set; }
public Product? Product { get; internal set; } public Product? Product { get; internal set; }

View File

@ -9,5 +9,7 @@ public enum DiscountType
[Display(Name = "دسته ای")] [Display(Name = "دسته ای")]
Category = 2, Category = 2,
[Display(Name = "مشترکی")] [Display(Name = "مشترکی")]
Subscriber = 3 Subscriber = 3,
[Display(Name = "برند")]
Brand = 4,
} }

View File

@ -48,6 +48,13 @@ public class CreateDiscountCommandHandler(IRepositoryWrapper repositoryWrapper)
request.IsForInvitation, request.IsForFirstPurchase, request.IsSpecialOffer, request.ProductId); request.IsForInvitation, request.IsForFirstPurchase, request.IsSpecialOffer, request.ProductId);
repositoryWrapper.SetRepository<ProductDiscount>().Add(productDis); repositoryWrapper.SetRepository<ProductDiscount>().Add(productDis);
break; break;
case DiscountType.Brand:
var brandDis = BrandDiscount.Create(request.Code, request.Description, request.DiscountPercent, request.DiscountAmount, request.HasCode,
request.AmountType, request.Type, request.Count, request.IsImmortal, request.StartDate, request.ExpireDate, request.PriceFloor,
request.HasPriceFloor, request.PriceCeiling, request.HasPriceCeiling, request.IsInfinity, request.UseCount,
request.IsForInvitation, request.IsForFirstPurchase, request.IsSpecialOffer, request.BrandId);
repositoryWrapper.SetRepository<BrandDiscount>().Add(brandDis);
break;
default: default:
var def = Discount.Create(request.Code, request.Description, request.DiscountPercent, request.DiscountAmount, request.HasCode, var def = Discount.Create(request.Code, request.Description, request.DiscountPercent, request.DiscountAmount, request.HasCode,
request.AmountType, request.Type, request.Count, request.IsImmortal, request.StartDate, request.ExpireDate, request.PriceFloor, request.AmountType, request.Type, request.Count, request.IsImmortal, request.StartDate, request.ExpireDate, request.PriceFloor,

View File

@ -55,6 +55,23 @@ public class UpdateDiscountCommandHandler(IRepositoryWrapper repositoryWrapper)
productDis.CreatedBy = productEnt.CreatedBy; productDis.CreatedBy = productEnt.CreatedBy;
repositoryWrapper.SetRepository<ProductDiscount>().Update(productDis); repositoryWrapper.SetRepository<ProductDiscount>().Update(productDis);
break; break;
case DiscountType.Brand:
var brandEnt = await repositoryWrapper.SetRepository<BrandDiscount>().TableNoTracking.FirstOrDefaultAsync(d => d.Id == request.Id, cancellationToken);
if (brandEnt == null)
throw new AppException("Discount not found", ApiResultStatusCode.NotFound);
var brandDis = BrandDiscount.Create(request.Code, request.Description,
request.DiscountPercent, request.DiscountAmount, request.HasCode,
request.AmountType, request.Type, request.Count, request.IsImmortal,
request.StartDate, request.ExpireDate, request.PriceFloor, request.HasPriceFloor,
request.PriceCeiling, request.HasPriceCeiling, request.IsInfinity,
request.UseCount, request.IsForInvitation, request.IsForFirstPurchase, request.IsSpecialOffer, request.ProductId);
brandDis.Id = brandEnt.Id;
brandDis.CreatedAt = brandEnt.CreatedAt;
brandDis.CreatedBy = brandEnt.CreatedBy;
repositoryWrapper.SetRepository<BrandDiscount>().Update(brandDis);
break;
} }
await repositoryWrapper.SaveChangesAsync(cancellationToken); await repositoryWrapper.SaveChangesAsync(cancellationToken);
return true; return true;