This commit is contained in:
lumijiez
2025-06-20 14:54:19 +03:00
parent b418607a89
commit 92bdb8444b
46 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
using AutoMapper;
using Imprink.Application.Products.Dtos;
using Imprink.Domain.Entities.Product;
using MediatR;
namespace Imprink.Application.Domains.ProductVariants;
public class CreateProductVariantCommand : IRequest<ProductVariantDto>
{
public Guid ProductId { get; set; }
public string Size { get; set; } = null!;
public string? Color { get; set; }
public decimal Price { get; set; }
public string? ImageUrl { get; set; }
public string Sku { get; set; } = null!;
public int StockQuantity { get; set; }
public bool IsActive { get; set; } = true;
}
public class CreateProductVariantHandler(IUnitOfWork unitOfWork, IMapper mapper)
: IRequestHandler<CreateProductVariantCommand, ProductVariantDto>
{
public async Task<ProductVariantDto> Handle(CreateProductVariantCommand request, CancellationToken cancellationToken)
{
await unitOfWork.BeginTransactionAsync(cancellationToken);
try
{
var productVariant = mapper.Map<ProductVariant>(request);
productVariant.Product = null!;
var createdVariant = await unitOfWork.ProductVariantRepository.AddAsync(productVariant, cancellationToken);
await unitOfWork.SaveAsync(cancellationToken);
await unitOfWork.CommitTransactionAsync(cancellationToken);
return mapper.Map<ProductVariantDto>(createdVariant);
}
catch
{
await unitOfWork.RollbackTransactionAsync(cancellationToken);
throw;
}
}
}

View File

@@ -0,0 +1,39 @@
using MediatR;
namespace Imprink.Application.Domains.ProductVariants;
public class DeleteProductVariantCommand : IRequest<bool>
{
public Guid Id { get; set; }
}
public class DeleteProductVariantHandler(IUnitOfWork unitOfWork) : IRequestHandler<DeleteProductVariantCommand, bool>
{
public async Task<bool> Handle(DeleteProductVariantCommand request, CancellationToken cancellationToken)
{
await unitOfWork.BeginTransactionAsync(cancellationToken);
try
{
var exists = await unitOfWork.ProductVariantRepository.ExistsAsync(request.Id, cancellationToken);
if (!exists)
{
await unitOfWork.RollbackTransactionAsync(cancellationToken);
return false;
}
await unitOfWork.ProductVariantRepository.DeleteAsync(request.Id, cancellationToken);
await unitOfWork.SaveAsync(cancellationToken);
await unitOfWork.CommitTransactionAsync(cancellationToken);
return true;
}
catch
{
await unitOfWork.RollbackTransactionAsync(cancellationToken);
throw;
}
}
}

View File

@@ -0,0 +1,45 @@
using AutoMapper;
using Imprink.Application.Products.Dtos;
using Imprink.Domain.Entities.Product;
using MediatR;
using Microsoft.Extensions.Logging;
namespace Imprink.Application.Domains.ProductVariants;
public class GetProductVariantsQuery : IRequest<IEnumerable<ProductVariantDto>>
{
public Guid? ProductId { get; set; }
public bool? IsActive { get; set; }
public bool InStockOnly { get; set; } = false;
}
public class GetProductVariantsHandler(IUnitOfWork unitOfWork, IMapper mapper, ILogger<GetProductVariantsHandler> logger)
: IRequestHandler<GetProductVariantsQuery, IEnumerable<ProductVariantDto>>
{
public async Task<IEnumerable<ProductVariantDto>> Handle(GetProductVariantsQuery request, CancellationToken cancellationToken)
{
IEnumerable<ProductVariant> variants;
if (request.ProductId.HasValue)
{
if (request.InStockOnly)
{
variants = await unitOfWork.ProductVariantRepository.GetInStockByProductIdAsync(request.ProductId.Value, cancellationToken);
}
else if (request.IsActive.HasValue && request.IsActive.Value)
{
variants = await unitOfWork.ProductVariantRepository.GetActiveByProductIdAsync(request.ProductId.Value, cancellationToken);
}
else
{
variants = await unitOfWork.ProductVariantRepository.GetByProductIdAsync(request.ProductId.Value, cancellationToken);
}
}
else
{
variants = new List<ProductVariant>();
}
return mapper.Map<IEnumerable<ProductVariantDto>>(variants);
}
}

View File

@@ -0,0 +1,50 @@
using AutoMapper;
using Imprink.Application.Exceptions;
using Imprink.Application.Products.Dtos;
using MediatR;
namespace Imprink.Application.Domains.ProductVariants;
public class UpdateProductVariantCommand : IRequest<ProductVariantDto>
{
public Guid Id { get; set; }
public Guid ProductId { get; set; }
public string Size { get; set; } = null!;
public string? Color { get; set; }
public decimal Price { get; set; }
public string? ImageUrl { get; set; }
public string Sku { get; set; } = null!;
public int StockQuantity { get; set; }
public bool IsActive { get; set; }
}
public class UpdateProductVariantHandler(IUnitOfWork unitOfWork, IMapper mapper)
: IRequestHandler<UpdateProductVariantCommand, ProductVariantDto>
{
public async Task<ProductVariantDto> Handle(UpdateProductVariantCommand request, CancellationToken cancellationToken)
{
await unitOfWork.BeginTransactionAsync(cancellationToken);
try
{
var existingVariant = await unitOfWork.ProductVariantRepository.GetByIdAsync(request.Id, cancellationToken);
if (existingVariant == null)
throw new NotFoundException($"Product variant with ID {request.Id} not found.");
mapper.Map(request, existingVariant);
var updatedVariant = await unitOfWork.ProductVariantRepository.UpdateAsync(existingVariant, cancellationToken);
await unitOfWork.SaveAsync(cancellationToken);
await unitOfWork.CommitTransactionAsync(cancellationToken);
return mapper.Map<ProductVariantDto>(updatedVariant);
}
catch
{
await unitOfWork.RollbackTransactionAsync(cancellationToken);
throw;
}
}
}

