Compare commits

...

2 Commits

Author SHA1 Message Date
Amir Hossein Khademi e445be641e Merge remote-tracking branch 'origin/subProduct' 2025-03-27 10:35:37 +03:30
Amir Hossein Khademi 7d94e8cd47 Add sub-product management feature
Introduced a new feature for managing sub-products within the application. This includes:

- Added a new tab panel for "Sub Products" in `ProductActionDialogBox.razor`.
- Created `SubProductActionDialogBox` component for sub-product creation and editing.
- Updated `ProductActionDialogBoxViewModel` to handle sub-product operations.
- Modified `Address` class and `IProductRestApi` interface to support sub-products.
- Added `SubProductController` for API requests related to sub-products.
- Introduced command and query classes for sub-product operations.
- Created `SubProductSDto` for sub-product data transfer.
- Added `SubProduct` class inheriting from `Product` with additional properties.
- Introduced `ProductDiversity` and `ColorDiversity` enums.
- Added `SubProductMapper` for mapping between `SubProduct` and `SubProductSDto`.
- Implemented command handlers for sub-product CRUD operations.
- Added migration script `20241217212716_AddSubProduct` to update the database schema.
- Updated `ProductController`, `SiteMapService`, and `Product` class to support sub-products.
- Added `SubProductActionDialogBox.razor` for sub-product UI management.
2024-12-18 02:14:07 +03:30
23 changed files with 3063 additions and 59 deletions

View File

@ -28,6 +28,10 @@ public class ProductController : ICarterModule
.RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ManageProducts)) .RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ManageProducts))
.HasApiVersion(1.0); .HasApiVersion(1.0);
group.MapGet("{productId}/sub", GetSubProductsAsync)
.WithDisplayName("Get Sub Products")
.HasApiVersion(1.0);
group.MapPut("{productId}/displayed", ChangeDisplayedAsync) group.MapPut("{productId}/displayed", ChangeDisplayedAsync)
.WithDisplayName("Change Product Display") .WithDisplayName("Change Product Display")
.RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ManageProducts)) .RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ManageProducts))
@ -78,6 +82,11 @@ public class ProductController : ICarterModule
CancellationToken cancellationToken) CancellationToken cancellationToken)
=> TypedResults.Ok(await mediator.Send(request, cancellationToken)); => TypedResults.Ok(await mediator.Send(request, cancellationToken));
// PUT:Update Entity
public async Task<IResult> GetSubProductsAsync(Guid productId, IMediator mediator,
CancellationToken cancellationToken)
=> TypedResults.Ok(await mediator.Send(new GetSubProductsQuery(productId), cancellationToken));
public async Task<IResult> ChangeDisplayedAsync(Guid productId, [FromQuery] bool beDisplayed, [FromServices] IMediator mediator, CancellationToken cancellationToken) public async Task<IResult> ChangeDisplayedAsync(Guid productId, [FromQuery] bool beDisplayed, [FromServices] IMediator mediator, CancellationToken cancellationToken)
=> TypedResults.Ok(await mediator.Send(new ChangeProductDisplayedCommand(productId, beDisplayed), cancellationToken)); => TypedResults.Ok(await mediator.Send(new ChangeProductDisplayedCommand(productId, beDisplayed), cancellationToken));

View File

@ -0,0 +1,40 @@
namespace Netina.Api.Controllers;
public class SubProductController : ICarterModule
{
public virtual void AddRoutes(IEndpointRouteBuilder app)
{
var group = app.NewVersionedApi("SubProduct")
.MapGroup($"api/sub/product");
group.MapPost("", PostAsync)
.RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ManageBlogs))
.WithDisplayName("Create SubProduct")
.HasApiVersion(1.0);
group.MapPut("", PutAsync)
.RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ManageBlogs))
.WithDisplayName("Update SubProduct")
.HasApiVersion(1.0);
group.MapDelete("{id}", DeleteAsync)
.RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ManageBlogs))
.WithDisplayName("Delete SubProduct")
.HasApiVersion(1.0);
}
// POST:Create Entity
private async Task<IResult> PostAsync([FromBody] CreateSubProductCommand request, IMediator mediator,
CancellationToken cancellationToken)
=> TypedResults.Ok(await mediator.Send(request, cancellationToken));
// PUT:Update Entity
private async Task<IResult> PutAsync([FromBody] UpdateSubProductCommand request, IMediator mediator,
CancellationToken cancellationToken)
=> TypedResults.Ok(await mediator.Send(request, cancellationToken));
// DELETE:Delete Entity
private async Task<IResult> DeleteAsync(Guid id, IMediator mediator, CancellationToken cancellationToken)
=> TypedResults.Ok(await mediator.Send(new DeleteSubProductCommand(id), cancellationToken));
}

View File

