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.subProduct
parent
559ab04d9b
commit
7d94e8cd47
|
@ -28,6 +28,10 @@ public class ProductController : ICarterModule
|
|||
.RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ManageProducts))
|
||||
.HasApiVersion(1.0);
|
||||
|
||||
group.MapGet("{productId}/sub", GetSubProductsAsync)
|
||||
.WithDisplayName("Get Sub Products")
|
||||
.HasApiVersion(1.0);
|
||||
|
||||
group.MapPut("{productId}/displayed", ChangeDisplayedAsync)
|
||||
.WithDisplayName("Change Product Display")
|
||||
.RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ManageProducts))
|
||||
|
@ -78,6 +82,11 @@ public class ProductController : ICarterModule
|
|||
CancellationToken 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)
|
||||
=> TypedResults.Ok(await mediator.Send(new ChangeProductDisplayedCommand(productId, beDisplayed), cancellationToken));
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
||||
}
|
|
@ -324,6 +324,7 @@ public class SiteMapService(
|
|||
|
||||
var products = await repositoryWrapper.SetRepository<Product>()
|
||||
.TableNoTracking
|
||||
.Where(p=>!p.IsSubProduct)
|
||||
.Select(ProductMapper.ProjectToSDto)
|
||||
.ToListAsync();
|
||||
|
||||
|
|
|
@ -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>;
|
|
@ -0,0 +1,3 @@
|
|||
namespace Netina.Domain.CommandQueries.Queries;
|
||||
|
||||
public record GetSubProductsQuery(Guid ProductId) : IRequest<List<SubProductSDto>>;
|
|
@ -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;
|
||||
}
|
|
@ -4,7 +4,12 @@ namespace Netina.Domain.Entities.Products;
|
|||
|
||||
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,
|
||||
double cost,
|
||||
double packingCost,
|
||||
|
@ -30,6 +35,7 @@ public partial class Product
|
|||
hasExpressDelivery,
|
||||
maxOrderCount,
|
||||
stock > 0,
|
||||
false,
|
||||
brandId,
|
||||
categoryId,authorId);
|
||||
}
|
||||
|
@ -94,3 +100,52 @@ public partial class Specification
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -33,6 +33,7 @@ public partial class Product : ApiEntity
|
|||
bool hasExpressDelivery,
|
||||
int maxOrderCount,
|
||||
bool isEnable,
|
||||
bool isSubProduct,
|
||||
Guid brandId,
|
||||
Guid categoryId,
|
||||
Guid authorId)
|
||||
|
@ -51,6 +52,7 @@ public partial class Product : ApiEntity
|
|||
HasExpressDelivery = hasExpressDelivery;
|
||||
MaxOrderCount = maxOrderCount;
|
||||
IsEnable = isEnable;
|
||||
IsSubProduct = isSubProduct;
|
||||
BrandId = brandId;
|
||||
CategoryId = categoryId;
|
||||
AuthorId = authorId;
|
||||
|
@ -72,7 +74,7 @@ public partial class Product : ApiEntity
|
|||
public int Viewed { get; internal set; }
|
||||
public bool HasExpressDelivery { get; internal set; }
|
||||
public int MaxOrderCount { get; internal set; }
|
||||
|
||||
public bool IsSubProduct { get; internal set; }
|
||||
public Guid BrandId { 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 List<Specification> Specifications { get; internal set; } = [];
|
||||
public List<ProductComment> Comments { get; internal set; } = [];
|
||||
public List<ProductStorageFile> Files { get; internal set; } = [];
|
||||
|
||||
public List<SubProduct> SubProducts { 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; } = [];
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -44,8 +44,8 @@ namespace Netina.Domain.Mappers
|
|||
Id = p1.CategoryId
|
||||
},
|
||||
Specifications = funcMain1(p1.Specifications),
|
||||
Files = funcMain2(p1.Files),
|
||||
MetaTags = funcMain3(p1.MetaTags),
|
||||
MetaTags = funcMain2(p1.MetaTags),
|
||||
Files = funcMain3(p1.Files),
|
||||
Id = p1.Id,
|
||||
CreatedAt = p1.CreatedAt
|
||||
};
|
||||
|
@ -79,8 +79,8 @@ namespace Netina.Domain.Mappers
|
|||
result.Author = funcMain5(new Never(), result.Author, p5);
|
||||
result.Category = funcMain6(new Never(), result.Category, p5);
|
||||
result.Specifications = funcMain7(p5.Specifications, result.Specifications);
|
||||
result.Files = funcMain8(p5.Files, result.Files);
|
||||
result.MetaTags = funcMain9(p5.MetaTags, result.MetaTags);
|
||||
result.MetaTags = funcMain8(p5.MetaTags, result.MetaTags);
|
||||
result.Files = funcMain9(p5.Files, result.Files);
|
||||
result.Id = p5.Id;
|
||||
result.CreatedAt = p5.CreatedAt;
|
||||
return result;
|
||||
|
@ -123,24 +123,24 @@ namespace Netina.Domain.Mappers
|
|||
Id = p20.Id,
|
||||
CreatedAt = p20.CreatedAt
|
||||
}).ToList<Specification>(),
|
||||
Files = p19.Files.Select<StorageFileSDto, ProductStorageFile>(p21 => new ProductStorageFile()
|
||||
MetaTags = p19.MetaTags.Select<MetaTagSDto, ProductMetaTag>(p21 => new ProductMetaTag()
|
||||
{
|
||||
Name = p21.Name,
|
||||
FileLocation = p21.FileLocation,
|
||||
FileName = p21.FileName,
|
||||
IsHeader = p21.IsHeader,
|
||||
IsPrimary = p21.IsPrimary,
|
||||
FileType = p21.FileType,
|
||||
Type = p21.Type,
|
||||
Value = p21.Value,
|
||||
Id = p21.Id,
|
||||
CreatedAt = p21.CreatedAt
|
||||
}).ToList<ProductStorageFile>(),
|
||||
MetaTags = p19.MetaTags.Select<MetaTagSDto, ProductMetaTag>(p22 => new ProductMetaTag()
|
||||
}).ToList<ProductMetaTag>(),
|
||||
Files = p19.Files.Select<StorageFileSDto, ProductStorageFile>(p22 => new ProductStorageFile()
|
||||
{
|
||||
Type = p22.Type,
|
||||
Value = p22.Value,
|
||||
Name = p22.Name,
|
||||
FileLocation = p22.FileLocation,
|
||||
FileName = p22.FileName,
|
||||
IsHeader = p22.IsHeader,
|
||||
IsPrimary = p22.IsPrimary,
|
||||
FileType = p22.FileType,
|
||||
Id = p22.Id,
|
||||
CreatedAt = p22.CreatedAt
|
||||
}).ToList<ProductMetaTag>(),
|
||||
}).ToList<ProductStorageFile>(),
|
||||
Id = p19.Id,
|
||||
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)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
List<ProductStorageFile> result = new List<ProductStorageFile>(p3.Count);
|
||||
List<ProductMetaTag> result = new List<ProductMetaTag>(p3.Count);
|
||||
|
||||
int i = 0;
|
||||
int len = p3.Count;
|
||||
|
||||
while (i < len)
|
||||
{
|
||||
StorageFileSDto item = p3[i];
|
||||
result.Add(item == null ? null : new ProductStorageFile()
|
||||
MetaTagSDto item = p3[i];
|
||||
result.Add(item == null ? null : new ProductMetaTag()
|
||||
{
|
||||
Name = item.Name,
|
||||
FileLocation = item.FileLocation,
|
||||
FileName = item.FileName,
|
||||
IsHeader = item.IsHeader,
|
||||
IsPrimary = item.IsPrimary,
|
||||
FileType = item.FileType,
|
||||
Type = item.Type,
|
||||
Value = item.Value,
|
||||
Id = item.Id,
|
||||
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)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
List<ProductMetaTag> result = new List<ProductMetaTag>(p4.Count);
|
||||
List<ProductStorageFile> result = new List<ProductStorageFile>(p4.Count);
|
||||
|
||||
int i = 0;
|
||||
int len = p4.Count;
|
||||
|
||||
while (i < len)
|
||||
{
|
||||
MetaTagSDto item = p4[i];
|
||||
result.Add(item == null ? null : new ProductMetaTag()
|
||||
StorageFileSDto item = p4[i];
|
||||
result.Add(item == null ? null : new ProductStorageFile()
|
||||
{
|
||||
Type = item.Type,
|
||||
Value = item.Value,
|
||||
Name = item.Name,
|
||||
FileLocation = item.FileLocation,
|
||||
FileName = item.FileName,
|
||||
IsHeader = item.IsHeader,
|
||||
IsPrimary = item.IsPrimary,
|
||||
FileType = item.FileType,
|
||||
Id = item.Id,
|
||||
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)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
List<ProductStorageFile> result = new List<ProductStorageFile>(p15.Count);
|
||||
List<ProductMetaTag> result = new List<ProductMetaTag>(p15.Count);
|
||||
|
||||
int i = 0;
|
||||
int len = p15.Count;
|
||||
|
||||
while (i < len)
|
||||
{
|
||||
StorageFileSDto item = p15[i];
|
||||
result.Add(item == null ? null : new ProductStorageFile()
|
||||
MetaTagSDto item = p15[i];
|
||||
result.Add(item == null ? null : new ProductMetaTag()
|
||||
{
|
||||
Name = item.Name,
|
||||
FileLocation = item.FileLocation,
|
||||
FileName = item.FileName,
|
||||
IsHeader = item.IsHeader,
|
||||
IsPrimary = item.IsPrimary,
|
||||
FileType = item.FileType,
|
||||
Type = item.Type,
|
||||
Value = item.Value,
|
||||
Id = item.Id,
|
||||
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)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
List<ProductMetaTag> result = new List<ProductMetaTag>(p17.Count);
|
||||
List<ProductStorageFile> result = new List<ProductStorageFile>(p17.Count);
|
||||
|
||||
int i = 0;
|
||||
int len = p17.Count;
|
||||
|
||||
while (i < len)
|
||||
{
|
||||
MetaTagSDto item = p17[i];
|
||||
result.Add(item == null ? null : new ProductMetaTag()
|
||||
StorageFileSDto item = p17[i];
|
||||
result.Add(item == null ? null : new ProductStorageFile()
|
||||
{
|
||||
Type = item.Type,
|
||||
Value = item.Value,
|
||||
Name = item.Name,
|
||||
FileLocation = item.FileLocation,
|
||||
FileName = item.FileName,
|
||||
IsHeader = item.IsHeader,
|
||||
IsPrimary = item.IsPrimary,
|
||||
FileType = item.FileType,
|
||||
Id = item.Id,
|
||||
CreatedAt = item.CreatedAt
|
||||
});
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
|
@ -17,11 +17,17 @@ public class CreateProductCommandHandler(
|
|||
throw new BaseApiException(ApiResultStatusCode.UnAuthorized, "User id is wrong");
|
||||
|
||||
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.Stock,
|
||||
request.MaxOrderCount,
|
||||
request.BrandId,request.CategoryId, userId);
|
||||
request.BrandId,
|
||||
request.CategoryId,
|
||||
userId);
|
||||
|
||||
foreach (var specification in request.Specifications)
|
||||
{
|
||||
|
|
|
@ -9,7 +9,7 @@ public class GetProductsQueryHandler(
|
|||
public async Task<GetProductsResponseDto> Handle(GetProductsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = new GetProductsResponseDto();
|
||||
var products = repositoryWrapper.SetRepository<Product>().TableNoTracking;
|
||||
var products = repositoryWrapper.SetRepository<Product>().TableNoTracking.Where(s=>!s.IsSubProduct);
|
||||
if (currentUserService.JwtToken == null)
|
||||
products = products.Where(p => p.BeDisplayed);
|
||||
var roleClaim = currentUserService.JwtToken?.Claims.FirstOrDefault(c => c.Type == "role");
|
||||
|
|
|
@ -13,6 +13,7 @@ public class UpdateProductCommandHandler(IRepositoryWrapper repositoryWrapper,IM
|
|||
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 newEnt = Product.Create(request.PersianName, request.EnglishName, request.Summery, request.ExpertCheck,
|
||||
request.Tags,
|
||||
request.Warranty,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@ namespace NetinaShop.Repository.Migrations
|
|||
table: "Products",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
defaultValue: new Guid("11c47231-4f8b-4a73-b848-d2edf3c2d9ab"));
|
||||
defaultValue: new Guid("8723f1d2-e091-4812-9110-5161c9e23586"));
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "AuthorId",
|
||||
|
@ -25,7 +25,7 @@ namespace NetinaShop.Repository.Migrations
|
|||
table: "Blogs",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
defaultValue: new Guid("11c47231-4f8b-4a73-b848-d2edf3c2d9ab"));
|
||||
defaultValue: new Guid("8723f1d2-e091-4812-9110-5161c9e23586"));
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Products_AuthorId",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -892,6 +892,11 @@ namespace NetinaShop.Repository.Migrations
|
|||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Discriminator")
|
||||
.IsRequired()
|
||||
.HasMaxLength(13)
|
||||
.HasColumnType("character varying(13)");
|
||||
|
||||
b.Property<string>("EnglishName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
@ -909,6 +914,9 @@ namespace NetinaShop.Repository.Migrations
|
|||
b.Property<bool>("IsRemoved")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("IsSubProduct")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("MaxOrderCount")
|
||||
.HasColumnType("integer");
|
||||
|
||||
|
@ -970,6 +978,10 @@ namespace NetinaShop.Repository.Migrations
|
|||
b.HasIndex("CategoryId");
|
||||
|
||||
b.ToTable("Products", "public");
|
||||
|
||||
b.HasDiscriminator().HasValue("Product");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Netina.Domain.Entities.Products.Specification", b =>
|
||||
|
@ -1686,6 +1698,29 @@ namespace NetinaShop.Repository.Migrations
|
|||
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 =>
|
||||
{
|
||||
b.HasBaseType("Netina.Domain.Entities.Seo.MetaTag");
|
||||
|
@ -2096,6 +2131,15 @@ namespace NetinaShop.Repository.Migrations
|
|||
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 =>
|
||||
{
|
||||
b.HasOne("Netina.Domain.Entities.Blogs.Blog", "Brand")
|
||||
|
@ -2236,6 +2280,8 @@ namespace NetinaShop.Repository.Migrations
|
|||
b.Navigation("OrderProducts");
|
||||
|
||||
b.Navigation("Specifications");
|
||||
|
||||
b.Navigation("SubProducts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Netina.Domain.Entities.Products.Specification", b =>
|
||||
|
|
Loading…
Reference in New Issue