View File

@@ -0,0 +1,17 @@
namespace Imprink.Application.Products.Dtos;
public class ProductVariantDto
{
public Guid Id { get; set; }
public Guid ProductId { get; set; }
public string Size { get; set; } = null!;
public string? Color { get; set; }
public decimal Price { get; set; }
public string? ImageUrl { get; set; }
public string Sku { get; set; } = null!;
public int StockQuantity { get; set; }
public bool IsActive { get; set; }
public ProductDto? Product { get; set; }
public DateTime? CreatedAt { get; set; }
public DateTime? ModifiedAt { get; set; }
}

View File

@@ -0,0 +1,41 @@
using FluentValidation;
using Imprink.Application.Domains.ProductVariants;
namespace Imprink.Application.Validation.ProductVariants;
public class CreateProductVariantCommandValidator : AbstractValidator<CreateProductVariantCommand>
{
public CreateProductVariantCommandValidator()
{
RuleFor(x => x.ProductId)
.NotEmpty().WithMessage("ProductId is required.")
.NotEqual(Guid.Empty).WithMessage("ProductId must be a valid GUID.");
RuleFor(x => x.Size)
.NotEmpty().WithMessage("Size is required.")
.Length(1, 50).WithMessage("Size must be between 1 and 50 characters.");
RuleFor(x => x.Color)
.Length(1, 50).WithMessage("Color must be between 1 and 50 characters.")
.When(x => !string.IsNullOrWhiteSpace(x.Color));
RuleFor(x => x.Price)
.GreaterThan(0).WithMessage("Price must be greater than 0.");
RuleFor(x => x.ImageUrl)
.Must(BeValidUrl).When(x => !string.IsNullOrWhiteSpace(x.ImageUrl))
.WithMessage("ImageUrl must be a valid URL.");
RuleFor(x => x.Sku)
.NotEmpty().WithMessage("SKU is required.")
.Length(1, 50).WithMessage("SKU must be between 1 and 50 characters.");
RuleFor(x => x.StockQuantity)
.GreaterThanOrEqualTo(0).WithMessage("StockQuantity cannot be negative.");
}
private static bool BeValidUrl(string? url)
{
return Uri.TryCreate(url, UriKind.Absolute, out _);
}
}

View File

@@ -0,0 +1,14 @@
using FluentValidation;
using Imprink.Application.Domains.ProductVariants;
namespace Imprink.Application.Validation.ProductVariants;
public class DeleteProductVariantCommandValidator : AbstractValidator<DeleteProductVariantCommand>
{
public DeleteProductVariantCommandValidator()
{
RuleFor(x => x.Id)
.NotEmpty().WithMessage("Id is required.")
.NotEqual(Guid.Empty).WithMessage("Id must be a valid GUID.");
}
}

View File

@@ -0,0 +1,14 @@
using FluentValidation;
using Imprink.Application.Domains.ProductVariants;
namespace Imprink.Application.Validation.ProductVariants;
public class GetProductVariantsQueryValidator : AbstractValidator<GetProductVariantsQuery>
{
public GetProductVariantsQueryValidator()
{
RuleFor(x => x.ProductId)
.NotEqual(Guid.Empty).When(x => x.ProductId.HasValue)
.WithMessage("ProductId must be a valid GUID when provided.");
}
}

View File

@@ -0,0 +1,45 @@
using FluentValidation;
using Imprink.Application.Domains.ProductVariants;
namespace Imprink.Application.Validation.ProductVariants;
public class UpdateProductVariantCommandValidator : AbstractValidator<UpdateProductVariantCommand>
{
public UpdateProductVariantCommandValidator()
{
RuleFor(x => x.Id)
.NotEmpty().WithMessage("Id is required.")
.NotEqual(Guid.Empty).WithMessage("Id must be a valid GUID.");
RuleFor(x => x.ProductId)
.NotEmpty().WithMessage("ProductId is required.")
.NotEqual(Guid.Empty).WithMessage("ProductId must be a valid GUID.");
RuleFor(x => x.Size)
.NotEmpty().WithMessage("Size is required.")
.Length(1, 50).WithMessage("Size must be between 1 and 50 characters.");
RuleFor(x => x.Color)
.Length(1, 50).WithMessage("Color must be between 1 and 50 characters.")
.When(x => !string.IsNullOrWhiteSpace(x.Color));
RuleFor(x => x.Price)
.GreaterThan(0).WithMessage("Price must be greater than 0.");
RuleFor(x => x.ImageUrl)
.Must(BeValidUrl).When(x => !string.IsNullOrWhiteSpace(x.ImageUrl))
.WithMessage("ImageUrl must be a valid URL.");
RuleFor(x => x.Sku)
.NotEmpty().WithMessage("SKU is required.")
.Length(1, 50).WithMessage("SKU must be between 1 and 50 characters.");
RuleFor(x => x.StockQuantity)
.GreaterThanOrEqualTo(0).WithMessage("StockQuantity cannot be negative.");
}
private static bool BeValidUrl(string? url)
{
return Uri.TryCreate(url, UriKind.Absolute, out _);
}
}