From b466a39e89dd5b158cd3a062bca4c617da094c44 Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Sun, 4 May 2025 23:42:28 +0300 Subject: [PATCH] Good structure so far --- .../Entities/Products/Product.cs | 15 +- .../Entities/Products/ProductGroup.cs | 5 +- .../Entities/Products/ProductType.cs | 15 +- .../Entities/Products/ProductVariant.cs | 30 +++- .../Repositories/IProductGroupRepository.cs | 10 +- .../Repositories/IProductRepository.cs | 12 +- .../Repositories/IProductTypeRepository.cs | 10 +- .../Repositories/IProductVariantRepository.cs | 9 +- .../DbEntities/Products/ProductDbEntity.cs | 20 ++- .../Products/ProductGroupDbEntity.cs | 12 +- .../Products/ProductTypeDbEntity.cs | 24 ++- .../Products/ProductVariantDbEntity.cs | 33 +++- .../Mapping/ProductMappingProfile.cs | 46 +++++- .../Repositories/ProductGroupRepository.cs | 117 ++++++------- .../Repositories/ProductRepository.cs | 155 ++++++++++-------- .../Repositories/ProductTypeRepository.cs | 124 ++++++++------ .../Repositories/ProductVariantRepository.cs | 120 +++++++++----- .../Printbase.Application.Tests/UnitTest1.cs | 10 -- tests/Printbase.Domain.Tests/UnitTest1.cs | 10 -- .../Printbase.Infrastructure.Tests.csproj | 4 + .../UnitTest1.cs | 10 -- tests/Printbase.WebApi.Tests/UnitTest1.cs | 10 -- 22 files changed, 466 insertions(+), 335 deletions(-) delete mode 100644 tests/Printbase.Application.Tests/UnitTest1.cs delete mode 100644 tests/Printbase.Domain.Tests/UnitTest1.cs delete mode 100644 tests/Printbase.Infrastructure.Tests/UnitTest1.cs delete mode 100644 tests/Printbase.WebApi.Tests/UnitTest1.cs diff --git a/src/Printbase.Domain/Entities/Products/Product.cs b/src/Printbase.Domain/Entities/Products/Product.cs index 1fe8d03..573ed8d 100644 --- a/src/Printbase.Domain/Entities/Products/Product.cs +++ b/src/Printbase.Domain/Entities/Products/Product.cs @@ -2,10 +2,13 @@ namespace Printbase.Domain.Entities.Products; public class Product { - public Guid Id { get; init; } - public string Name { get; init; } = string.Empty; - public string? Description { get; init; } - public Guid TypeId { get; init; } - public ProductType Type { get; init; } = null!; - public ICollection Variants { get; init; } = []; + public Guid Id { get; set; } + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public Guid TypeId { get; set; } + public ProductType Type { get; set; } = null!; + public ICollection Variants { get; set; } = new List(); + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + public bool IsActive { get; set; } } \ No newline at end of file diff --git a/src/Printbase.Domain/Entities/Products/ProductGroup.cs b/src/Printbase.Domain/Entities/Products/ProductGroup.cs index a385996..e608022 100644 --- a/src/Printbase.Domain/Entities/Products/ProductGroup.cs +++ b/src/Printbase.Domain/Entities/Products/ProductGroup.cs @@ -5,5 +5,8 @@ public class ProductGroup public Guid Id { get; set; } public string Name { get; set; } = string.Empty; public string? Description { get; set; } - public ICollection Types { get; set; } = []; + public ICollection Types { get; set; } = new List(); + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + public bool IsActive { get; set; } } \ No newline at end of file diff --git a/src/Printbase.Domain/Entities/Products/ProductType.cs b/src/Printbase.Domain/Entities/Products/ProductType.cs index d382195..b12d6b0 100644 --- a/src/Printbase.Domain/Entities/Products/ProductType.cs +++ b/src/Printbase.Domain/Entities/Products/ProductType.cs @@ -2,10 +2,13 @@ namespace Printbase.Domain.Entities.Products; public class ProductType { - public Guid Id { get; init; } - public string Name { get; init; } = string.Empty; - public string? Description { get; init; } - public Guid GroupId { get; init; } - public ProductGroup Group { get; init; } = null!; - public ICollection? Products { get; init; } + public Guid Id { get; set; } + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public Guid GroupId { get; set; } + public ProductGroup Group { get; set; } = null!; + public ICollection Products { get; set; } = new List(); + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + public bool IsActive { get; set; } } \ No newline at end of file diff --git a/src/Printbase.Domain/Entities/Products/ProductVariant.cs b/src/Printbase.Domain/Entities/Products/ProductVariant.cs index cf56254..c6246ba 100644 --- a/src/Printbase.Domain/Entities/Products/ProductVariant.cs +++ b/src/Printbase.Domain/Entities/Products/ProductVariant.cs @@ -2,12 +2,26 @@ namespace Printbase.Domain.Entities.Products; public class ProductVariant { - public Guid Id { get; init; } - public Guid ProductId { get; init; } - public string? Color { get; init; } - public string? Size { get; init; } - public decimal Price { get; init; } - public decimal? Discount { get; init; } - public int Stock { get; init; } - public Product Product { get; init; } = null!; + public Guid Id { get; set; } + public Guid ProductId { get; set; } + public string? Color { get; set; } + public string? Size { get; set; } + public decimal Price { get; set; } + public decimal? Discount { get; set; } + public int Stock { get; set; } + public string? SKU { get; set; } + public Product Product { get; set; } = null!; + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + public bool IsActive { get; set; } + + public decimal GetDiscountedPrice() + { + if (Discount is > 0) + { + return Price - Price * Discount.Value / 100m; + } + + return Price; + } } \ No newline at end of file diff --git a/src/Printbase.Domain/Repositories/IProductGroupRepository.cs b/src/Printbase.Domain/Repositories/IProductGroupRepository.cs index d2e2dca..ab963a0 100644 --- a/src/Printbase.Domain/Repositories/IProductGroupRepository.cs +++ b/src/Printbase.Domain/Repositories/IProductGroupRepository.cs @@ -4,10 +4,10 @@ namespace Printbase.Domain.Repositories; public interface IProductGroupRepository { - Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); - Task> GetAllAsync(CancellationToken cancellationToken = default); + Task GetByIdAsync(Guid id, bool includeRelations = false, CancellationToken cancellationToken = default); + Task> GetAllAsync(bool includeRelations = false, CancellationToken cancellationToken = default); + Task AddAsync(ProductGroup group, CancellationToken cancellationToken = default); + Task UpdateAsync(ProductGroup group, CancellationToken cancellationToken = default); + Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); Task ExistsAsync(Guid id, CancellationToken cancellationToken = default); - Task AddAsync(ProductGroup productGroup, CancellationToken cancellationToken = default); - Task UpdateAsync(ProductGroup productGroup, CancellationToken cancellationToken = default); - Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/Printbase.Domain/Repositories/IProductRepository.cs b/src/Printbase.Domain/Repositories/IProductRepository.cs index 33ccd9e..f810121 100644 --- a/src/Printbase.Domain/Repositories/IProductRepository.cs +++ b/src/Printbase.Domain/Repositories/IProductRepository.cs @@ -4,11 +4,11 @@ namespace Printbase.Domain.Repositories; public interface IProductRepository { - Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); - Task> GetAllAsync(CancellationToken cancellationToken = default); - Task> GetByTypeIdAsync(Guid typeId, CancellationToken cancellationToken = default); + Task GetByIdAsync(Guid id, bool includeRelations = false, CancellationToken cancellationToken = default); + Task> GetAllAsync(bool includeRelations = false, CancellationToken cancellationToken = default); + Task> GetByTypeIdAsync(Guid typeId, bool includeRelations = false, CancellationToken cancellationToken = default); + Task AddAsync(Product product, CancellationToken cancellationToken = default); + Task UpdateAsync(Product product, CancellationToken cancellationToken = default); + Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); Task ExistsAsync(Guid id, CancellationToken cancellationToken = default); - Task AddAsync(Product product, CancellationToken cancellationToken = default); - Task UpdateAsync(Product product, CancellationToken cancellationToken = default); - Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/Printbase.Domain/Repositories/IProductTypeRepository.cs b/src/Printbase.Domain/Repositories/IProductTypeRepository.cs index cf9906c..0603b59 100644 --- a/src/Printbase.Domain/Repositories/IProductTypeRepository.cs +++ b/src/Printbase.Domain/Repositories/IProductTypeRepository.cs @@ -4,11 +4,11 @@ namespace Printbase.Domain.Repositories; public interface IProductTypeRepository { - Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); - Task> GetAllAsync(CancellationToken cancellationToken = default); + Task GetByIdAsync(Guid id, bool includeRelations = false, CancellationToken cancellationToken = default); + Task> GetAllAsync(bool includeRelations = false, CancellationToken cancellationToken = default); Task> GetByGroupIdAsync(Guid groupId, CancellationToken cancellationToken = default); + Task AddAsync(ProductType type, CancellationToken cancellationToken = default); + Task UpdateAsync(ProductType type, CancellationToken cancellationToken = default); + Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); Task ExistsAsync(Guid id, CancellationToken cancellationToken = default); - Task AddAsync(ProductType productType, CancellationToken cancellationToken = default); - Task UpdateAsync(ProductType productType, CancellationToken cancellationToken = default); - Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/Printbase.Domain/Repositories/IProductVariantRepository.cs b/src/Printbase.Domain/Repositories/IProductVariantRepository.cs index 6f14a60..de6b560 100644 --- a/src/Printbase.Domain/Repositories/IProductVariantRepository.cs +++ b/src/Printbase.Domain/Repositories/IProductVariantRepository.cs @@ -4,10 +4,11 @@ namespace Printbase.Domain.Repositories; public interface IProductVariantRepository { - Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); + Task GetByIdAsync(Guid id, bool includeRelations = false, CancellationToken cancellationToken = default); + Task> GetAllAsync(bool includeRelations = false, CancellationToken cancellationToken = default); Task> GetByProductIdAsync(Guid productId, CancellationToken cancellationToken = default); + Task AddAsync(ProductVariant variant, CancellationToken cancellationToken = default); + Task UpdateAsync(ProductVariant variant, CancellationToken cancellationToken = default); + Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); Task ExistsAsync(Guid id, CancellationToken cancellationToken = default); - Task AddAsync(ProductVariant productVariant, CancellationToken cancellationToken = default); - Task UpdateAsync(ProductVariant productVariant, CancellationToken cancellationToken = default); - Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/Printbase.Infrastructure/DbEntities/Products/ProductDbEntity.cs b/src/Printbase.Infrastructure/DbEntities/Products/ProductDbEntity.cs index e0b0c64..06a7e96 100644 --- a/src/Printbase.Infrastructure/DbEntities/Products/ProductDbEntity.cs +++ b/src/Printbase.Infrastructure/DbEntities/Products/ProductDbEntity.cs @@ -7,20 +7,28 @@ namespace Printbase.Infrastructure.DbEntities.Products; public class ProductDbEntity { [Key, Required] - public Guid Id { get; init; } + public Guid Id { get; set; } [MaxLength(50), Required] - public required string Name { get; init; } + public required string Name { get; set; } [MaxLength(1000)] - public string? Description { get; init; } + public string? Description { get; set; } [Required] - public Guid TypeId { get; init; } + public Guid TypeId { get; set; } + + [Required] + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + public DateTime? UpdatedAt { get; set; } + + [Required] + public bool IsActive { get; set; } = true; [ForeignKey(nameof(TypeId)), Required] - public required ProductTypeDbEntity Type { get; init; } + public required ProductTypeDbEntity Type { get; set; } [InverseProperty(nameof(ProductVariantDbEntity.Product)), Required] - public required ICollection Variants { get; init; } = []; + public required ICollection Variants { get; set; } = []; } \ No newline at end of file diff --git a/src/Printbase.Infrastructure/DbEntities/Products/ProductGroupDbEntity.cs b/src/Printbase.Infrastructure/DbEntities/Products/ProductGroupDbEntity.cs index 08bacd0..b9b4285 100644 --- a/src/Printbase.Infrastructure/DbEntities/Products/ProductGroupDbEntity.cs +++ b/src/Printbase.Infrastructure/DbEntities/Products/ProductGroupDbEntity.cs @@ -1,9 +1,11 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; namespace Printbase.Infrastructure.DbEntities.Products; [Table("ProductGroups")] +[Index(nameof(Name), IsUnique = true)] public class ProductGroupDbEntity { [Key, Required] @@ -16,5 +18,13 @@ public class ProductGroupDbEntity public string? Description { get; set; } [InverseProperty(nameof(ProductTypeDbEntity.Group)), Required] - public required ICollection Types { get; set; } + public required ICollection Types { get; set; } = []; + + [Required] + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + public DateTime? UpdatedAt { get; set; } + + [Required] + public bool IsActive { get; set; } = true; } \ No newline at end of file diff --git a/src/Printbase.Infrastructure/DbEntities/Products/ProductTypeDbEntity.cs b/src/Printbase.Infrastructure/DbEntities/Products/ProductTypeDbEntity.cs index 646f45d..4cda53f 100644 --- a/src/Printbase.Infrastructure/DbEntities/Products/ProductTypeDbEntity.cs +++ b/src/Printbase.Infrastructure/DbEntities/Products/ProductTypeDbEntity.cs @@ -1,26 +1,36 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; namespace Printbase.Infrastructure.DbEntities.Products; [Table("ProductTypes")] +[Index(nameof(Name), nameof(GroupId), IsUnique = true)] public class ProductTypeDbEntity { [Key, Required] - public Guid Id { get; init; } + public Guid Id { get; set; } [MaxLength(50), Required] - public required string Name { get; init; } + public required string Name { get; set; } [MaxLength(255)] - public string? Description { get; init; } + public string? Description { get; set; } [Required] - public Guid GroupId { get; init; } + public Guid GroupId { get; set; } [ForeignKey(nameof(GroupId)), Required] - public required ProductGroupDbEntity Group { get; init; } + public required ProductGroupDbEntity Group { get; set; } - [InverseProperty(nameof(ProductDbEntity.Type))] - public ICollection? Products { get; init; } + [InverseProperty(nameof(ProductDbEntity.Type)), Required] + public required ICollection Products { get; set; } = []; + + [Required] + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + public DateTime? UpdatedAt { get; set; } + + [Required] + public bool IsActive { get; set; } = true; } \ No newline at end of file diff --git a/src/Printbase.Infrastructure/DbEntities/Products/ProductVariantDbEntity.cs b/src/Printbase.Infrastructure/DbEntities/Products/ProductVariantDbEntity.cs index ab10212..0c5f48b 100644 --- a/src/Printbase.Infrastructure/DbEntities/Products/ProductVariantDbEntity.cs +++ b/src/Printbase.Infrastructure/DbEntities/Products/ProductVariantDbEntity.cs @@ -1,32 +1,47 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; namespace Printbase.Infrastructure.DbEntities.Products; [Table("ProductVariants")] +[Index(nameof(ProductId), nameof(Color), nameof(Size), IsUnique = true)] public class ProductVariantDbEntity { [Key, Required] - public Guid Id { get; init; } + public Guid Id { get; set; } [Required] - public Guid ProductId { get; init; } + public Guid ProductId { get; set; } [MaxLength(50)] - public string? Color { get; init; } + public string? Color { get; set; } [MaxLength(20)] - public string? Size { get; init; } + public string? Size { get; set; } [Column(TypeName = "decimal(18,2)"), Required] - public decimal Price { get; init; } + [Range(0.01, 9999999.99)] + public decimal Price { get; set; } - [Column(TypeName = "decimal(18,2)")] - public decimal? Discount { get; init; } + [Range(0, 100)] + public decimal? Discount { get; set; } [Required] - public int Stock { get; init; } + [Range(0, int.MaxValue)] + public int Stock { get; set; } + + [MaxLength(50)] + public string? SKU { get; set; } [ForeignKey(nameof(ProductId)), Required] - public required ProductDbEntity Product { get; init; } + public required ProductDbEntity Product { get; set; } + + [Required] + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + public DateTime? UpdatedAt { get; set; } + + [Required] + public bool IsActive { get; set; } = true; } \ No newline at end of file diff --git a/src/Printbase.Infrastructure/Mapping/ProductMappingProfile.cs b/src/Printbase.Infrastructure/Mapping/ProductMappingProfile.cs index cc1c52c..2bcc594 100644 --- a/src/Printbase.Infrastructure/Mapping/ProductMappingProfile.cs +++ b/src/Printbase.Infrastructure/Mapping/ProductMappingProfile.cs @@ -8,14 +8,44 @@ public class ProductMappingProfile : Profile { public ProductMappingProfile() { - CreateMap(); - CreateMap(); - CreateMap(); - CreateMap(); + CreateMap() + .ForMember(dest => dest.Type, + opt => opt.MapFrom(src => src.Type)) + .ForMember(dest => dest.Variants, + opt => opt.MapFrom(src => src.Variants)); + + CreateMap() + .ForMember(dest => dest.Type, + opt => opt.Ignore()) // in repo + .ForMember(dest => dest.Variants, + opt => opt.Ignore()); // in repo - CreateMap(); - CreateMap(); - CreateMap(); - CreateMap(); + CreateMap() + .ForMember(dest => dest.Product, + opt => opt.MapFrom(src => src.Product)); + + CreateMap() + .ForMember(dest => dest.Product, + opt => opt.Ignore()); // in repo + + CreateMap() + .ForMember(dest => dest.Group, + opt => opt.MapFrom(src => src.Group)) + .ForMember(dest => dest.Products, + opt => opt.MapFrom(src => src.Products)); + + CreateMap() + .ForMember(dest => dest.Group, + opt => opt.Ignore()) // in repo + .ForMember(dest => dest.Products, + opt => opt.Ignore()); // in repo + + CreateMap() + .ForMember(dest => dest.Types, + opt => opt.MapFrom(src => src.Types)); + + CreateMap() + .ForMember(dest => dest.Types, + opt => opt.Ignore()); // in repo } } \ No newline at end of file diff --git a/src/Printbase.Infrastructure/Repositories/ProductGroupRepository.cs b/src/Printbase.Infrastructure/Repositories/ProductGroupRepository.cs index dddab4e..29e04c4 100644 --- a/src/Printbase.Infrastructure/Repositories/ProductGroupRepository.cs +++ b/src/Printbase.Infrastructure/Repositories/ProductGroupRepository.cs @@ -12,80 +12,69 @@ public class ProductGroupRepository(ApplicationDbContext dbContext, IMapper mapp private readonly ApplicationDbContext _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); private readonly IMapper _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); - public async Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default) + public async Task GetByIdAsync(Guid id, bool includeRelations = false, CancellationToken cancellationToken = default) { - var dbEntity = await _dbContext.Set() - .Include(g => g.Types) - .FirstOrDefaultAsync(g => g.Id == id, cancellationToken); + IQueryable query = _dbContext.ProductGroups; - return dbEntity != null ? _mapper.Map(dbEntity) : null; + if (includeRelations) query = query.Include(g => g.Types); + + var dbEntity = await query.FirstOrDefaultAsync(g => g.Id == id, cancellationToken); + + return dbEntity == null ? null : _mapper.Map(dbEntity); } - public async Task> GetAllAsync(CancellationToken cancellationToken = default) + public async Task> GetAllAsync(bool includeRelations = false, CancellationToken cancellationToken = default) { - var dbEntities = await _dbContext.Set() - .Include(g => g.Types) - .ToListAsync(cancellationToken); + IQueryable query = _dbContext.ProductGroups; + + if (includeRelations) query = query.Include(g => g.Types); + + var dbEntities = await query.ToListAsync(cancellationToken); return _mapper.Map>(dbEntities); } + public async Task AddAsync(ProductGroup group, CancellationToken cancellationToken = default) + { + var dbEntity = _mapper.Map(group); + + _dbContext.ProductGroups.Add(dbEntity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return _mapper.Map(dbEntity); + } + + public async Task UpdateAsync(ProductGroup group, CancellationToken cancellationToken = default) + { + var existingEntity = await _dbContext.ProductGroups + .Include(g => g.Types) + .FirstOrDefaultAsync(g => g.Id == group.Id, cancellationToken); + + if (existingEntity == null) throw new KeyNotFoundException($"ProductGroup with ID {group.Id} not found"); + + _mapper.Map(group, existingEntity); + + existingEntity.UpdatedAt = DateTime.UtcNow; + + await _dbContext.SaveChangesAsync(cancellationToken); + + return _mapper.Map(existingEntity); + } + + public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default) + { + var entity = await _dbContext.ProductGroups.FindAsync([id], cancellationToken); + + if (entity == null) return false; + + _dbContext.ProductGroups.Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return true; + } + public async Task ExistsAsync(Guid id, CancellationToken cancellationToken = default) { - return await _dbContext.Set() - .AnyAsync(g => g.Id == id, cancellationToken); - } - - public async Task AddAsync(ProductGroup productGroup, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(productGroup); - - var dbEntity = _mapper.Map(productGroup); - await _dbContext.Set().AddAsync(dbEntity, cancellationToken); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task UpdateAsync(ProductGroup productGroup, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(productGroup); - - var dbEntity = await _dbContext.Set() - .Include(g => g.Types) - .FirstOrDefaultAsync(g => g.Id == productGroup.Id, cancellationToken); - - if (dbEntity == null) - throw new KeyNotFoundException($"ProductGroup with ID {productGroup.Id} not found"); - - _mapper.Map(productGroup, dbEntity); - - var existingTypeIds = dbEntity.Types.Select(t => t.Id).ToList(); - var updatedTypeIds = productGroup.Types.Select(t => t.Id).ToList(); - - var typesToRemove = dbEntity.Types.Where(t => !updatedTypeIds.Contains(t.Id)).ToList(); - foreach (var type in typesToRemove) - { - dbEntity.Types.Remove(type); - } - - foreach (var type in productGroup.Types) - { - if (existingTypeIds.Contains(type.Id)) continue; - var newType = _mapper.Map(type); - dbEntity.Types.Add(newType); - } - - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default) - { - var dbEntity = await _dbContext.Set() - .FindAsync([id], cancellationToken); - - if (dbEntity == null) - return; - - _dbContext.Set().Remove(dbEntity); - await _dbContext.SaveChangesAsync(cancellationToken); + return await _dbContext.ProductGroups.AnyAsync(g => g.Id == id, cancellationToken); } } \ No newline at end of file diff --git a/src/Printbase.Infrastructure/Repositories/ProductRepository.cs b/src/Printbase.Infrastructure/Repositories/ProductRepository.cs index 6854a0d..fa33590 100644 --- a/src/Printbase.Infrastructure/Repositories/ProductRepository.cs +++ b/src/Printbase.Infrastructure/Repositories/ProductRepository.cs @@ -12,89 +12,108 @@ public class ProductRepository(ApplicationDbContext dbContext, IMapper mapper) : private readonly ApplicationDbContext _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); private readonly IMapper _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); - public async Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default) + public async Task GetByIdAsync(Guid id, bool includeRelations = false, CancellationToken cancellationToken = default) { - var dbEntity = await _dbContext.Set() - .Include(p => p.Variants) - .FirstOrDefaultAsync(p => p.Id == id, cancellationToken); + IQueryable query = _dbContext.Products; - return dbEntity != null ? _mapper.Map(dbEntity) : null; + if (includeRelations) + { + query = query + .Include(p => p.Type) + .ThenInclude(t => t.Group) + .Include(p => p.Variants); + } + + var dbEntity = await query.FirstOrDefaultAsync(p => p.Id == id, cancellationToken); + + return dbEntity == null ? null : _mapper.Map(dbEntity); } - public async Task> GetAllAsync(CancellationToken cancellationToken = default) + public async Task> GetAllAsync(bool includeRelations = false, CancellationToken cancellationToken = default) { - var dbEntities = await _dbContext.Set() - .Include(p => p.Variants) - .ToListAsync(cancellationToken); + IQueryable query = _dbContext.Products; + + if (includeRelations) + { + query = query + .Include(p => p.Type) + .ThenInclude(t => t.Group) + .Include(p => p.Variants); + } + + var dbEntities = await query.ToListAsync(cancellationToken); return _mapper.Map>(dbEntities); } - public async Task> GetByTypeIdAsync(Guid typeId, CancellationToken cancellationToken = default) + public async Task> GetByTypeIdAsync(Guid typeId, bool includeRelations = false, CancellationToken cancellationToken = default) { - var dbEntities = await _dbContext.Set() - .Include(p => p.Variants) - .Where(p => p.TypeId == typeId) - .ToListAsync(cancellationToken); + var query = _dbContext.Products.Where(p => p.TypeId == typeId); + + if (includeRelations) + { + query = query + .Include(p => p.Type) + .ThenInclude(t => t.Group) + .Include(p => p.Variants); + } + + var dbEntities = await query.ToListAsync(cancellationToken); return _mapper.Map>(dbEntities); } + public async Task AddAsync(Product product, CancellationToken cancellationToken = default) + { + var dbEntity = _mapper.Map(product); + + dbEntity.Type = await _dbContext.ProductTypes.FindAsync([product.TypeId], cancellationToken) + ?? throw new InvalidOperationException($"ProductType with ID {product.TypeId} not found"); + + _dbContext.Products.Add(dbEntity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return _mapper.Map(dbEntity); + } + + public async Task UpdateAsync(Product product, CancellationToken cancellationToken = default) + { + var existingEntity = await _dbContext.Products + .Include(p => p.Variants) + .FirstOrDefaultAsync(p => p.Id == product.Id, cancellationToken); + + if (existingEntity == null) throw new KeyNotFoundException($"Product with ID {product.Id} not found"); + + _mapper.Map(product, existingEntity); + + if (existingEntity.TypeId != product.TypeId) + { + existingEntity.Type = await _dbContext.ProductTypes.FindAsync([product.TypeId], cancellationToken) + ?? throw new InvalidOperationException($"ProductType with ID {product.TypeId} not found"); + existingEntity.TypeId = product.TypeId; + } + + existingEntity.UpdatedAt = DateTime.UtcNow; + + await _dbContext.SaveChangesAsync(cancellationToken); + + return _mapper.Map(existingEntity); + } + + public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default) + { + var entity = await _dbContext.Products.FindAsync([id], cancellationToken); + + if (entity == null) return false; + + _dbContext.Products.Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return true; + } + public async Task ExistsAsync(Guid id, CancellationToken cancellationToken = default) { - return await _dbContext.Set() - .AnyAsync(p => p.Id == id, cancellationToken); - } - - public async Task AddAsync(Product product, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(product); - - var dbEntity = _mapper.Map(product); - await _dbContext.Set().AddAsync(dbEntity, cancellationToken); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task UpdateAsync(Product product, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(product); - - var dbEntity = await _dbContext.Set() - .Include(p => p.Variants) - .FirstOrDefaultAsync(p => p.Id == product.Id, cancellationToken); - - if (dbEntity == null) - throw new KeyNotFoundException($"Product with ID {product.Id} not found"); - - _mapper.Map(product, dbEntity); - - var existingVariantIds = dbEntity.Variants.Select(v => v.Id).ToList(); - var updatedVariantIds = product.Variants.Select(v => v.Id).ToList(); - - var variantsToRemove = dbEntity.Variants.Where(v => !updatedVariantIds.Contains(v.Id)).ToList(); - foreach (var variant in variantsToRemove) - { - dbEntity.Variants.Remove(variant); - } - - foreach (var variant in product.Variants) - { - if (existingVariantIds.Contains(variant.Id)) continue; - var newVariant = _mapper.Map(variant); - dbEntity.Variants.Add(newVariant); - } - - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default) - { - var dbEntity = await _dbContext.Set() - .FindAsync([id], cancellationToken); - - if (dbEntity == null) return; - - _dbContext.Set().Remove(dbEntity); - await _dbContext.SaveChangesAsync(cancellationToken); + return await _dbContext.Products.AnyAsync(p => p.Id == id, cancellationToken); } } \ No newline at end of file diff --git a/src/Printbase.Infrastructure/Repositories/ProductTypeRepository.cs b/src/Printbase.Infrastructure/Repositories/ProductTypeRepository.cs index 4916597..b016b72 100644 --- a/src/Printbase.Infrastructure/Repositories/ProductTypeRepository.cs +++ b/src/Printbase.Infrastructure/Repositories/ProductTypeRepository.cs @@ -12,69 +12,101 @@ public class ProductTypeRepository(ApplicationDbContext dbContext, IMapper mappe private readonly ApplicationDbContext _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); private readonly IMapper _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); - public async Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default) + public async Task GetByIdAsync(Guid id, bool includeRelations = false, CancellationToken cancellationToken = default) { - var dbEntity = await _dbContext.Set() - .FirstOrDefaultAsync(t => t.Id == id, cancellationToken); + IQueryable query = _dbContext.ProductTypes; - return dbEntity != null ? _mapper.Map(dbEntity) : null; + if (includeRelations) + { + query = query + .Include(t => t.Group) + .Include(t => t.Products); + } + + var dbEntity = await query.FirstOrDefaultAsync(t => t.Id == id, cancellationToken); + + return dbEntity == null ? null : _mapper.Map(dbEntity); } - public async Task> GetAllAsync(CancellationToken cancellationToken = default) + public async Task> GetAllAsync(bool includeRelations = false, CancellationToken cancellationToken = default) { - var dbEntities = await _dbContext.Set() - .ToListAsync(cancellationToken); + IQueryable query = _dbContext.ProductTypes; + + if (includeRelations) + { + query = query + .Include(t => t.Group) + .Include(t => t.Products); + } + + var dbEntities = await query.ToListAsync(cancellationToken); return _mapper.Map>(dbEntities); } public async Task> GetByGroupIdAsync(Guid groupId, CancellationToken cancellationToken = default) { - var dbEntities = await _dbContext.Set() + var dbEntities = await _dbContext.ProductTypes .Where(t => t.GroupId == groupId) .ToListAsync(cancellationToken); - + return _mapper.Map>(dbEntities); } + public async Task AddAsync(ProductType type, CancellationToken cancellationToken = default) + { + var dbEntity = _mapper.Map(type); + + dbEntity.Group = await _dbContext.ProductGroups.FindAsync([type.GroupId], cancellationToken) + ?? throw new InvalidOperationException($"ProductGroup with ID {type.GroupId} not found"); + + _dbContext.ProductTypes.Add(dbEntity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return _mapper.Map(dbEntity); + } + + public async Task UpdateAsync(ProductType type, CancellationToken cancellationToken = default) + { + var existingEntity = await _dbContext.ProductTypes + .Include(t => t.Products) + .FirstOrDefaultAsync(t => t.Id == type.Id, cancellationToken); + + if (existingEntity == null) throw new KeyNotFoundException($"ProductType with ID {type.Id} not found"); + + _mapper.Map(type, existingEntity); + + if (existingEntity.GroupId != type.GroupId) + { + existingEntity.Group = await _dbContext.ProductGroups.FindAsync([type.GroupId], cancellationToken) + ?? throw new InvalidOperationException($"ProductGroup with ID {type.GroupId} not found"); + existingEntity.GroupId = type.GroupId; + } + + existingEntity.UpdatedAt = DateTime.UtcNow; + + await _dbContext.SaveChangesAsync(cancellationToken); + + return _mapper.Map(existingEntity); + } + + public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default) + { + var entity = await _dbContext.ProductTypes.FindAsync([id], cancellationToken); + + if (entity == null) + { + return false; + } + + _dbContext.ProductTypes.Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return true; + } + public async Task ExistsAsync(Guid id, CancellationToken cancellationToken = default) { - return await _dbContext.Set() - .AnyAsync(t => t.Id == id, cancellationToken); - } - - public async Task AddAsync(ProductType productType, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(productType); - - var dbEntity = _mapper.Map(productType); - await _dbContext.Set().AddAsync(dbEntity, cancellationToken); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task UpdateAsync(ProductType productType, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(productType); - - var dbEntity = await _dbContext.Set() - .FirstOrDefaultAsync(t => t.Id == productType.Id, cancellationToken); - - if (dbEntity == null) - throw new KeyNotFoundException($"ProductType with ID {productType.Id} not found"); - - _mapper.Map(productType, dbEntity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default) - { - var dbEntity = await _dbContext.Set() - .FindAsync([id], cancellationToken); - - if (dbEntity == null) - return; - - _dbContext.Set().Remove(dbEntity); - await _dbContext.SaveChangesAsync(cancellationToken); + return await _dbContext.ProductTypes.AnyAsync(t => t.Id == id, cancellationToken); } } \ No newline at end of file diff --git a/src/Printbase.Infrastructure/Repositories/ProductVariantRepository.cs b/src/Printbase.Infrastructure/Repositories/ProductVariantRepository.cs index c556fc5..e2761fb 100644 --- a/src/Printbase.Infrastructure/Repositories/ProductVariantRepository.cs +++ b/src/Printbase.Infrastructure/Repositories/ProductVariantRepository.cs @@ -12,61 +12,91 @@ public class ProductVariantRepository(ApplicationDbContext dbContext, IMapper ma private readonly ApplicationDbContext _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); private readonly IMapper _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); - public async Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default) + public async Task GetByIdAsync(Guid id, bool includeRelations = false, CancellationToken cancellationToken = default) { - var dbEntity = await _dbContext.Set() - .FirstOrDefaultAsync(v => v.Id == id, cancellationToken); + IQueryable query = _dbContext.ProductVariants; - return dbEntity != null ? _mapper.Map(dbEntity) : null; + if (includeRelations) + query = query.Include(v => v.Product) + .ThenInclude(p => p.Type); + + var dbEntity = await query.FirstOrDefaultAsync(v => v.Id == id, cancellationToken); + + return dbEntity == null ? null : _mapper.Map(dbEntity); } - public async Task> GetByProductIdAsync(Guid productId, CancellationToken cancellationToken = default) + public async Task> GetAllAsync(bool includeRelations = false, CancellationToken cancellationToken = default) { - var dbEntities = await _dbContext.Set() - .Where(v => v.ProductId == productId) - .ToListAsync(cancellationToken); + IQueryable query = _dbContext.ProductVariants; + + if (includeRelations) + query = query.Include(v => v.Product) + .ThenInclude(p => p.Type); + + var dbEntities = await query.ToListAsync(cancellationToken); return _mapper.Map>(dbEntities); } + public async Task> GetByProductIdAsync(Guid productId, CancellationToken cancellationToken = default) + { + var dbEntities = await _dbContext.ProductVariants + .Where(v => v.ProductId == productId) + .ToListAsync(cancellationToken); + + return _mapper.Map>(dbEntities); + } + + public async Task AddAsync(ProductVariant variant, CancellationToken cancellationToken = default) + { + var dbEntity = _mapper.Map(variant); + + dbEntity.Product = await _dbContext.Products.FindAsync([variant.ProductId], cancellationToken) + ?? throw new InvalidOperationException($"Product with ID {variant.ProductId} not found"); + + _dbContext.ProductVariants.Add(dbEntity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return _mapper.Map(dbEntity); + } + + public async Task UpdateAsync(ProductVariant variant, CancellationToken cancellationToken = default) + { + var existingEntity = await _dbContext.ProductVariants + .FindAsync([variant.Id], cancellationToken); + + if (existingEntity == null) throw new KeyNotFoundException($"ProductVariant with ID {variant.Id} not found"); + + _mapper.Map(variant, existingEntity); + + if (existingEntity.ProductId != variant.ProductId) + { + existingEntity.Product = await _dbContext.Products.FindAsync([variant.ProductId], cancellationToken) + ?? throw new InvalidOperationException($"Product with ID {variant.ProductId} not found"); + existingEntity.ProductId = variant.ProductId; + } + + existingEntity.UpdatedAt = DateTime.UtcNow; + + await _dbContext.SaveChangesAsync(cancellationToken); + + return _mapper.Map(existingEntity); + } + + public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default) + { + var entity = await _dbContext.ProductVariants.FindAsync([id], cancellationToken); + + if (entity == null) return false; + + _dbContext.ProductVariants.Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return true; + } + public async Task ExistsAsync(Guid id, CancellationToken cancellationToken = default) { - return await _dbContext.Set() - .AnyAsync(v => v.Id == id, cancellationToken); - } - - public async Task AddAsync(ProductVariant productVariant, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(productVariant); - - var dbEntity = _mapper.Map(productVariant); - await _dbContext.Set().AddAsync(dbEntity, cancellationToken); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task UpdateAsync(ProductVariant productVariant, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(productVariant); - - var dbEntity = await _dbContext.Set() - .FirstOrDefaultAsync(v => v.Id == productVariant.Id, cancellationToken); - - if (dbEntity == null) - throw new KeyNotFoundException($"ProductVariant with ID {productVariant.Id} not found"); - - _mapper.Map(productVariant, dbEntity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default) - { - var dbEntity = await _dbContext.Set() - .FindAsync([id], cancellationToken); - - if (dbEntity == null) - return; - - _dbContext.Set().Remove(dbEntity); - await _dbContext.SaveChangesAsync(cancellationToken); + return await _dbContext.ProductVariants.AnyAsync(v => v.Id == id, cancellationToken); } } \ No newline at end of file diff --git a/tests/Printbase.Application.Tests/UnitTest1.cs b/tests/Printbase.Application.Tests/UnitTest1.cs deleted file mode 100644 index 63a23b5..0000000 --- a/tests/Printbase.Application.Tests/UnitTest1.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Printbase.Application.Tests; - -public class UnitTest1 -{ - [Fact] - public void Test1() - { - Assert.True(true); - } -} diff --git a/tests/Printbase.Domain.Tests/UnitTest1.cs b/tests/Printbase.Domain.Tests/UnitTest1.cs deleted file mode 100644 index 7012dc7..0000000 --- a/tests/Printbase.Domain.Tests/UnitTest1.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Printbase.Domain.Tests; - -public class UnitTest1 -{ - [Fact] - public void Test1() - { - Assert.True(true); - } -} diff --git a/tests/Printbase.Infrastructure.Tests/Printbase.Infrastructure.Tests.csproj b/tests/Printbase.Infrastructure.Tests/Printbase.Infrastructure.Tests.csproj index d7f0b2e..c6d6b68 100644 --- a/tests/Printbase.Infrastructure.Tests/Printbase.Infrastructure.Tests.csproj +++ b/tests/Printbase.Infrastructure.Tests/Printbase.Infrastructure.Tests.csproj @@ -18,4 +18,8 @@ + + + + diff --git a/tests/Printbase.Infrastructure.Tests/UnitTest1.cs b/tests/Printbase.Infrastructure.Tests/UnitTest1.cs deleted file mode 100644 index f01b6d4..0000000 --- a/tests/Printbase.Infrastructure.Tests/UnitTest1.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Printbase.Infrastructure.Tests; - -public class UnitTest1 -{ - [Fact] - public void Test1() - { - Assert.True(true); - } -} diff --git a/tests/Printbase.WebApi.Tests/UnitTest1.cs b/tests/Printbase.WebApi.Tests/UnitTest1.cs deleted file mode 100644 index cec8565..0000000 --- a/tests/Printbase.WebApi.Tests/UnitTest1.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Printbase.WebApi.Tests; - -public class UnitTest1 -{ - [Fact] - public void Test1() - { - Assert.True(true); - } -}