@ -324,6 +324,7 @@ public class SiteMapService(
var products = await repositoryWrapper.SetRepository<Product>() var products = await repositoryWrapper.SetRepository<Product>()
.TableNoTracking .TableNoTracking
.Where(p=>!p.IsSubProduct)
.Select(ProductMapper.ProjectToSDto) .Select(ProductMapper.ProjectToSDto)
.ToListAsync(); .ToListAsync();

View File

@ -0,0 +1,31 @@
namespace Netina.Domain.CommandQueries.Commands;
public sealed record CreateSubProductCommand(
Guid ParentId,
ProductDiversity Diversity,
string DiversityValue,
string DiversityDescription,
string PersianName,
double Cost,
double PackingCost,
int Stock,
bool HasExpressDelivery,
int MaxOrderCount,
List<StorageFileSDto> Files) : IRequest<Guid>;
public sealed record UpdateSubProductCommand(
Guid Id,
Guid ParentId,
ProductDiversity Diversity,
string DiversityValue,
string DiversityDescription,
string PersianName,
double Cost,
double PackingCost,
int Stock,
bool HasExpressDelivery,
int MaxOrderCount,
List<StorageFileSDto> Files) : IRequest<Guid>;
public sealed record DeleteSubProductCommand(Guid Id) : IRequest<bool>;

View File

@ -0,0 +1,3 @@
namespace Netina.Domain.CommandQueries.Queries;
public record GetSubProductsQuery(Guid ProductId) : IRequest<List<SubProductSDto>>;

View File

@ -0,0 +1,15 @@
namespace Netina.Domain.Dtos.SmallDtos;
public class SubProductSDto : BaseDto<SubProductSDto,SubProduct>
{
public Guid ParentId { get; set; }
public string PersianName { get; set; } = string.Empty;
public double Cost { get; set; }
public int Stock { get; set; }
public int MaxOrderCount { get; set; }
public double PackingCost { get; set; }
public bool HasExpressDelivery { get; set; }
public ProductDiversity Diversity { get; set; }
public string DiversityValue { get; set; } = string.Empty;
public string DiversityDescription { get; set; } = string.Empty;
}

View File

@ -4,7 +4,12 @@ namespace Netina.Domain.Entities.Products;
public partial class Product public partial class Product
{ {
public static Product Create(string persianName, string englishName, string summery, string expertCheck, string tags, string warranty, public static Product Create(string persianName,
string englishName,
string summery,
string expertCheck,
string tags,
string warranty,
bool beDisplayed, bool beDisplayed,
double cost, double cost,
double packingCost, double packingCost,
@ -30,6 +35,7 @@ public partial class Product
hasExpressDelivery, hasExpressDelivery,
maxOrderCount, maxOrderCount,
stock > 0, stock > 0,
false,
brandId, brandId,
categoryId,authorId); categoryId,authorId);
} }
@ -93,4 +99,53 @@ public partial class Specification
{ {
return new Specification(title, detail, value, isFeature, productId, parentId); return new Specification(title, detail, value, isFeature, productId, parentId);
} }
}
public partial class SubProduct
{
public static SubProduct Create(
Guid parentId,
ProductDiversity diversity,
string diversityValue,
string diversityDescription,
string persianName,
string englishName,
string summery,
string slug,
string expertCheck,
string tags,
string warranty,
bool beDisplayed,
double cost,
double packingCost,
bool hasExpressDelivery,
int stock,
int maxOrderCount,
Guid brandId,
Guid categoryId,
Guid authorId)
{
return new SubProduct(
parentId,
diversity,
diversityValue,
diversityDescription,
persianName,
englishName,
slug,
summery,
expertCheck,
tags,
warranty,
beDisplayed,
cost,
packingCost,
stock,
hasExpressDelivery,
maxOrderCount,
stock > 0,
brandId,
categoryId,
authorId);
}
} }

View File

@ -33,6 +33,7 @@ public partial class Product : ApiEntity
bool hasExpressDelivery, bool hasExpressDelivery,
int maxOrderCount, int maxOrderCount,
bool isEnable, bool isEnable,
bool isSubProduct,
Guid brandId, Guid brandId,
Guid categoryId, Guid categoryId,
Guid authorId) Guid authorId)
@ -51,6 +52,7 @@ public partial class Product : ApiEntity
HasExpressDelivery = hasExpressDelivery; HasExpressDelivery = hasExpressDelivery;
MaxOrderCount = maxOrderCount; MaxOrderCount = maxOrderCount;
IsEnable = isEnable; IsEnable = isEnable;
IsSubProduct = isSubProduct;
BrandId = brandId; BrandId = brandId;
CategoryId = categoryId; CategoryId = categoryId;
AuthorId = authorId; AuthorId = authorId;
@ -72,7 +74,7 @@ public partial class Product : ApiEntity
public int Viewed { get; internal set; } public int Viewed { get; internal set; }
public bool HasExpressDelivery { get; internal set; } public bool HasExpressDelivery { get; internal set; }
public int MaxOrderCount { get; internal set; } public int MaxOrderCount { get; internal set; }
public bool IsSubProduct { get; internal set; }
public Guid BrandId { get; internal set; } public Guid BrandId { get; internal set; }
public Brand? Brand { get; internal set; } public Brand? Brand { get; internal set; }
@ -82,10 +84,9 @@ public partial class Product : ApiEntity
public ProductCategory? Category { get; internal set; } public ProductCategory? Category { get; internal set; }
public List<Specification> Specifications { get; internal set; } = []; public List<Specification> Specifications { get; internal set; } = [];
public List<ProductComment> Comments { get; internal set; } = []; public List<SubProduct> SubProducts { get; internal set; } = [];
public List<ProductStorageFile> Files { get; internal set; } = [];
public List<ProductMetaTag> MetaTags { get; internal set; } = []; public List<ProductMetaTag> MetaTags { get; internal set; } = [];
public List<ProductStorageFile> Files { get; internal set; } = [];
public List<ProductComment> Comments { get; internal set; } = [];
public List<OrderProduct> OrderProducts { get; internal set; } = []; public List<OrderProduct> OrderProducts { get; internal set; } = [];
} }

