Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion API/Controllers/ProductsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public async Task<IActionResult> DeleteCategory(int id)
await productService.DeleteProductCategory(id);
return Ok();
}

/// <summary>
/// Re-order categories
/// </summary>
Expand All @@ -169,4 +169,40 @@ public async Task<IActionResult> OrderProducts(IList<int> ids)
await productService.OrderProducts(ids);
return Ok();
}

/// <summary>
/// Returns all price categories (e.g., Member, Guest, VIP)
/// </summary>
/// <returns></returns>
[HttpGet("price-category")]
public async Task<ActionResult<IList<PriceCategoryDto>>> GetPriceCategories()
{
return Ok(await unitOfWork.ProductRepository.GetAllPriceCategoryDtos());
}

/// <summary>
/// Create a new price category
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPost("price-category")]
[Authorize(Policy = PolicyConstants.ManageProducts)]
public async Task<ActionResult<PriceCategoryDto>> CreatePriceCategory(PriceCategoryDto dto)
{
var res = await productService.CreatePriceCategory(dto);
return Ok(res);
}

/// <summary>
/// Update an existing price category
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPut("price-category")]
[Authorize(Policy = PolicyConstants.ManageProducts)]
public async Task<IActionResult> UpdatePriceCategory(PriceCategoryDto dto)
{
await productService.UpdatePriceCategory(dto);
return Ok();
}
}
10 changes: 6 additions & 4 deletions API/DTOs/ClientDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ public sealed record ClientDto
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }

public string CompanyNumber { get; set; }
public string InvoiceEmail { get; set; }

public string ContactName { get; set; }
public string ContactEmail { get; set; }
public string ContactNumber { get; set; }


public int? DefaultPriceCategoryId { get; set; }

public bool New { get; set; }
}
}
12 changes: 7 additions & 5 deletions API/DTOs/DeliveryDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,24 @@ public class DeliveryDto
{
public int Id { get; set; }
public DeliveryState State { get; set; }

public int FromId { get; set; }
public UserDto From { get; set; }

public int ClientId { get; set; }
public ClientDto Recipient { get; set; }


public int PriceCategoryId { get; set; }

public string Message { get; set; }
/// <summary>
/// Cannot be changed via the API
/// </summary>
public IList<SystemMessage> SystemMessages { get; set; }
public IList<DeliveryLineDto> Lines { get; set; }

public DateTime Created { get; set; }
public DateTime CreatedUtc { get; set; }
public DateTime LastModified { get; set; }
public DateTime LastModifiedUtc { get; set; }
}
}
7 changes: 7 additions & 0 deletions API/DTOs/PriceCategoryDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace API.DTOs;

public class PriceCategoryDto
{
public int Id { get; set; }
public string Name { get; set; }
}
4 changes: 2 additions & 2 deletions API/DTOs/ProductDto.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using API.Entities;
using API.Entities.Enums;

namespace API.DTOs;
Expand All @@ -13,4 +12,5 @@ public sealed record ProductDto
public ProductType Type { get; set; }
public bool IsTracked { get; set; }
public bool Enabled { get; set; }
}
public Dictionary<int, float> Prices { get; set; }
}
27 changes: 18 additions & 9 deletions API/Data/DataContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public DataContext(DbContextOptions<DataContext> options): base(options)
ChangeTracker.Tracked += OnEntityTracked;
ChangeTracker.StateChanged += OnEntityStateChanged;
}

protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
Expand All @@ -24,21 +24,30 @@ protected override void OnModelCreating(ModelBuilder builder)
v => JsonSerializer.Serialize(v, JsonSerializerOptions.Default),
v => JsonSerializer.Deserialize<List<SystemMessage>>(v, JsonSerializerOptions.Default) ?? new List<SystemMessage>()
);

builder.Entity<Product>()
.Property(d => d.Prices)
.HasConversion(
v => JsonSerializer.Serialize(v, JsonSerializerOptions.Default),
v => JsonSerializer.Deserialize<Dictionary<int, float>>(v, JsonSerializerOptions.Default) ?? new Dictionary<int, float>()
).HasColumnType("TEXT")
.HasDefaultValue(new Dictionary<int, float>());
}

