feat: enhance user club feature management with sorting and creation timestamps
This commit is contained in:
+4
-1
@@ -31,8 +31,11 @@ public class GetUserClubFeaturesQueryHandler : IRequestHandler<GetUserClubFeatur
|
|||||||
FeatureDescription = ucf.ClubFeature.Description,
|
FeatureDescription = ucf.ClubFeature.Description,
|
||||||
IsActive = ucf.IsActive,
|
IsActive = ucf.IsActive,
|
||||||
GrantedAt = ucf.GrantedAt,
|
GrantedAt = ucf.GrantedAt,
|
||||||
Notes = ucf.Notes
|
Notes = ucf.Notes,
|
||||||
|
SortOrder = ucf.ClubFeature.SortOrder,
|
||||||
|
CreatedAt = ucf.Created
|
||||||
})
|
})
|
||||||
|
.OrderBy(ucf => ucf.SortOrder)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
return userClubFeatures;
|
return userClubFeatures;
|
||||||
|
|||||||
+12
-2
@@ -31,7 +31,7 @@ public class UserClubFeatureDto
|
|||||||
public string FeatureTitle { get; set; }
|
public string FeatureTitle { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// توضیحات ویژگی
|
/// توضیحات کوتاه ویژگی
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? FeatureDescription { get; set; }
|
public string? FeatureDescription { get; set; }
|
||||||
|
|
||||||
@@ -46,7 +46,17 @@ public class UserClubFeatureDto
|
|||||||
public DateTime GrantedAt { get; set; }
|
public DateTime GrantedAt { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// یادداشت
|
/// یادداشت (برای نمایش در مدال جزئیات)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Notes { get; set; }
|
public string? Notes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ترتیب نمایش
|
||||||
|
/// </summary>
|
||||||
|
public int SortOrder { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تاریخ ایجاد رکورد
|
||||||
|
/// </summary>
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ public record UpdateUserAddressCommand : IRequest<Unit>
|
|||||||
//شناسه
|
//شناسه
|
||||||
public long Id { get; init; }
|
public long Id { get; init; }
|
||||||
//شناسه کاربر
|
//شناسه کاربر
|
||||||
public long UserId { get; init; }
|
|
||||||
//عنوان
|
//عنوان
|
||||||
public string Title { get; init; }
|
public string Title { get; init; }
|
||||||
//آدرس
|
//آدرس
|
||||||
|
|||||||
+1
-2
@@ -5,8 +5,7 @@ public class UpdateUserAddressCommandValidator : AbstractValidator<UpdateUserAdd
|
|||||||
{
|
{
|
||||||
RuleFor(model => model.Id)
|
RuleFor(model => model.Id)
|
||||||
.NotNull();
|
.NotNull();
|
||||||
RuleFor(model => model.UserId)
|
|
||||||
.NotNull();
|
|
||||||
RuleFor(model => model.Title)
|
RuleFor(model => model.Title)
|
||||||
.NotEmpty();
|
.NotEmpty();
|
||||||
RuleFor(model => model.Address)
|
RuleFor(model => model.Address)
|
||||||
|
|||||||
+40
@@ -32,6 +32,34 @@ public class
|
|||||||
if (user.UserCarts.Count == 0)
|
if (user.UserCarts.Count == 0)
|
||||||
throw new NotFoundException("UserCart", request.UserId);
|
throw new NotFoundException("UserCart", request.UserId);
|
||||||
|
|
||||||
|
// چک موجودی محصولات
|
||||||
|
var outOfStockProducts = new List<string>();
|
||||||
|
var insufficientStockProducts = new List<(string Title, int Requested, int Available)>();
|
||||||
|
|
||||||
|
foreach (var cartItem in user.UserCarts)
|
||||||
|
{
|
||||||
|
if (cartItem.Product.RemainingCount <= 0)
|
||||||
|
{
|
||||||
|
outOfStockProducts.Add(cartItem.Product.Title);
|
||||||
|
}
|
||||||
|
else if (cartItem.Product.RemainingCount < cartItem.Count)
|
||||||
|
{
|
||||||
|
insufficientStockProducts.Add((cartItem.Product.Title, cartItem.Count, cartItem.Product.RemainingCount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outOfStockProducts.Any())
|
||||||
|
{
|
||||||
|
throw new Exception($"محصولات زیر ناموجود شدهاند: {string.Join("، ", outOfStockProducts)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insufficientStockProducts.Any())
|
||||||
|
{
|
||||||
|
var messages = insufficientStockProducts.Select(p =>
|
||||||
|
$"«{p.Title}»: درخواست {p.Requested} عدد، موجودی {p.Available} عدد");
|
||||||
|
throw new Exception($"موجودی محصولات زیر کافی نیست: {string.Join(" | ", messages)}");
|
||||||
|
}
|
||||||
|
|
||||||
if (user.UserCarts.Sum(s => s.Count * s.Product.Price) != request.TotalAmount)
|
if (user.UserCarts.Sum(s => s.Count * s.Product.Price) != request.TotalAmount)
|
||||||
throw new Exception("مبلغ سفارش با مجموع سبد خرید مطابقت ندارد.");
|
throw new Exception("مبلغ سفارش با مجموع سبد خرید مطابقت ندارد.");
|
||||||
|
|
||||||
@@ -100,8 +128,20 @@ public class
|
|||||||
OrderId = newOrder.Id
|
OrderId = newOrder.Id
|
||||||
});
|
});
|
||||||
await _context.FactorDetails.AddRangeAsync(factorDetailsList, cancellationToken);
|
await _context.FactorDetails.AddRangeAsync(factorDetailsList, cancellationToken);
|
||||||
|
|
||||||
|
// کاهش موجودی محصولات و افزایش تعداد فروش
|
||||||
|
foreach (var cartItem in user.UserCarts)
|
||||||
|
{
|
||||||
|
cartItem.Product.RemainingCount -= cartItem.Count;
|
||||||
|
cartItem.Product.SaleCount += cartItem.Count;
|
||||||
|
}
|
||||||
|
|
||||||
user.UserCarts.Clear();
|
user.UserCarts.Clear();
|
||||||
await _context.SaveChangesAsync(cancellationToken);
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
_logger.LogInformation("Order {OrderId} completed. Stock updated for {ProductCount} products.",
|
||||||
|
newOrder.Id, factorDetailsList.Count());
|
||||||
|
|
||||||
var finalResult = new SubmitShopBuyOrderResponseDto()
|
var finalResult = new SubmitShopBuyOrderResponseDto()
|
||||||
{
|
{
|
||||||
Id = newOrder.Id,
|
Id = newOrder.Id,
|
||||||
|
|||||||
@@ -11,19 +11,17 @@ public class ClubFeature : BaseAuditableEntity
|
|||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// توضیحات
|
/// توضیحات کوتاه
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// وضعیت فعال/غیرفعال
|
/// وضعیت فعال/غیرفعال
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsActive { get; set; }
|
public bool IsActive { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// امتیاز لازم برای دریافت (اختیاری)
|
|
||||||
/// </summary>
|
|
||||||
public int? RequiredPoints { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ترتیب نمایش
|
/// ترتیب نمایش
|
||||||
|
|||||||
-1
@@ -19,7 +19,6 @@ public class ClubFeatureConfiguration : IEntityTypeConfiguration<ClubFeature>
|
|||||||
builder.Property(entity => entity.Title).IsRequired().HasMaxLength(200);
|
builder.Property(entity => entity.Title).IsRequired().HasMaxLength(200);
|
||||||
builder.Property(entity => entity.Description).IsRequired(false).HasMaxLength(1000);
|
builder.Property(entity => entity.Description).IsRequired(false).HasMaxLength(1000);
|
||||||
builder.Property(entity => entity.IsActive).IsRequired();
|
builder.Property(entity => entity.IsActive).IsRequired();
|
||||||
builder.Property(entity => entity.RequiredPoints).IsRequired(false);
|
|
||||||
builder.Property(entity => entity.SortOrder).IsRequired();
|
builder.Property(entity => entity.SortOrder).IsRequired();
|
||||||
|
|
||||||
// Index برای IsActive و SortOrder
|
// Index برای IsActive و SortOrder
|
||||||
|
|||||||
+3520
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class u15 : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "RequiredPoints",
|
||||||
|
schema: "CMS",
|
||||||
|
table: "ClubFeatures");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "RequiredPoints",
|
||||||
|
schema: "CMS",
|
||||||
|
table: "ClubFeatures",
|
||||||
|
type: "int",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-3
@@ -106,9 +106,6 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
|||||||
b.Property<string>("LastModifiedBy")
|
b.Property<string>("LastModifiedBy")
|
||||||
.HasColumnType("nvarchar(max)");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<int?>("RequiredPoints")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<int>("SortOrder")
|
b.Property<int>("SortOrder")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
|||||||
@@ -249,6 +249,8 @@ message UserClubFeatureModel
|
|||||||
bool is_active = 7;
|
bool is_active = 7;
|
||||||
google.protobuf.Timestamp granted_at = 8;
|
google.protobuf.Timestamp granted_at = 8;
|
||||||
string notes = 9;
|
string notes = 9;
|
||||||
|
int32 sort_order = 10;
|
||||||
|
google.protobuf.Timestamp created_at = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToggleUserClubFeature Command
|
// ToggleUserClubFeature Command
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ message DeactivateConfigurationRequest
|
|||||||
// GetByKey Query
|
// GetByKey Query
|
||||||
message GetConfigurationByKeyRequest
|
message GetConfigurationByKeyRequest
|
||||||
{
|
{
|
||||||
string key = 1;
|
int32 scope = 1;
|
||||||
|
string key = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetConfigurationByKeyResponse
|
message GetConfigurationByKeyResponse
|
||||||
@@ -77,11 +78,17 @@ message GetConfigurationByKeyResponse
|
|||||||
|
|
||||||
// GetAll Query
|
// GetAll Query
|
||||||
message GetAllConfigurationsRequest
|
message GetAllConfigurationsRequest
|
||||||
|
{
|
||||||
|
messages.PaginationState pagination_state = 1;
|
||||||
|
google.protobuf.StringValue sort_by = 2;
|
||||||
|
GetAllConfigurationsFilter filter = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetAllConfigurationsFilter
|
||||||
{
|
{
|
||||||
google.protobuf.Int32Value scope = 1;
|
google.protobuf.Int32Value scope = 1;
|
||||||
google.protobuf.BoolValue is_active = 2;
|
google.protobuf.StringValue key_contains = 2;
|
||||||
int32 page_index = 3;
|
google.protobuf.BoolValue is_active = 3;
|
||||||
int32 page_size = 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetAllConfigurationsResponse
|
message GetAllConfigurationsResponse
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ public class ClubFeatureProfile : IRegister
|
|||||||
.Map(dest => dest.FeatureDescription, src => src.FeatureDescription ?? "")
|
.Map(dest => dest.FeatureDescription, src => src.FeatureDescription ?? "")
|
||||||
.Map(dest => dest.IsActive, src => src.IsActive)
|
.Map(dest => dest.IsActive, src => src.IsActive)
|
||||||
.Map(dest => dest.GrantedAt, src => Timestamp.FromDateTime(DateTime.SpecifyKind(src.GrantedAt, DateTimeKind.Utc)))
|
.Map(dest => dest.GrantedAt, src => Timestamp.FromDateTime(DateTime.SpecifyKind(src.GrantedAt, DateTimeKind.Utc)))
|
||||||
.Map(dest => dest.Notes, src => src.Notes ?? "");
|
.Map(dest => dest.Notes, src => src.Notes ?? "")
|
||||||
|
.Map(dest => dest.SortOrder, src => src.SortOrder)
|
||||||
|
.Map(dest => dest.CreatedAt, src => Timestamp.FromDateTime(DateTime.SpecifyKind(src.CreatedAt, DateTimeKind.Utc)));
|
||||||
|
|
||||||
// List<UserClubFeatureDto> → GetUserClubFeaturesResponse
|
// List<UserClubFeatureDto> → GetUserClubFeaturesResponse
|
||||||
config.NewConfig<List<UserClubFeatureDto>, GetUserClubFeaturesResponse>()
|
config.NewConfig<List<UserClubFeatureDto>, GetUserClubFeaturesResponse>()
|
||||||
|
|||||||
Reference in New Issue
Block a user