View File

@ -0,0 +1,65 @@
namespace Netina.Domain.Entities.Products;
[AdaptTwoWays("[name]SDto", IgnoreAttributes = [typeof(AdaptIgnoreAttribute)], MapType = MapType.Map | MapType.MapToTarget)]
[GenerateMapper]
public partial class SubProduct : Product
{
public SubProduct()
{
}
public SubProduct(
Guid parentId,
ProductDiversity diversity,
string diversityValue,
string diversityDescription,
string persianName,
string englishName,
string slug,
string summery,
string expertCheck,
string tags,
string warranty,
bool beDisplayed,
double cost,
double packingCost,
int stock,
bool hasExpressDelivery,
int maxOrderCount,
bool isEnable,
Guid brandId,
Guid categoryId,
Guid authorId) : base(persianName,
englishName,
slug,
summery,
expertCheck,
tags,
warranty,
beDisplayed,
cost,
packingCost,
stock,
hasExpressDelivery,
maxOrderCount,
isEnable,
true,
brandId,
categoryId,
authorId)
{
ParentId = parentId;
Diversity = diversity;
DiversityValue = diversityValue;
DiversityDescription = diversityDescription;
}
public Guid? ParentId { get; internal set; }
public Product? Parent { get; internal set; }
public ProductDiversity Diversity { get; internal set; }
public string DiversityValue { get; internal set; } = string.Empty;
public string DiversityDescription { get; internal set; } = string.Empty;
}

View File

@ -0,0 +1,43 @@
namespace Netina.Domain.Enums;
public enum ProductDiversity
{
[Display(Name = "هیچکدام")]
None = 0,
[Display(Name = "رنگ")]
Color = 1,
[Display(Name = "گارانتی")]
Guarantee = 2,
[Display(Name = "سایز")]
Size = 3
}
public enum ColorDiversity
{
[Display(Name = "سفید", Description = "#ffffff")]
White,
[Display(Name = "سیاه", Description = "#000000")]
Black,
[Display(Name = "آبی", Description = "#0000ff")]
Blue,
[Display(Name = "قرمز", Description = "#ff0000")]
Red,
[Display(Name = "زرد", Description = "#ffff00")]
Yellow,
[Display(Name = "سبز", Description = "#008000")]
Green,
[Display(Name = "نارنجی", Description = "#ffa500")]
Orange,
[Display(Name = "بنفش", Description = "#800080")]
Purple,
[Display(Name = "قهوه‌ای", Description = "#a52a2a")]
Brown,
[Display(Name = "صورتی", Description = "#ffc0cb")]
Pink,
[Display(Name = "خاکستری", Description = "#808080")]
Gray,
[Display(Name = "فیروزه‌ای", Description = "#00ffff")]
Cyan,
[Display(Name = "ارغوانی", Description = "#ff00ff")]
Magenta
}

View File