public DbSet<ManualMigration> ManualMigrations { get; set; }
public DbSet<ServerSetting> ServerSettings { get; set; }
public DbSet<User> Users { get; set; }

public DbSet<ProductCategory> ProductCategories { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<PriceCategory> PriceCategories { get; set; }
public DbSet<Delivery> Deliveries { get; set; }
public DbSet<DeliveryLine> DeliveryLines { get; set; }
public DbSet<Stock> ProductStock { get; set; }
public DbSet<StockHistory> StockHistory { get; set; }

public DbSet<Client> Clients { get; set; }

private static void OnEntityTracked(object? sender, EntityTrackedEventArgs e)
{
if (e.FromQuery || e.Entry.State != EntityState.Added || e.Entry.Entity is not IEntityDate entity) return;
Expand All @@ -51,13 +60,13 @@ private static void OnEntityTracked(object? sender, EntityTrackedEventArgs e)
entity.CreatedUtc = DateTime.UtcNow;
}
}

private static void OnEntityStateChanged(object? sender, EntityStateChangedEventArgs e)
{
if (e.NewState != EntityState.Modified || e.Entry.Entity is not IEntityDate entity) return;
entity.LastModifiedUtc = DateTime.UtcNow;
}

private void OnSaveChanges()
{
foreach (var saveEntity in ChangeTracker.Entries()
Expand All @@ -82,5 +91,5 @@ public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, Cance

return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
}

}
60 changes: 56 additions & 4 deletions API/Data/Repositories/ProductRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,19 @@ public interface IProductRepository
Task<IList<ProductCategoryDto>> GetAllCategoriesDtos(bool onlyEnabled = false);
Task<IList<Product>> GetByCategory(ProductCategory category);
Task<int> GetHighestSortValue(ProductCategory category);
Task<PriceCategory?> GetPriceCategoryById(int id);
Task<PriceCategory?> GetPriceCategoryByName(string name);
Task<IList<PriceCategory>> GetAllPriceCategories();
Task<IList<PriceCategoryDto>> GetAllPriceCategoryDtos();
void Add(Product product);
void Add(ProductCategory category);
void Add(PriceCategory priceCategory);
void Update(Product product);
void Update(ProductCategory category);
void Update(PriceCategory priceCategory);
void Delete(Product product);
void Delete(ProductCategory category);
void Delete(PriceCategory priceCategory);
Task<int> Count();
}

Expand Down Expand Up @@ -110,9 +117,39 @@ public async Task<IList<Product>> GetByCategory(ProductCategory category)

public async Task<int> GetHighestSortValue(ProductCategory category)
{
return await ctx.Products
.Where(p => p.CategoryId == category.Id)
.MaxAsync(p => p.SortValue);
try
{
return await ctx.Products
.Where(p => p.CategoryId == category.Id)
.MaxAsync(p => p.SortValue);
}
catch (InvalidOperationException)
{
return 0;
}
}

public Task<PriceCategory?> GetPriceCategoryById(int id)
{
return ctx.PriceCategories.FirstOrDefaultAsync(p => p.Id == id);
}

public Task<PriceCategory?> GetPriceCategoryByName(string name)
{
var normalized = name.ToNormalized();
return ctx.PriceCategories.FirstOrDefaultAsync(p => p.NormalizedName == normalized);
}

public async Task<IList<PriceCategory>> GetAllPriceCategories()
{
return await ctx.PriceCategories.ToListAsync();
}

public async Task<IList<PriceCategoryDto>> GetAllPriceCategoryDtos()
{
return await ctx.PriceCategories
.ProjectTo<PriceCategoryDto>(mapper.ConfigurationProvider)
.ToListAsync();
}

public void Add(Product product)
Expand All @@ -125,6 +162,11 @@ public void Add(ProductCategory category)
ctx.ProductCategories.Add(category).State = EntityState.Added;
}

public void Add(PriceCategory priceCategory)
{
ctx.PriceCategories.Add(priceCategory).State = EntityState.Added;
}

public void Update(Product product)
{
ctx.Products.Update(product).State = EntityState.Modified;
Expand All @@ -135,6 +177,11 @@ public void Update(ProductCategory category)
ctx.ProductCategories.Update(category).State = EntityState.Modified;
}

public void Update(PriceCategory priceCategory)
{
ctx.PriceCategories.Update(priceCategory).State = EntityState.Modified;
}

public void Delete(Product product)
{
ctx.Products.Remove(product).State = EntityState.Deleted;
Expand All @@ -145,8 +192,13 @@ public void Delete(ProductCategory category)
ctx.ProductCategories.Remove(category).State = EntityState.Deleted;
}

public void Delete(PriceCategory priceCategory)
{
ctx.PriceCategories.Remove(priceCategory).State = EntityState.Deleted;
}

public async Task<int> Count()
{
return await ctx.Products.CountAsync();
}
}
}
9 changes: 6 additions & 3 deletions API/Entities/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ public class Client
public required string Name { get; set; }
public string NormalizedName { get; set; }
public string Address { get; set; }

public string CompanyNumber { get; set; }
public string InvoiceEmail { get; set; }

public string ContactName { get; set; }
public string ContactEmail { get; set; }
public string ContactNumber { get; set; }
}

public int? DefaultPriceCategoryId { get; set; }
public PriceCategory? DefaultPriceCategory { get; set; }
}
8 changes: 5 additions & 3 deletions API/Entities/Delivery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ public class Delivery: IEntityDate
{
public int Id { get; set; }
public DeliveryState State { get; set; }

public int UserId { get; set; }
public User From { get; set; }
public Client Recipient { get; set; }

public int PriceCategoryId { get; set; }
public PriceCategory PriceCategory { get; set; }

public string Message { get; set; }
public IList<SystemMessage> SystemMessages { get; set; }
public IList<DeliveryLine> Lines { get; set; }

public DateTime CreatedUtc { get; set; }
public DateTime LastModifiedUtc { get; set; }
}
}
8 changes: 8 additions & 0 deletions API/Entities/PriceCategory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace API.Entities;

public class PriceCategory
{
public int Id { get; set; }
public string Name { get; set; }
public string NormalizedName { get; set; }
}
9 changes: 5 additions & 4 deletions API/Entities/Product.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@ public class Product

public required string Name { get; set; }
public string NormalizedName { get; set; }

public string Description { get; set; }

public int CategoryId { get; set; }
public ProductCategory Category { get; set; }
public Dictionary<int, float> Prices { get; set; }
/// <summary>
/// This value is valid inside a category, not between
/// </summary>
public int SortValue { get; set; }
public ProductType Type { get; set; }

public bool IsTracked { get; set; }
public bool Enabled { get; set; }
}
}
13 changes: 7 additions & 6 deletions API/Helpers/AutoMapperProfiles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public AutoMapperProfiles()

CreateMap<Product, ProductDto>();
CreateMap<ProductCategory, ProductCategoryDto>();

CreateMap<Client, ClientDto>();
CreateMap<User, UserDto>();

Expand All @@ -25,13 +25,14 @@ public AutoMapperProfiles()
opt.MapFrom(s => s.Recipient.Id));
CreateMap<DeliveryLine, DeliveryLineDto>();
CreateMap<Stock, StockDto>()
.ForMember(d => d.Name,
opt =>
.ForMember(d => d.Name,
opt =>
opt.MapFrom(s => s.Product.Name))
.ForMember(d => d.Description,
opt =>
.ForMember(d => d.Description,
opt =>
opt.MapFrom(s => s.Product.Description));
CreateMap<StockHistory, StockHistoryDto>();
CreateMap<PriceCategory, PriceCategoryDto>();

}
}
}
Loading