@ -44,8 +44,8 @@ namespace Netina.Domain.Mappers
Id = p1.CategoryId Id = p1.CategoryId
}, },
Specifications = funcMain1(p1.Specifications), Specifications = funcMain1(p1.Specifications),
Files = funcMain2(p1.Files), MetaTags = funcMain2(p1.MetaTags),
MetaTags = funcMain3(p1.MetaTags), Files = funcMain3(p1.Files),
Id = p1.Id, Id = p1.Id,
CreatedAt = p1.CreatedAt CreatedAt = p1.CreatedAt
}; };
@ -79,8 +79,8 @@ namespace Netina.Domain.Mappers
result.Author = funcMain5(new Never(), result.Author, p5); result.Author = funcMain5(new Never(), result.Author, p5);
result.Category = funcMain6(new Never(), result.Category, p5); result.Category = funcMain6(new Never(), result.Category, p5);
result.Specifications = funcMain7(p5.Specifications, result.Specifications); result.Specifications = funcMain7(p5.Specifications, result.Specifications);
result.Files = funcMain8(p5.Files, result.Files); result.MetaTags = funcMain8(p5.MetaTags, result.MetaTags);
result.MetaTags = funcMain9(p5.MetaTags, result.MetaTags); result.Files = funcMain9(p5.Files, result.Files);
result.Id = p5.Id; result.Id = p5.Id;
result.CreatedAt = p5.CreatedAt; result.CreatedAt = p5.CreatedAt;
return result; return result;
@ -123,24 +123,24 @@ namespace Netina.Domain.Mappers
Id = p20.Id, Id = p20.Id,
CreatedAt = p20.CreatedAt CreatedAt = p20.CreatedAt
}).ToList<Specification>(), }).ToList<Specification>(),
Files = p19.Files.Select<StorageFileSDto, ProductStorageFile>(p21 => new ProductStorageFile() MetaTags = p19.MetaTags.Select<MetaTagSDto, ProductMetaTag>(p21 => new ProductMetaTag()
{ {
Name = p21.Name, Type = p21.Type,
FileLocation = p21.FileLocation, Value = p21.Value,
FileName = p21.FileName,
IsHeader = p21.IsHeader,
IsPrimary = p21.IsPrimary,
FileType = p21.FileType,
Id = p21.Id, Id = p21.Id,
CreatedAt = p21.CreatedAt CreatedAt = p21.CreatedAt
}).ToList<ProductStorageFile>(), }).ToList<ProductMetaTag>(),
MetaTags = p19.MetaTags.Select<MetaTagSDto, ProductMetaTag>(p22 => new ProductMetaTag() Files = p19.Files.Select<StorageFileSDto, ProductStorageFile>(p22 => new ProductStorageFile()
{ {
Type = p22.Type, Name = p22.Name,
Value = p22.Value, FileLocation = p22.FileLocation,
FileName = p22.FileName,
IsHeader = p22.IsHeader,
IsPrimary = p22.IsPrimary,
FileType = p22.FileType,
Id = p22.Id, Id = p22.Id,
CreatedAt = p22.CreatedAt CreatedAt = p22.CreatedAt
}).ToList<ProductMetaTag>(), }).ToList<ProductStorageFile>(),
Id = p19.Id, Id = p19.Id,
CreatedAt = p19.CreatedAt CreatedAt = p19.CreatedAt
}; };
@ -472,28 +472,24 @@ namespace Netina.Domain.Mappers
} }
private static List<ProductStorageFile> funcMain2(List<StorageFileSDto> p3) private static List<ProductMetaTag> funcMain2(List<MetaTagSDto> p3)
{ {
if (p3 == null) if (p3 == null)
{ {
return null; return null;
} }
List<ProductStorageFile> result = new List<ProductStorageFile>(p3.Count); List<ProductMetaTag> result = new List<ProductMetaTag>(p3.Count);
int i = 0; int i = 0;
int len = p3.Count; int len = p3.Count;
while (i < len) while (i < len)
{ {
StorageFileSDto item = p3[i]; MetaTagSDto item = p3[i];
result.Add(item == null ? null : new ProductStorageFile() result.Add(item == null ? null : new ProductMetaTag()
{ {
Name = item.Name, Type = item.Type,
FileLocation = item.FileLocation, Value = item.Value,
FileName = item.FileName,
IsHeader = item.IsHeader,
IsPrimary = item.IsPrimary,
FileType = item.FileType,
Id = item.Id, Id = item.Id,
CreatedAt = item.CreatedAt CreatedAt = item.CreatedAt
}); });
@ -503,24 +499,28 @@ namespace Netina.Domain.Mappers
} }
private static List<ProductMetaTag> funcMain3(List<MetaTagSDto> p4) private static List<ProductStorageFile> funcMain3(List<StorageFileSDto> p4)
{ {
if (p4 == null) if (p4 == null)
{ {
return null; return null;
} }
List<ProductMetaTag> result = new List<ProductMetaTag>(p4.Count); List<ProductStorageFile> result = new List<ProductStorageFile>(p4.Count);
int i = 0; int i = 0;
int len = p4.Count; int len = p4.Count;
while (i < len) while (i < len)
{ {
MetaTagSDto item = p4[i]; StorageFileSDto item = p4[i];
result.Add(item == null ? null : new ProductMetaTag() result.Add(item == null ? null : new ProductStorageFile()
{ {
Type = item.Type, Name = item.Name,
Value = item.Value, FileLocation = item.FileLocation,
FileName = item.FileName,
IsHeader = item.IsHeader,
IsPrimary = item.IsPrimary,
FileType = item.FileType,
Id = item.Id, Id = item.Id,
CreatedAt = item.CreatedAt CreatedAt = item.CreatedAt
}); });
@ -589,28 +589,24 @@ namespace Netina.Domain.Mappers
} }
private static List<ProductStorageFile> funcMain8(List<StorageFileSDto> p15, List<ProductStorageFile> p16) private static List<ProductMetaTag> funcMain8(List<MetaTagSDto> p15, List<ProductMetaTag> p16)
{ {
if (p15 == null) if (p15 == null)
{ {
return null; return null;
} }
List<ProductStorageFile> result = new List<ProductStorageFile>(p15.Count); List<ProductMetaTag> result = new List<ProductMetaTag>(p15.Count);
int i = 0; int i = 0;
int len = p15.Count; int len = p15.Count;
while (i < len) while (i < len)
{ {
StorageFileSDto item = p15[i]; MetaTagSDto item = p15[i];
result.Add(item == null ? null : new ProductStorageFile() result.Add(item == null ? null : new ProductMetaTag()
{ {
Name = item.Name, Type = item.Type,
FileLocation = item.FileLocation, Value = item.Value,
FileName = item.FileName,
IsHeader = item.IsHeader,
IsPrimary = item.IsPrimary,
FileType = item.FileType,
Id = item.Id, Id = item.Id,
CreatedAt = item.CreatedAt CreatedAt = item.CreatedAt
}); });
@ -620,24 +616,28 @@ namespace Netina.Domain.Mappers
} }
private static List<ProductMetaTag> funcMain9(List<MetaTagSDto> p17, List<ProductMetaTag> p18) private static List<ProductStorageFile> funcMain9(List<StorageFileSDto> p17, List<ProductStorageFile> p18)
{ {
if (p17 == null) if (p17 == null)
{ {
return null; return null;
} }
List<ProductMetaTag> result = new List<ProductMetaTag>(p17.Count); List<ProductStorageFile> result = new List<ProductStorageFile>(p17.Count);
int i = 0; int i = 0;
int len = p17.Count; int len = p17.Count;
while (i < len) while (i < len)
{ {
MetaTagSDto item = p17[i]; StorageFileSDto item = p17[i];
result.Add(item == null ? null : new ProductMetaTag() result.Add(item == null ? null : new ProductStorageFile()
{ {
Type = item.Type, Name = item.Name,
Value = item.Value, FileLocation = item.FileLocation,
FileName = item.FileName,
IsHeader = item.IsHeader,
IsPrimary = item.IsPrimary,
FileType = item.FileType,
Id = item.Id, Id = item.Id,
CreatedAt = item.CreatedAt CreatedAt = item.CreatedAt
}); });

View File

@ -0,0 +1,115 @@
using System;
using System.Linq.Expressions;
using Netina.Domain.Dtos.ResponseDtos.Torob;
using Netina.Domain.Dtos.SmallDtos;
using Netina.Domain.Entities.Products;
namespace Netina.Domain.Mappers
{
public static partial class SubProductMapper
{
public static SubProduct AdaptToSubProduct(this SubProductSDto p1)
{
return p1 == null ? null : new SubProduct()
{
ParentId = (Guid?)p1.ParentId,
Diversity = p1.Diversity,
DiversityValue = p1.DiversityValue,
DiversityDescription = p1.DiversityDescription,
PersianName = p1.PersianName,
Cost = p1.Cost,
PackingCost = p1.PackingCost,
Stock = p1.Stock,
HasExpressDelivery = p1.HasExpressDelivery,
MaxOrderCount = p1.MaxOrderCount,
Id = p1.Id,
CreatedAt = p1.CreatedAt
};
}
public static SubProduct AdaptTo(this SubProductSDto p2, SubProduct p3)
{
if (p2 == null)
{
return null;
}
SubProduct result = p3 ?? new SubProduct();
result.ParentId = (Guid?)p2.ParentId;
result.Diversity = p2.Diversity;
result.DiversityValue = p2.DiversityValue;
result.DiversityDescription = p2.DiversityDescription;
result.PersianName = p2.PersianName;
result.Cost = p2.Cost;
result.PackingCost = p2.PackingCost;
result.Stock = p2.Stock;
result.HasExpressDelivery = p2.HasExpressDelivery;
result.MaxOrderCount = p2.MaxOrderCount;
result.Id = p2.Id;
result.CreatedAt = p2.CreatedAt;
return result;
}
public static SubProductSDto AdaptToSDto(this SubProduct p4)
{
return p4 == null ? null : new SubProductSDto()
{
ParentId = p4.ParentId == null ? default(Guid) : (Guid)p4.ParentId,
PersianName = p4.PersianName,
Cost = p4.Cost,
Stock = p4.Stock,
MaxOrderCount = p4.MaxOrderCount,
PackingCost = p4.PackingCost,
HasExpressDelivery = p4.HasExpressDelivery,
Diversity = p4.Diversity,
DiversityValue = p4.DiversityValue,
DiversityDescription = p4.DiversityDescription,
Id = p4.Id,
CreatedAt = p4.CreatedAt
};
}
public static SubProductSDto AdaptTo(this SubProduct p5, SubProductSDto p6)
{
if (p5 == null)
{
return null;
}
SubProductSDto result = p6 ?? new SubProductSDto();
result.ParentId = p5.ParentId == null ? default(Guid) : (Guid)p5.ParentId;
result.PersianName = p5.PersianName;
result.Cost = p5.Cost;
result.Stock = p5.Stock;
result.MaxOrderCount = p5.MaxOrderCount;
result.PackingCost = p5.PackingCost;
result.HasExpressDelivery = p5.HasExpressDelivery;
result.Diversity = p5.Diversity;
result.DiversityValue = p5.DiversityValue;
result.DiversityDescription = p5.DiversityDescription;
result.Id = p5.Id;
result.CreatedAt = p5.CreatedAt;
return result;
}
public static Expression<Func<SubProduct, SubProductSDto>> ProjectToSDto => p7 => new SubProductSDto()
{
ParentId = p7.ParentId == null ? default(Guid) : (Guid)p7.ParentId,
PersianName = p7.PersianName,
Cost = p7.Cost,
Stock = p7.Stock,
MaxOrderCount = p7.MaxOrderCount,
PackingCost = p7.PackingCost,
HasExpressDelivery = p7.HasExpressDelivery,
Diversity = p7.Diversity,
DiversityValue = p7.DiversityValue,
DiversityDescription = p7.DiversityDescription,
Id = p7.Id,
CreatedAt = p7.CreatedAt
};
public static Expression<Func<SubProduct, TorobProductResponseDto>> ProjectToTorobProductResponseDto => p8 => new TorobProductResponseDto()
{
product_id = ((Product)p8).Id.ToString(),
price = p8.Cost,
availibility = p8.IsEnable
};
}
}

View File

@ -17,11 +17,17 @@ public class CreateProductCommandHandler(
throw new BaseApiException(ApiResultStatusCode.UnAuthorized, "User id is wrong"); throw new BaseApiException(ApiResultStatusCode.UnAuthorized, "User id is wrong");
var ent = Product.Create(request.PersianName, request.EnglishName, request.Summery, request.ExpertCheck, var ent = Product.Create(request.PersianName, request.EnglishName, request.Summery, request.ExpertCheck,
request.Tags, request.Warranty,request.BeDisplayed,request.Cost,request.PackingCost, request.Tags,
request.Warranty,
request.BeDisplayed,
request.Cost,
request.PackingCost,
request.HasExpressDelivery, request.HasExpressDelivery,
request.Stock, request.Stock,
request.MaxOrderCount, request.MaxOrderCount,
request.BrandId,request.CategoryId, userId); request.BrandId,
request.CategoryId,
userId);
foreach (var specification in request.Specifications) foreach (var specification in request.Specifications)
{ {

View File

@ -9,7 +9,7 @@ public class GetProductsQueryHandler(
public async Task<GetProductsResponseDto> Handle(GetProductsQuery request, CancellationToken cancellationToken) public async Task<GetProductsResponseDto> Handle(GetProductsQuery request, CancellationToken cancellationToken)
{ {
var response = new GetProductsResponseDto(); var response = new GetProductsResponseDto();
var products = repositoryWrapper.SetRepository<Product>().TableNoTracking; var products = repositoryWrapper.SetRepository<Product>().TableNoTracking.Where(s=>!s.IsSubProduct);
if (currentUserService.JwtToken == null) if (currentUserService.JwtToken == null)
products = products.Where(p => p.BeDisplayed); products = products.Where(p => p.BeDisplayed);
var roleClaim = currentUserService.JwtToken?.Claims.FirstOrDefault(c => c.Type == "role"); var roleClaim = currentUserService.JwtToken?.Claims.FirstOrDefault(c => c.Type == "role");

View File

@ -13,6 +13,7 @@ public class UpdateProductCommandHandler(IRepositoryWrapper repositoryWrapper,IM
throw new BaseApiException(ApiResultStatusCode.UnAuthorized, "User id is wrong"); throw new BaseApiException(ApiResultStatusCode.UnAuthorized, "User id is wrong");
if(!Guid.TryParse(currentUserService.UserId,out Guid userId)) if(!Guid.TryParse(currentUserService.UserId,out Guid userId))
throw new BaseApiException(ApiResultStatusCode.UnAuthorized, "User id is wrong"); throw new BaseApiException(ApiResultStatusCode.UnAuthorized, "User id is wrong");
var newEnt = Product.Create(request.PersianName, request.EnglishName, request.Summery, request.ExpertCheck, var newEnt = Product.Create(request.PersianName, request.EnglishName, request.Summery, request.ExpertCheck,
request.Tags, request.Tags,
request.Warranty, request.Warranty,

View File

@ -0,0 +1,49 @@
namespace Netina.Repository.Handlers.SubProducts;
public class CreateSubProductCommandHandler(IRepositoryWrapper repositoryWrapper,
ICurrentUserService currentUserService) : IRequestHandler<CreateSubProductCommand,Guid>
{
public async Task<Guid> Handle(CreateSubProductCommand request, CancellationToken cancellationToken)
{
if (currentUserService.UserId == null)
throw new BaseApiException(ApiResultStatusCode.UnAuthorized, "User id is wrong");
if (!Guid.TryParse(currentUserService.UserId, out Guid userId))
throw new BaseApiException(ApiResultStatusCode.UnAuthorized, "User id is wrong");
var parent = await repositoryWrapper.SetRepository<Product>().TableNoTracking
.FirstOrDefaultAsync(p => p.Id == request.ParentId, cancellationToken);
if (parent == null)
throw new BaseApiException(ApiResultStatusCode.NotFound, "Parent product not found");
var subProduct = SubProduct.Create(
request.ParentId,
request.Diversity,
request.DiversityValue,
request.DiversityDescription,
$"{parent.PersianName} - {request.DiversityDescription}",
parent.EnglishName,
parent.Summery,
parent.Slug,
parent.ExpertCheck,
parent.Tags,
parent.Warranty,
parent.BeDisplayed,
request.Cost,
request.PackingCost,
request.HasExpressDelivery,
request.Stock,
request.MaxOrderCount,
parent.BrandId,
parent.CategoryId,
userId);
foreach (var file in request.Files)
subProduct.AddFile(file.Name, file.FileLocation, file.FileName, file.IsHeader, file.IsPrimary, file.FileType);
repositoryWrapper.SetRepository<SubProduct>().Add(subProduct);
await repositoryWrapper.SaveChangesAsync(cancellationToken);
return subProduct.Id;
}
}

View File

@ -0,0 +1,17 @@
namespace Netina.Repository.Handlers.SubProducts;
public class DeleteSubProductCommandHandler(IRepositoryWrapper repositoryWrapper)
: IRequestHandler<DeleteSubProductCommand, bool>
{
public async Task<bool> Handle(DeleteSubProductCommand request, CancellationToken cancellationToken)
{
var ent = await repositoryWrapper.SetRepository<SubProduct>().TableNoTracking
.FirstOrDefaultAsync(d => d.Id == request.Id, cancellationToken);
if (ent == null)
throw new AppException("Product NotFound", ApiResultStatusCode.NotFound);
repositoryWrapper.SetRepository<Product>().Delete(ent);
await repositoryWrapper.SaveChangesAsync(cancellationToken);
return true;
}
}

View File

@ -0,0 +1,17 @@
namespace Netina.Repository.Handlers.SubProducts;
public class GetSubProductsQueryHandler(IRepositoryWrapper repositoryWrapper) : IRequestHandler<GetSubProductsQuery,List<SubProductSDto>>
{
public async Task<List<SubProductSDto>> Handle(GetSubProductsQuery request, CancellationToken cancellationToken)
{
var product = await repositoryWrapper.SetRepository<Product>().TableNoTracking
.FirstOrDefaultAsync(p => p.Id == request.ProductId, cancellationToken);
if (product == null)
throw new AppException("Product not found");
return await repositoryWrapper.SetRepository<SubProduct>().TableNoTracking
.Where(s => s.ParentId == request.ProductId)
.Select(SubProductMapper.ProjectToSDto)
.ToListAsync(cancellationToken);
}
}

View File

@ -0,0 +1,58 @@
namespace Netina.Repository.Handlers.SubProducts;
public class UpdateSubProductCommandHandler(IRepositoryWrapper repositoryWrapper,
ICurrentUserService currentUserService) : IRequestHandler<UpdateSubProductCommand, Guid>
{
public async Task<Guid> Handle(UpdateSubProductCommand request, CancellationToken cancellationToken)
{
if (currentUserService.UserId == null)
throw new BaseApiException(ApiResultStatusCode.UnAuthorized, "User id is wrong");
if (!Guid.TryParse(currentUserService.UserId, out Guid userId))
throw new BaseApiException(ApiResultStatusCode.UnAuthorized, "User id is wrong");
var parent = await repositoryWrapper.SetRepository<Product>().TableNoTracking
.FirstOrDefaultAsync(p => p.Id == request.ParentId, cancellationToken);
if (parent == null)
throw new BaseApiException(ApiResultStatusCode.NotFound, "Parent product not found");
var subProduct = await repositoryWrapper.SetRepository<SubProduct>().TableNoTracking
.FirstOrDefaultAsync(p => p.Id == request.Id, cancellationToken);
if (subProduct == null)
throw new BaseApiException(ApiResultStatusCode.NotFound, "Parent product not found");
var newEnt = SubProduct.Create(
request.ParentId,
request.Diversity,
request.DiversityValue,
request.DiversityDescription,
$"{parent.PersianName} - {request.DiversityDescription}",
parent.EnglishName,
parent.Summery,
parent.Slug,
parent.ExpertCheck,
parent.Tags,
parent.Warranty,
parent.BeDisplayed,
request.Cost,
request.PackingCost,
request.HasExpressDelivery,
request.Stock,
request.MaxOrderCount,
parent.BrandId,
parent.CategoryId,
userId);
newEnt.Id = subProduct.Id;
newEnt.CreatedAt = subProduct.CreatedAt;
newEnt.CreatedBy = subProduct.CreatedBy;
foreach (var file in request.Files)
subProduct.AddFile(file.Name, file.FileLocation, file.FileName, file.IsHeader, file.IsPrimary, file.FileType);
repositoryWrapper.SetRepository<SubProduct>().Update(newEnt);
await repositoryWrapper.SaveChangesAsync(cancellationToken);
return subProduct.Id;
}
}

View File

@ -17,7 +17,7 @@ namespace NetinaShop.Repository.Migrations
table: "Products", table: "Products",
type: "uuid", type: "uuid",
nullable: false, nullable: false,
defaultValue: new Guid("11c47231-4f8b-4a73-b848-d2edf3c2d9ab")); defaultValue: new Guid("8723f1d2-e091-4812-9110-5161c9e23586"));
migrationBuilder.AddColumn<Guid>( migrationBuilder.AddColumn<Guid>(
name: "AuthorId", name: "AuthorId",
@ -25,7 +25,7 @@ namespace NetinaShop.Repository.Migrations
table: "Blogs", table: "Blogs",
type: "uuid", type: "uuid",
nullable: false, nullable: false,
defaultValue: new Guid("11c47231-4f8b-4a73-b848-d2edf3c2d9ab")); defaultValue: new Guid("8723f1d2-e091-4812-9110-5161c9e23586"));
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_Products_AuthorId", name: "IX_Products_AuthorId",

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,119 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NetinaShop.Repository.Migrations
{
/// <inheritdoc />
public partial class AddSubProduct : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Discriminator",
schema: "public",
table: "Products",
type: "character varying(13)",
maxLength: 13,
nullable: false,
defaultValue: "Product");
migrationBuilder.AddColumn<int>(
name: "Diversity",
schema: "public",
table: "Products",
type: "integer",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "DiversityDescription",
schema: "public",
table: "Products",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "DiversityValue",
schema: "public",
table: "Products",
type: "text",
nullable: true);
migrationBuilder.AddColumn<bool>(
name: "IsSubProduct",
schema: "public",
table: "Products",
type: "boolean",
nullable: false,
defaultValue: true);
migrationBuilder.AddColumn<Guid>(
name: "ParentId",
schema: "public",
table: "Products",
type: "uuid",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_Products_ParentId",
schema: "public",
table: "Products",
column: "ParentId");
migrationBuilder.AddForeignKey(
name: "FK_Products_Products_ParentId",
schema: "public",
table: "Products",
column: "ParentId",
principalSchema: "public",
principalTable: "Products",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Products_Products_ParentId",
schema: "public",
table: "Products");
migrationBuilder.DropIndex(
name: "IX_Products_ParentId",
schema: "public",
table: "Products");
migrationBuilder.DropColumn(
name: "Discriminator",
schema: "public",
table: "Products");
migrationBuilder.DropColumn(
name: "Diversity",
schema: "public",
table: "Products");
migrationBuilder.DropColumn(
name: "DiversityDescription",
schema: "public",
table: "Products");
migrationBuilder.DropColumn(
name: "DiversityValue",
schema: "public",
table: "Products");
migrationBuilder.DropColumn(
name: "IsSubProduct",
schema: "public",
table: "Products");
migrationBuilder.DropColumn(
name: "ParentId",
schema: "public",
table: "Products");
}
}
}

View File

@ -892,6 +892,11 @@ namespace NetinaShop.Repository.Migrations
.IsRequired() .IsRequired()
.HasColumnType("text"); .HasColumnType("text");
b.Property<string>("Discriminator")
.IsRequired()
.HasMaxLength(13)
.HasColumnType("character varying(13)");
b.Property<string>("EnglishName") b.Property<string>("EnglishName")
.IsRequired() .IsRequired()
.HasColumnType("text"); .HasColumnType("text");
@ -909,6 +914,9 @@ namespace NetinaShop.Repository.Migrations
b.Property<bool>("IsRemoved") b.Property<bool>("IsRemoved")
.HasColumnType("boolean"); .HasColumnType("boolean");
b.Property<bool>("IsSubProduct")
.HasColumnType("boolean");
b.Property<int>("MaxOrderCount") b.Property<int>("MaxOrderCount")
.HasColumnType("integer"); .HasColumnType("integer");
@ -970,6 +978,10 @@ namespace NetinaShop.Repository.Migrations
b.HasIndex("CategoryId"); b.HasIndex("CategoryId");
b.ToTable("Products", "public"); b.ToTable("Products", "public");
b.HasDiscriminator().HasValue("Product");
b.UseTphMappingStrategy();
}); });
modelBuilder.Entity("Netina.Domain.Entities.Products.Specification", b => modelBuilder.Entity("Netina.Domain.Entities.Products.Specification", b =>
@ -1686,6 +1698,29 @@ namespace NetinaShop.Repository.Migrations
b.HasDiscriminator().HasValue("ProductDiscount"); b.HasDiscriminator().HasValue("ProductDiscount");
}); });
modelBuilder.Entity("Netina.Domain.Entities.Products.SubProduct", b =>
{
b.HasBaseType("Netina.Domain.Entities.Products.Product");
b.Property<int>("Diversity")
.HasColumnType("integer");
b.Property<string>("DiversityDescription")
.IsRequired()
.HasColumnType("text");
b.Property<string>("DiversityValue")
.IsRequired()
.HasColumnType("text");
b.Property<Guid?>("ParentId")
.HasColumnType("uuid");
b.HasIndex("ParentId");
b.HasDiscriminator().HasValue("SubProduct");
});
modelBuilder.Entity("Netina.Domain.Entities.Blogs.BlogMetaTag", b => modelBuilder.Entity("Netina.Domain.Entities.Blogs.BlogMetaTag", b =>
{ {
b.HasBaseType("Netina.Domain.Entities.Seo.MetaTag"); b.HasBaseType("Netina.Domain.Entities.Seo.MetaTag");
@ -2096,6 +2131,15 @@ namespace NetinaShop.Repository.Migrations
b.Navigation("Product"); b.Navigation("Product");
}); });
modelBuilder.Entity("Netina.Domain.Entities.Products.SubProduct", b =>
{
b.HasOne("Netina.Domain.Entities.Products.Product", "Parent")
.WithMany("SubProducts")
.HasForeignKey("ParentId");
b.Navigation("Parent");
});
modelBuilder.Entity("Netina.Domain.Entities.Blogs.BlogMetaTag", b => modelBuilder.Entity("Netina.Domain.Entities.Blogs.BlogMetaTag", b =>
{ {
b.HasOne("Netina.Domain.Entities.Blogs.Blog", "Brand") b.HasOne("Netina.Domain.Entities.Blogs.Blog", "Brand")
@ -2236,6 +2280,8 @@ namespace NetinaShop.Repository.Migrations
b.Navigation("OrderProducts"); b.Navigation("OrderProducts");
b.Navigation("Specifications"); b.Navigation("Specifications");
b.Navigation("SubProducts");
}); });
modelBuilder.Entity("Netina.Domain.Entities.Products.Specification", b => modelBuilder.Entity("Netina.Domain.Entities.Products.Specification", b =>