item mng with sync

This commit is contained in:
Kalana B. Thilakarathna
2026-08-19 12:49:04 +05:30
parent 45f374133f
commit 397af8fd59
42 changed files with 5191 additions and 16 deletions
+6 -1
View File
@@ -10,11 +10,16 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
public DbSet<Vehicle> Vehicles { get; set; }
public DbSet<Store> Stores { get; set; }
public DbSet<Warehouse> Warehouses { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<SubCategory> SubCategories { get; set; }
public DbSet<Brand> Brands { get; set; }
public DbSet<Uom> Uoms { get; set; }
public DbSet<UomConversion> UomConversions { get; set; }
public DbSet<Item> Items { get; set; }
public DbSet<SaleRoute> SaleRoutes { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<OrderItem> OrderItems { get; set; }
public DbSet<DeliveryRun> DeliveryRuns { get; set; }
public DbSet<Item> Items { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<Permission> Permissions { get; set; }
@@ -52,6 +52,16 @@ public static class RoleAndPermissionSeed
("users.deactivate", "Deactivate users"),
("users.assign_role", "Assign a role to a user"),
("users.assign_scope", "Assign region/route/store scope to a user"),
("items.view", "View items, brands and UOMs"),
("items.create", "Create items, brands and UOMs"),
("items.update", "Update items, brands and UOMs"),
("items.delete", "Delete items, brands and UOMs"),
("categories.view", "View categories"),
("categories.create", "Create categories and subcategories"),
("categories.update", "Update categories and subcategories"),
("categories.delete", "Delete categories and subcategories"),
];
private static readonly Dictionary<string, Guid> PermissionIds = PermissionDefs
@@ -83,10 +93,15 @@ public static class RoleAndPermissionSeed
})
);
var salesRepPerms = new[] { "orders.view", "orders.create", "orders.update", "stores.view", "routes.view" };
var itemViewPerms = new[] { "items.view", "categories.view" };
var itemManagePerms = new[] { "items.view", "items.create", "items.update", "categories.view" };
var itemPerms = new[] { "items.view", "items.create", "items.update", "items.delete", "categories.view", "categories.create", "categories.update", "categories.delete" };
var salesRepPerms = new[] { "orders.view", "orders.create", "orders.update", "stores.view", "routes.view" }.Concat(itemViewPerms).ToArray();
var supervisorPerms = salesRepPerms.Concat(["orders.delete", "stores.create", "stores.update"]).ToArray();
var areaManagerPerms = supervisorPerms.Concat(["vehicles.view", "warehouses.view"]).ToArray();
var areaSupervisorPerms = areaManagerPerms.Concat(["routes.create", "routes.update"]).ToArray();
var areaManagerPerms = supervisorPerms.Concat(["vehicles.view", "warehouses.view"]).Concat(itemManagePerms).Distinct().ToArray();
var areaSupervisorPerms = areaManagerPerms.Concat(["routes.create", "routes.update"]).Concat(itemPerms).Distinct().ToArray();
var regionalManagerPerms = new[]
{
"orders.view", "orders.create", "orders.update", "orders.delete",
@@ -94,7 +109,9 @@ public static class RoleAndPermissionSeed
"routes.view", "routes.create", "routes.update", "routes.delete",
"vehicles.view", "vehicles.create", "vehicles.update", "vehicles.delete",
"warehouses.view", "warehouses.create", "warehouses.update", "warehouses.delete",
"regions.view", "users.view"
"regions.view", "users.view",
"items.view", "items.create", "items.update", "items.delete",
"categories.view", "categories.create", "categories.update", "categories.delete"
};
var regionalAgencyPerms = PermissionDefs.Select(p => p.Code).ToArray();
@@ -0,0 +1,47 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Distribution.Features.Brand;
[ApiController]
[Route("api/brands")]
[Authorize]
public class BrandController(BrandService service) : ControllerBase
{
[HttpGet]
[Authorize(Policy = "items.view")]
public async Task<IActionResult> GetAll() =>
Ok(await service.GetAllAsync());
[HttpGet("{id:guid}")]
[Authorize(Policy = "items.view")]
public async Task<IActionResult> GetById(Guid id)
{
var result = await service.GetByIdAsync(id);
return result is null ? NotFound() : Ok(result);
}
[HttpPost]
[Authorize(Policy = "items.create")]
public async Task<IActionResult> Create(CreateBrandRequest request, CancellationToken ct)
{
var result = await service.CreateAsync(request, ct);
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
}
[HttpPut("{id:guid}")]
[Authorize(Policy = "items.update")]
public async Task<IActionResult> Update(Guid id, UpdateBrandRequest request, CancellationToken ct)
{
var result = await service.UpdateAsync(id, request, ct);
return result is null ? NotFound() : Ok(result);
}
[HttpDelete("{id:guid}")]
[Authorize(Policy = "items.delete")]
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
{
var deleted = await service.DeleteAsync(id, ct);
return deleted ? NoContent() : NotFound();
}
}
+5
View File
@@ -0,0 +1,5 @@
namespace Distribution.Features.Brand;
public record CreateBrandRequest(string Name);
public record UpdateBrandRequest(string Name);
public record BrandResponse(Guid Id, string Name, bool IsActive, DateTime CreatedAt);
@@ -0,0 +1,31 @@
using Distribution.Data;
using Microsoft.EntityFrameworkCore;
namespace Distribution.Features.Brand;
public class BrandRepository(AppDBContext context) : IBrandRepository
{
public async Task<Models.Brand?> GetByIdAsync(Guid id) =>
await context.Brands.FindAsync(id);
public async Task<List<Models.Brand>> GetAllAsync() =>
await context.Brands.ToListAsync();
public async Task<Models.Brand?> GetByExternalErpIdAsync(int erpId) =>
await context.Brands.FirstOrDefaultAsync(b => b.ExternalErpId == erpId);
public async Task<bool> HasActiveItemsAsync(Guid id) =>
await context.Items.AnyAsync(i => i.FK_BrandId == id && i.IsActive);
public async Task AddAsync(Models.Brand entity) =>
await context.Brands.AddAsync(entity);
public void Update(Models.Brand entity) =>
context.Brands.Update(entity);
public void Remove(Models.Brand entity)
{
entity.IsActive = false;
context.Brands.Update(entity);
}
}
@@ -0,0 +1,52 @@
using Distribution.Infra.UoW;
namespace Distribution.Features.Brand;
public class BrandService(IBrandRepository repo, IUnitOfWork uow)
{
public async Task<List<BrandResponse>> GetAllAsync() =>
(await repo.GetAllAsync())
.Where(b => b.IsActive)
.Select(b => new BrandResponse(b.Id, b.Name, b.IsActive, b.CreatedAt))
.ToList();
public async Task<BrandResponse?> GetByIdAsync(Guid id)
{
var brand = await repo.GetByIdAsync(id);
if (brand is null || !brand.IsActive) return null;
return new BrandResponse(brand.Id, brand.Name, brand.IsActive, brand.CreatedAt);
}
public async Task<BrandResponse> CreateAsync(CreateBrandRequest request, CancellationToken ct = default)
{
var brand = new Models.Brand { Name = request.Name };
await uow.BeginAsync(ct);
try { await repo.AddAsync(brand); await uow.CommitAsync(ct); }
catch { await uow.RollbackAsync(ct); throw; }
return new BrandResponse(brand.Id, brand.Name, brand.IsActive, brand.CreatedAt);
}
public async Task<BrandResponse?> UpdateAsync(Guid id, UpdateBrandRequest request, CancellationToken ct = default)
{
var brand = await repo.GetByIdAsync(id);
if (brand is null || !brand.IsActive) return null;
if (brand.ExternalErpId.HasValue) return null;
brand.Name = request.Name;
await uow.BeginAsync(ct);
try { repo.Update(brand); await uow.CommitAsync(ct); }
catch { await uow.RollbackAsync(ct); throw; }
return new BrandResponse(brand.Id, brand.Name, brand.IsActive, brand.CreatedAt);
}
public async Task<bool> DeleteAsync(Guid id, CancellationToken ct = default)
{
var brand = await repo.GetByIdAsync(id);
if (brand is null || !brand.IsActive) return false;
if (brand.ExternalErpId.HasValue) return false;
if (await repo.HasActiveItemsAsync(id)) return false;
await uow.BeginAsync(ct);
try { repo.Remove(brand); await uow.CommitAsync(ct); }
catch { await uow.RollbackAsync(ct); throw; }
return true;
}
}
@@ -0,0 +1,12 @@
namespace Distribution.Features.Brand;
public interface IBrandRepository
{
Task<Models.Brand?> GetByIdAsync(Guid id);
Task<List<Models.Brand>> GetAllAsync();
Task<Models.Brand?> GetByExternalErpIdAsync(int erpId);
Task<bool> HasActiveItemsAsync(Guid id);
Task AddAsync(Models.Brand entity);
void Update(Models.Brand entity);
void Remove(Models.Brand entity);
}
@@ -0,0 +1,70 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Distribution.Features.Category;
[ApiController]
[Authorize]
public class CategoryController(CategoryService service) : ControllerBase
{
[HttpGet("api/categories")]
[Authorize(Policy = "categories.view")]
public async Task<IActionResult> GetAll() =>
Ok(await service.GetAllAsync());
[HttpGet("api/categories/{id:guid}")]
[Authorize(Policy = "categories.view")]
public async Task<IActionResult> GetById(Guid id)
{
var result = await service.GetByIdAsync(id);
return result is null ? NotFound() : Ok(result);
}
[HttpPost("api/categories")]
[Authorize(Policy = "categories.create")]
public async Task<IActionResult> Create(CreateCategoryRequest request, CancellationToken ct)
{
var result = await service.CreateAsync(request, ct);
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
}
[HttpPut("api/categories/{id:guid}")]
[Authorize(Policy = "categories.update")]
public async Task<IActionResult> Update(Guid id, UpdateCategoryRequest request, CancellationToken ct)
{
var result = await service.UpdateAsync(id, request, ct);
return result is null ? NotFound() : Ok(result);
}
[HttpDelete("api/categories/{id:guid}")]
[Authorize(Policy = "categories.delete")]
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
{
var deleted = await service.DeleteAsync(id, ct);
return deleted ? NoContent() : NotFound();
}
[HttpPost("api/categories/{categoryId:guid}/subcategories")]
[Authorize(Policy = "categories.create")]
public async Task<IActionResult> CreateSubCategory(Guid categoryId, CreateSubCategoryRequest request, CancellationToken ct)
{
var result = await service.CreateSubCategoryAsync(categoryId, request, ct);
return result is null ? NotFound() : Ok(result);
}
[HttpPut("api/subcategories/{id:guid}")]
[Authorize(Policy = "categories.update")]
public async Task<IActionResult> UpdateSubCategory(Guid id, UpdateSubCategoryRequest request, CancellationToken ct)
{
var result = await service.UpdateSubCategoryAsync(id, request, ct);
return result is null ? NotFound() : Ok(result);
}
[HttpDelete("api/subcategories/{id:guid}")]
[Authorize(Policy = "categories.delete")]
public async Task<IActionResult> DeleteSubCategory(Guid id, CancellationToken ct)
{
var deleted = await service.DeleteSubCategoryAsync(id, ct);
return deleted ? NoContent() : NotFound();
}
}
@@ -0,0 +1,10 @@
namespace Distribution.Features.Category;
public record CreateCategoryRequest(string Name);
public record UpdateCategoryRequest(string Name);
public record CreateSubCategoryRequest(string Name);
public record UpdateSubCategoryRequest(string Name);
public record SubCategoryResponse(Guid Id, string Name, bool IsActive, DateTime CreatedAt);
public record CategorySummaryResponse(Guid Id, string Name, bool IsActive, DateTime CreatedAt);
public record CategoryResponse(Guid Id, string Name, bool IsActive, DateTime CreatedAt, List<SubCategoryResponse> SubCategories);
@@ -0,0 +1,34 @@
using Distribution.Data;
using Microsoft.EntityFrameworkCore;
namespace Distribution.Features.Category;
public class CategoryRepository(AppDBContext context) : ICategoryRepository
{
public async Task<Models.Category?> GetByIdAsync(Guid id) =>
await context.Categories.FindAsync(id);
public async Task<List<Models.Category>> GetAllAsync() =>
await context.Categories.ToListAsync();
public async Task<Models.Category?> GetByExternalErpIdAsync(int erpId) =>
await context.Categories.FirstOrDefaultAsync(c => c.ExternalErpId == erpId);
public async Task<bool> HasActiveSubCategoriesAsync(Guid id) =>
await context.SubCategories.AnyAsync(s => s.FK_CategoryId == id && s.IsActive);
public async Task<bool> HasActiveItemsAsync(Guid id) =>
await context.Items.AnyAsync(i => i.FK_CategoryId == id && i.IsActive);
public async Task AddAsync(Models.Category entity) =>
await context.Categories.AddAsync(entity);
public void Update(Models.Category entity) =>
context.Categories.Update(entity);
public void Remove(Models.Category entity)
{
entity.IsActive = false;
context.Categories.Update(entity);
}
}
@@ -0,0 +1,92 @@
using Distribution.Infra.UoW;
namespace Distribution.Features.Category;
public class CategoryService(ICategoryRepository categoryRepo, ISubCategoryRepository subCategoryRepo, IUnitOfWork uow)
{
public async Task<List<CategorySummaryResponse>> GetAllAsync() =>
(await categoryRepo.GetAllAsync())
.Where(c => c.IsActive)
.Select(c => new CategorySummaryResponse(c.Id, c.Name, c.IsActive, c.CreatedAt))
.ToList();
public async Task<CategoryResponse?> GetByIdAsync(Guid id)
{
var category = await categoryRepo.GetByIdAsync(id);
if (category is null || !category.IsActive) return null;
var subs = (await subCategoryRepo.GetByCategoryIdAsync(id))
.Where(s => s.IsActive)
.Select(s => new SubCategoryResponse(s.Id, s.Name, s.IsActive, s.CreatedAt))
.ToList();
return new CategoryResponse(category.Id, category.Name, category.IsActive, category.CreatedAt, subs);
}
public async Task<CategorySummaryResponse> CreateAsync(CreateCategoryRequest request, CancellationToken ct = default)
{
var category = new Models.Category { Name = request.Name };
await uow.BeginAsync(ct);
try { await categoryRepo.AddAsync(category); await uow.CommitAsync(ct); }
catch { await uow.RollbackAsync(ct); throw; }
return new CategorySummaryResponse(category.Id, category.Name, category.IsActive, category.CreatedAt);
}
public async Task<CategorySummaryResponse?> UpdateAsync(Guid id, UpdateCategoryRequest request, CancellationToken ct = default)
{
var category = await categoryRepo.GetByIdAsync(id);
if (category is null || !category.IsActive) return null;
if (category.ExternalErpId.HasValue) return null;
category.Name = request.Name;
await uow.BeginAsync(ct);
try { categoryRepo.Update(category); await uow.CommitAsync(ct); }
catch { await uow.RollbackAsync(ct); throw; }
return new CategorySummaryResponse(category.Id, category.Name, category.IsActive, category.CreatedAt);
}
public async Task<bool> DeleteAsync(Guid id, CancellationToken ct = default)
{
var category = await categoryRepo.GetByIdAsync(id);
if (category is null || !category.IsActive) return false;
if (category.ExternalErpId.HasValue) return false;
if (await categoryRepo.HasActiveSubCategoriesAsync(id)) return false;
if (await categoryRepo.HasActiveItemsAsync(id)) return false;
await uow.BeginAsync(ct);
try { categoryRepo.Remove(category); await uow.CommitAsync(ct); }
catch { await uow.RollbackAsync(ct); throw; }
return true;
}
public async Task<SubCategoryResponse?> CreateSubCategoryAsync(Guid categoryId, CreateSubCategoryRequest request, CancellationToken ct = default)
{
var category = await categoryRepo.GetByIdAsync(categoryId);
if (category is null || !category.IsActive) return null;
var sub = new Models.SubCategory { Name = request.Name, FK_CategoryId = categoryId };
await uow.BeginAsync(ct);
try { await subCategoryRepo.AddAsync(sub); await uow.CommitAsync(ct); }
catch { await uow.RollbackAsync(ct); throw; }
return new SubCategoryResponse(sub.Id, sub.Name, sub.IsActive, sub.CreatedAt);
}
public async Task<SubCategoryResponse?> UpdateSubCategoryAsync(Guid id, UpdateSubCategoryRequest request, CancellationToken ct = default)
{
var sub = await subCategoryRepo.GetByIdAsync(id);
if (sub is null || !sub.IsActive) return null;
if (sub.ExternalErpId.HasValue) return null;
sub.Name = request.Name;
await uow.BeginAsync(ct);
try { subCategoryRepo.Update(sub); await uow.CommitAsync(ct); }
catch { await uow.RollbackAsync(ct); throw; }
return new SubCategoryResponse(sub.Id, sub.Name, sub.IsActive, sub.CreatedAt);
}
public async Task<bool> DeleteSubCategoryAsync(Guid id, CancellationToken ct = default)
{
var sub = await subCategoryRepo.GetByIdAsync(id);
if (sub is null || !sub.IsActive) return false;
if (sub.ExternalErpId.HasValue) return false;
if (await subCategoryRepo.HasActiveItemsAsync(id)) return false;
await uow.BeginAsync(ct);
try { subCategoryRepo.Remove(sub); await uow.CommitAsync(ct); }
catch { await uow.RollbackAsync(ct); throw; }
return true;
}
}
@@ -0,0 +1,13 @@
namespace Distribution.Features.Category;
public interface ICategoryRepository
{
Task<Models.Category?> GetByIdAsync(Guid id);
Task<List<Models.Category>> GetAllAsync();
Task<Models.Category?> GetByExternalErpIdAsync(int erpId);
Task<bool> HasActiveSubCategoriesAsync(Guid id);
Task<bool> HasActiveItemsAsync(Guid id);
Task AddAsync(Models.Category entity);
void Update(Models.Category entity);
void Remove(Models.Category entity);
}
@@ -0,0 +1,12 @@
namespace Distribution.Features.Category;
public interface ISubCategoryRepository
{
Task<Models.SubCategory?> GetByIdAsync(Guid id);
Task<List<Models.SubCategory>> GetByCategoryIdAsync(Guid categoryId);
Task<Models.SubCategory?> GetByExternalErpIdAsync(int erpId);
Task<bool> HasActiveItemsAsync(Guid id);
Task AddAsync(Models.SubCategory entity);
void Update(Models.SubCategory entity);
void Remove(Models.SubCategory entity);
}
@@ -0,0 +1,31 @@
using Distribution.Data;
using Microsoft.EntityFrameworkCore;
namespace Distribution.Features.Category;
public class SubCategoryRepository(AppDBContext context) : ISubCategoryRepository
{
public async Task<Models.SubCategory?> GetByIdAsync(Guid id) =>
await context.SubCategories.FindAsync(id);
public async Task<List<Models.SubCategory>> GetByCategoryIdAsync(Guid categoryId) =>
await context.SubCategories.Where(s => s.FK_CategoryId == categoryId).ToListAsync();
public async Task<Models.SubCategory?> GetByExternalErpIdAsync(int erpId) =>
await context.SubCategories.FirstOrDefaultAsync(s => s.ExternalErpId == erpId);
public async Task<bool> HasActiveItemsAsync(Guid id) =>
await context.Items.AnyAsync(i => i.FK_SubCategoryId == id && i.IsActive);
public async Task AddAsync(Models.SubCategory entity) =>
await context.SubCategories.AddAsync(entity);
public void Update(Models.SubCategory entity) =>
context.SubCategories.Update(entity);
public void Remove(Models.SubCategory entity)
{
entity.IsActive = false;
context.SubCategories.Update(entity);
}
}
@@ -0,0 +1,13 @@
namespace Distribution.Features.Item;
public interface IItemRepository
{
Task<Models.Item?> GetByIdAsync(Guid id);
Task<List<Models.Item>> GetAllAsync(Guid? categoryId = null, Guid? subCategoryId = null, Guid? brandId = null);
Task<bool> SkuExistsAsync(string sku, Guid? excludeId = null);
Task<Models.Item?> GetByExternalErpItemIdAsync(int erpId);
Task AddAsync(Models.Item entity);
void Update(Models.Item entity);
void Remove(Models.Item entity);
Task ReplaceConversionsAsync(Guid itemId, List<Models.UomConversion> conversions);
}
@@ -0,0 +1,65 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Distribution.Features.Item;
[ApiController]
[Route("api/items")]
[Authorize]
public class ItemController(ItemService service) : ControllerBase
{
[HttpGet]
[Authorize(Policy = "items.view")]
public async Task<IActionResult> GetAll(
[FromQuery] Guid? categoryId,
[FromQuery] Guid? subCategoryId,
[FromQuery] Guid? brandId) =>
Ok(await service.GetAllAsync(categoryId, subCategoryId, brandId));
[HttpGet("{id:guid}")]
[Authorize(Policy = "items.view")]
public async Task<IActionResult> GetById(Guid id)
{
var result = await service.GetByIdAsync(id);
return result is null ? NotFound() : Ok(result);
}
[HttpPost]
[Authorize(Policy = "items.create")]
public async Task<IActionResult> Create(CreateItemRequest request, CancellationToken ct)
{
try
{
var result = await service.CreateAsync(request, ct);
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
}
catch (InvalidOperationException ex)
{
return Conflict(new { error = ex.Message });
}
}
[HttpPut("{id:guid}")]
[Authorize(Policy = "items.update")]
public async Task<IActionResult> Update(Guid id, UpdateItemRequest request, CancellationToken ct)
{
var result = await service.UpdateAsync(id, request, ct);
return result is null ? NotFound() : Ok(result);
}
[HttpDelete("{id:guid}")]
[Authorize(Policy = "items.delete")]
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
{
var deleted = await service.DeleteAsync(id, ct);
return deleted ? NoContent() : NotFound();
}
[HttpPut("{id:guid}/uom-conversions")]
[Authorize(Policy = "items.update")]
public async Task<IActionResult> ReplaceConversions(Guid id, ReplaceConversionsRequest request, CancellationToken ct)
{
var result = await service.ReplaceConversionsAsync(id, request, ct);
return result is null ? NotFound() : Ok(result);
}
}
+63
View File
@@ -0,0 +1,63 @@
namespace Distribution.Features.Item;
public record UomConversionRequest(Guid FromUomId, Guid ToUomId, decimal Factor);
public record CreateItemRequest(
string Sku,
string Name,
string? Description,
Guid CategoryId,
Guid? SubCategoryId,
Guid? BrandId,
Guid BaseUomId,
decimal? SalePrice,
List<UomConversionRequest>? Conversions);
public record UpdateItemRequest(
string Name,
string? Description,
Guid CategoryId,
Guid? SubCategoryId,
Guid? BrandId,
Guid BaseUomId,
decimal? SalePrice);
public record ReplaceConversionsRequest(List<UomConversionRequest> Conversions);
public record UomConversionResponse(Guid Id, Guid FromUomId, string FromUomName, Guid ToUomId, string ToUomName, decimal Factor);
public record ItemSummaryResponse(
Guid Id,
string Sku,
string Name,
Guid CategoryId,
string CategoryName,
Guid? SubCategoryId,
string? SubCategoryName,
Guid? BrandId,
string? BrandName,
Guid BaseUomId,
string BaseUomName,
decimal? SalePrice,
Models.SyncSource SyncSource,
bool IsActive,
DateTime CreatedAt);
public record ItemDetailResponse(
Guid Id,
string Sku,
string Name,
string? Description,
Guid CategoryId,
string CategoryName,
Guid? SubCategoryId,
string? SubCategoryName,
Guid? BrandId,
string? BrandName,
Guid BaseUomId,
string BaseUomName,
decimal? SalePrice,
Models.SyncSource SyncSource,
bool IsActive,
DateTime CreatedAt,
List<UomConversionResponse> Conversions);
@@ -0,0 +1,64 @@
using Distribution.Data;
using Microsoft.EntityFrameworkCore;
namespace Distribution.Features.Item;
public class ItemRepository(AppDBContext context) : IItemRepository
{
public async Task<Models.Item?> GetByIdAsync(Guid id) =>
await context.Items
.Include(i => i.Category)
.Include(i => i.SubCategory)
.Include(i => i.Brand)
.Include(i => i.BaseUom)
.Include(i => i.Conversions).ThenInclude(c => c.FromUom)
.Include(i => i.Conversions).ThenInclude(c => c.ToUom)
.FirstOrDefaultAsync(i => i.Id == id);
public async Task<List<Models.Item>> GetAllAsync(Guid? categoryId = null, Guid? subCategoryId = null, Guid? brandId = null)
{
var query = context.Items
.Include(i => i.Category)
.Include(i => i.SubCategory)
.Include(i => i.Brand)
.Include(i => i.BaseUom)
.Where(i => i.IsActive);
if (categoryId.HasValue) query = query.Where(i => i.FK_CategoryId == categoryId);
if (subCategoryId.HasValue) query = query.Where(i => i.FK_SubCategoryId == subCategoryId);
if (brandId.HasValue) query = query.Where(i => i.FK_BrandId == brandId);
return await query.ToListAsync();
}
public async Task<bool> SkuExistsAsync(string sku, Guid? excludeId = null)
{
var query = context.Items.Where(i => i.Sku == sku);
if (excludeId.HasValue) query = query.Where(i => i.Id != excludeId);
return await query.AnyAsync();
}
public async Task<Models.Item?> GetByExternalErpItemIdAsync(int erpId) =>
await context.Items
.Include(i => i.Conversions)
.FirstOrDefaultAsync(i => i.ExternalErpItemId == erpId);
public async Task AddAsync(Models.Item entity) =>
await context.Items.AddAsync(entity);
public void Update(Models.Item entity) =>
context.Items.Update(entity);
public void Remove(Models.Item entity)
{
entity.IsActive = false;
context.Items.Update(entity);
}
public async Task ReplaceConversionsAsync(Guid itemId, List<Models.UomConversion> conversions)
{
var existing = await context.UomConversions.Where(c => c.FK_ItemId == itemId).ToListAsync();
context.UomConversions.RemoveRange(existing);
await context.UomConversions.AddRangeAsync(conversions);
}
}
+126
View File
@@ -0,0 +1,126 @@
using Distribution.Infra.UoW;
namespace Distribution.Features.Item;
public class ItemService(IItemRepository repo, IUnitOfWork uow)
{
private static UomConversionResponse MapConversion(Models.UomConversion c) =>
new(c.Id, c.FK_FromUomId, c.FromUom.Name, c.FK_ToUomId, c.ToUom.Name, c.Factor);
private static ItemSummaryResponse MapSummary(Models.Item i) =>
new(i.Id, i.Sku, i.Name,
i.FK_CategoryId, i.Category.Name,
i.FK_SubCategoryId, i.SubCategory?.Name,
i.FK_BrandId, i.Brand?.Name,
i.FK_BaseUomId, i.BaseUom.Name,
i.SalePrice, i.SyncSource, i.IsActive, i.CreatedAt);
private static ItemDetailResponse MapDetail(Models.Item i) =>
new(i.Id, i.Sku, i.Name, i.Description,
i.FK_CategoryId, i.Category.Name,
i.FK_SubCategoryId, i.SubCategory?.Name,
i.FK_BrandId, i.Brand?.Name,
i.FK_BaseUomId, i.BaseUom.Name,
i.SalePrice, i.SyncSource, i.IsActive, i.CreatedAt,
i.Conversions.Select(MapConversion).ToList());
public async Task<List<ItemSummaryResponse>> GetAllAsync(Guid? categoryId = null, Guid? subCategoryId = null, Guid? brandId = null) =>
(await repo.GetAllAsync(categoryId, subCategoryId, brandId))
.Select(MapSummary)
.ToList();
public async Task<ItemDetailResponse?> GetByIdAsync(Guid id)
{
var item = await repo.GetByIdAsync(id);
if (item is null || !item.IsActive) return null;
return MapDetail(item);
}
public async Task<ItemDetailResponse> CreateAsync(CreateItemRequest request, CancellationToken ct = default)
{
if (await repo.SkuExistsAsync(request.Sku))
throw new InvalidOperationException($"SKU '{request.Sku}' already exists.");
var item = new Models.Item
{
Sku = request.Sku,
Name = request.Name,
Description = request.Description,
FK_CategoryId = request.CategoryId,
FK_SubCategoryId = request.SubCategoryId,
FK_BrandId = request.BrandId,
FK_BaseUomId = request.BaseUomId,
SalePrice = request.SalePrice,
SyncSource = Models.SyncSource.Manual
};
var conversions = (request.Conversions ?? []).Select(c => new Models.UomConversion
{
FK_ItemId = item.Id,
FK_FromUomId = c.FromUomId,
FK_ToUomId = c.ToUomId,
Factor = c.Factor
}).ToList();
item.Conversions = conversions;
await uow.BeginAsync(ct);
try { await repo.AddAsync(item); await uow.CommitAsync(ct); }
catch { await uow.RollbackAsync(ct); throw; }
return (await GetByIdAsync(item.Id))!;
}
public async Task<ItemDetailResponse?> UpdateAsync(Guid id, UpdateItemRequest request, CancellationToken ct = default)
{
var item = await repo.GetByIdAsync(id);
if (item is null || !item.IsActive) return null;
if (item.SyncSource == Models.SyncSource.Erp) return null;
item.Name = request.Name;
item.Description = request.Description;
item.FK_CategoryId = request.CategoryId;
item.FK_SubCategoryId = request.SubCategoryId;
item.FK_BrandId = request.BrandId;
item.FK_BaseUomId = request.BaseUomId;
item.SalePrice = request.SalePrice;
await uow.BeginAsync(ct);
try { repo.Update(item); await uow.CommitAsync(ct); }
catch { await uow.RollbackAsync(ct); throw; }
return (await GetByIdAsync(id))!;
}
public async Task<bool> DeleteAsync(Guid id, CancellationToken ct = default)
{
var item = await repo.GetByIdAsync(id);
if (item is null || !item.IsActive) return false;
if (item.SyncSource == Models.SyncSource.Erp) return false;
await uow.BeginAsync(ct);
try { repo.Remove(item); await uow.CommitAsync(ct); }
catch { await uow.RollbackAsync(ct); throw; }
return true;
}
public async Task<List<UomConversionResponse>?> ReplaceConversionsAsync(Guid id, ReplaceConversionsRequest request, CancellationToken ct = default)
{
var item = await repo.GetByIdAsync(id);
if (item is null || !item.IsActive) return null;
if (item.SyncSource == Models.SyncSource.Erp) return null;
var conversions = request.Conversions.Select(c => new Models.UomConversion
{
FK_ItemId = id,
FK_FromUomId = c.FromUomId,
FK_ToUomId = c.ToUomId,
Factor = c.Factor
}).ToList();
await uow.BeginAsync(ct);
try { await repo.ReplaceConversionsAsync(id, conversions); await uow.CommitAsync(ct); }
catch { await uow.RollbackAsync(ct); throw; }
return (await GetByIdAsync(id))!.Conversions;
}
}
@@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Distribution.Features.Sync;
[ApiController]
[Route("api/sync")]
[Authorize(Policy = "items.create")]
public class SyncController(SyncService service) : ControllerBase
{
[HttpPost("uoms")]
public async Task<IActionResult> SyncUoms(SyncUomsPayload payload, CancellationToken ct) =>
Ok(await service.SyncUomsAsync(payload, ct));
[HttpPost("categories")]
public async Task<IActionResult> SyncCategories(SyncCategoriesPayload payload, CancellationToken ct) =>
Ok(await service.SyncCategoriesAsync(payload, ct));
[HttpPost("brands")]
public async Task<IActionResult> SyncBrands(SyncBrandsPayload payload, CancellationToken ct) =>
Ok(await service.SyncBrandsAsync(payload, ct));
[HttpPost("items")]
public async Task<IActionResult> SyncItems(SyncItemsPayload payload, CancellationToken ct) =>
Ok(await service.SyncItemsAsync(payload, ct));
}
+26
View File
@@ -0,0 +1,26 @@
namespace Distribution.Features.Sync;
public record SyncUomRequest(int ErpId, string Name);
public record SyncUomsPayload(List<SyncUomRequest> Items);
public record SyncSubCategoryRequest(int ErpId, string Name, int ParentErpId);
public record SyncCategoryRequest(int ErpId, string Name, List<SyncSubCategoryRequest> SubCategories);
public record SyncCategoriesPayload(List<SyncCategoryRequest> Items);
public record SyncBrandRequest(int ErpId, string Name);
public record SyncBrandsPayload(List<SyncBrandRequest> Items);
public record SyncItemRequest(
int ErpId,
string Sku,
string Name,
string? Description,
int CategoryErpId,
int? SubCategoryErpId,
int? BrandErpId,
int BaseUomErpId,
decimal? SalePrice,
bool IsActive);
public record SyncItemsPayload(List<SyncItemRequest> Items);
public record SyncResult(int Inserted, int Updated, int Skipped);
+186
View File
@@ -0,0 +1,186 @@
using Distribution.Features.Brand;
using Distribution.Features.Category;
using Distribution.Features.Uom;
using Distribution.Features.Item;
using Distribution.Infra.UoW;
namespace Distribution.Features.Sync;
public class SyncService(
ICategoryRepository categoryRepo,
ISubCategoryRepository subCategoryRepo,
IBrandRepository brandRepo,
IUomRepository uomRepo,
IItemRepository itemRepo,
IUnitOfWork uow)
{
public async Task<SyncResult> SyncUomsAsync(SyncUomsPayload payload, CancellationToken ct = default)
{
int inserted = 0, updated = 0;
await uow.BeginAsync(ct);
try
{
foreach (var u in payload.Items)
{
var existing = await uomRepo.GetByExternalErpIdAsync(u.ErpId);
if (existing is null)
{
await uomRepo.AddAsync(new Models.Uom { Name = u.Name, ExternalErpId = u.ErpId });
inserted++;
}
else
{
existing.Name = u.Name;
uomRepo.Update(existing);
updated++;
}
}
await uow.CommitAsync(ct);
}
catch { await uow.RollbackAsync(ct); throw; }
return new SyncResult(inserted, updated, 0);
}
public async Task<SyncResult> SyncCategoriesAsync(SyncCategoriesPayload payload, CancellationToken ct = default)
{
int inserted = 0, updated = 0;
await uow.BeginAsync(ct);
try
{
foreach (var c in payload.Items)
{
var existingCat = await categoryRepo.GetByExternalErpIdAsync(c.ErpId);
Guid categoryId;
if (existingCat is null)
{
var newCat = new Models.Category { Name = c.Name, ExternalErpId = c.ErpId };
await categoryRepo.AddAsync(newCat);
categoryId = newCat.Id;
inserted++;
}
else
{
existingCat.Name = c.Name;
categoryRepo.Update(existingCat);
categoryId = existingCat.Id;
updated++;
}
foreach (var s in c.SubCategories)
{
var existingSub = await subCategoryRepo.GetByExternalErpIdAsync(s.ErpId);
if (existingSub is null)
{
await subCategoryRepo.AddAsync(new Models.SubCategory
{
Name = s.Name,
FK_CategoryId = categoryId,
ExternalErpId = s.ErpId
});
inserted++;
}
else
{
existingSub.Name = s.Name;
existingSub.FK_CategoryId = categoryId;
subCategoryRepo.Update(existingSub);
updated++;
}
}
}
await uow.CommitAsync(ct);
}
catch { await uow.RollbackAsync(ct); throw; }
return new SyncResult(inserted, updated, 0);
}
public async Task<SyncResult> SyncBrandsAsync(SyncBrandsPayload payload, CancellationToken ct = default)
{
int inserted = 0, updated = 0;
await uow.BeginAsync(ct);
try
{
foreach (var b in payload.Items)
{
var existing = await brandRepo.GetByExternalErpIdAsync(b.ErpId);
if (existing is null)
{
await brandRepo.AddAsync(new Models.Brand { Name = b.Name, ExternalErpId = b.ErpId });
inserted++;
}
else
{
existing.Name = b.Name;
brandRepo.Update(existing);
updated++;
}
}
await uow.CommitAsync(ct);
}
catch { await uow.RollbackAsync(ct); throw; }
return new SyncResult(inserted, updated, 0);
}
public async Task<SyncResult> SyncItemsAsync(SyncItemsPayload payload, CancellationToken ct = default)
{
int inserted = 0, updated = 0, skipped = 0;
await uow.BeginAsync(ct);
try
{
foreach (var i in payload.Items)
{
var category = await categoryRepo.GetByExternalErpIdAsync(i.CategoryErpId);
var baseUom = await uomRepo.GetByExternalErpIdAsync(i.BaseUomErpId);
if (category is null || baseUom is null) { skipped++; continue; }
Models.SubCategory? subCategory = null;
if (i.SubCategoryErpId.HasValue)
subCategory = await subCategoryRepo.GetByExternalErpIdAsync(i.SubCategoryErpId.Value);
Models.Brand? brand = null;
if (i.BrandErpId.HasValue)
brand = await brandRepo.GetByExternalErpIdAsync(i.BrandErpId.Value);
var existing = await itemRepo.GetByExternalErpItemIdAsync(i.ErpId);
if (existing is null)
{
await itemRepo.AddAsync(new Models.Item
{
Sku = i.Sku,
Name = i.Name,
Description = i.Description,
FK_CategoryId = category.Id,
FK_SubCategoryId = subCategory?.Id,
FK_BrandId = brand?.Id,
FK_BaseUomId = baseUom.Id,
SalePrice = i.SalePrice,
IsActive = i.IsActive,
SyncSource = Models.SyncSource.Erp,
ExternalErpItemId = i.ErpId,
LastSyncedAt = DateTime.UtcNow
});
inserted++;
}
else
{
existing.Sku = i.Sku;
existing.Name = i.Name;
existing.Description = i.Description;
existing.FK_CategoryId = category.Id;
existing.FK_SubCategoryId = subCategory?.Id;
existing.FK_BrandId = brand?.Id;
existing.FK_BaseUomId = baseUom.Id;
existing.SalePrice = i.SalePrice;
existing.IsActive = i.IsActive;
existing.LastSyncedAt = DateTime.UtcNow;
itemRepo.Update(existing);
updated++;
}
}
await uow.CommitAsync(ct);
}
catch { await uow.RollbackAsync(ct); throw; }
return new SyncResult(inserted, updated, skipped);
}
}
@@ -0,0 +1,12 @@
namespace Distribution.Features.Uom;
public interface IUomRepository
{
Task<Models.Uom?> GetByIdAsync(Guid id);
Task<List<Models.Uom>> GetAllAsync();
Task<Models.Uom?> GetByExternalErpIdAsync(int erpId);
Task<bool> IsUsedByItemsAsync(Guid id);
Task AddAsync(Models.Uom entity);
void Update(Models.Uom entity);
void Remove(Models.Uom entity);
}
@@ -0,0 +1,47 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Distribution.Features.Uom;
[ApiController]
[Route("api/uoms")]
[Authorize]
public class UomController(UomService service) : ControllerBase
{
[HttpGet]
[Authorize(Policy = "items.view")]
public async Task<IActionResult> GetAll() =>
Ok(await service.GetAllAsync());
[HttpGet("{id:guid}")]
[Authorize(Policy = "items.view")]
public async Task<IActionResult> GetById(Guid id)
{
var result = await service.GetByIdAsync(id);
return result is null ? NotFound() : Ok(result);
}
[HttpPost]
[Authorize(Policy = "items.create")]
public async Task<IActionResult> Create(CreateUomRequest request, CancellationToken ct)
{
var result = await service.CreateAsync(request, ct);
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
}
[HttpPut("{id:guid}")]
[Authorize(Policy = "items.update")]
public async Task<IActionResult> Update(Guid id, UpdateUomRequest request, CancellationToken ct)
{
var result = await service.UpdateAsync(id, request, ct);
return result is null ? NotFound() : Ok(result);
}
[HttpDelete("{id:guid}")]
[Authorize(Policy = "items.delete")]
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
{
var deleted = await service.DeleteAsync(id, ct);
return deleted ? NoContent() : NotFound();
}
}
+5
View File
@@ -0,0 +1,5 @@
namespace Distribution.Features.Uom;
public record CreateUomRequest(string Name);
public record UpdateUomRequest(string Name);
public record UomResponse(Guid Id, string Name, bool IsActive, DateTime CreatedAt);
@@ -0,0 +1,32 @@
using Distribution.Data;
using Microsoft.EntityFrameworkCore;
namespace Distribution.Features.Uom;
public class UomRepository(AppDBContext context) : IUomRepository
{
public async Task<Models.Uom?> GetByIdAsync(Guid id) =>
await context.Uoms.FindAsync(id);
public async Task<List<Models.Uom>> GetAllAsync() =>
await context.Uoms.ToListAsync();
public async Task<Models.Uom?> GetByExternalErpIdAsync(int erpId) =>
await context.Uoms.FirstOrDefaultAsync(u => u.ExternalErpId == erpId);
public async Task<bool> IsUsedByItemsAsync(Guid id) =>
await context.Items.AnyAsync(i => i.FK_BaseUomId == id && i.IsActive) ||
await context.UomConversions.AnyAsync(c => c.FK_FromUomId == id || c.FK_ToUomId == id);
public async Task AddAsync(Models.Uom entity) =>
await context.Uoms.AddAsync(entity);
public void Update(Models.Uom entity) =>
context.Uoms.Update(entity);
public void Remove(Models.Uom entity)
{
entity.IsActive = false;
context.Uoms.Update(entity);
}
}
+52
View File
@@ -0,0 +1,52 @@
using Distribution.Infra.UoW;
namespace Distribution.Features.Uom;
public class UomService(IUomRepository repo, IUnitOfWork uow)
{
public async Task<List<UomResponse>> GetAllAsync() =>
(await repo.GetAllAsync())
.Where(u => u.IsActive)
.Select(u => new UomResponse(u.Id, u.Name, u.IsActive, u.CreatedAt))
.ToList();
public async Task<UomResponse?> GetByIdAsync(Guid id)
{
var uom = await repo.GetByIdAsync(id);
if (uom is null || !uom.IsActive) return null;
return new UomResponse(uom.Id, uom.Name, uom.IsActive, uom.CreatedAt);
}
public async Task<UomResponse> CreateAsync(CreateUomRequest request, CancellationToken ct = default)
{
var uom = new Models.Uom { Name = request.Name };
await uow.BeginAsync(ct);
try { await repo.AddAsync(uom); await uow.CommitAsync(ct); }
catch { await uow.RollbackAsync(ct); throw; }
return new UomResponse(uom.Id, uom.Name, uom.IsActive, uom.CreatedAt);
}
public async Task<UomResponse?> UpdateAsync(Guid id, UpdateUomRequest request, CancellationToken ct = default)
{
var uom = await repo.GetByIdAsync(id);
if (uom is null || !uom.IsActive) return null;
if (uom.ExternalErpId.HasValue) return null;
uom.Name = request.Name;
await uow.BeginAsync(ct);
try { repo.Update(uom); await uow.CommitAsync(ct); }
catch { await uow.RollbackAsync(ct); throw; }
return new UomResponse(uom.Id, uom.Name, uom.IsActive, uom.CreatedAt);
}
public async Task<bool> DeleteAsync(Guid id, CancellationToken ct = default)
{
var uom = await repo.GetByIdAsync(id);
if (uom is null || !uom.IsActive) return false;
if (uom.ExternalErpId.HasValue) return false;
if (await repo.IsUsedByItemsAsync(id)) return false;
await uow.BeginAsync(ct);
try { repo.Remove(uom); await uow.CommitAsync(ct); }
catch { await uow.RollbackAsync(ct); throw; }
return true;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,621 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace Distribution.Migrations
{
/// <inheritdoc />
public partial class AddItemCatalog : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<decimal>(
name: "UnitPrice",
table: "OrderItems",
type: "decimal(18,2)",
nullable: false,
defaultValue: 0m);
migrationBuilder.AddColumn<string>(
name: "Description",
table: "Items",
type: "longtext",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "ExternalErpItemId",
table: "Items",
type: "int",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "FK_BaseUomId",
table: "Items",
type: "char(36)",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
migrationBuilder.AddColumn<Guid>(
name: "FK_BrandId",
table: "Items",
type: "char(36)",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "FK_CategoryId",
table: "Items",
type: "char(36)",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
migrationBuilder.AddColumn<Guid>(
name: "FK_SubCategoryId",
table: "Items",
type: "char(36)",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "LastSyncedAt",
table: "Items",
type: "datetime(6)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Name",
table: "Items",
type: "longtext",
nullable: false);
migrationBuilder.AddColumn<decimal>(
name: "SalePrice",
table: "Items",
type: "decimal(18,2)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Sku",
table: "Items",
type: "longtext",
nullable: false);
migrationBuilder.AddColumn<int>(
name: "SyncSource",
table: "Items",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateTable(
name: "Brands",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false),
Name = table.Column<string>(type: "longtext", nullable: false),
ExternalErpId = table.Column<int>(type: "int", nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Brands", x => x.Id);
})
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable(
name: "Categories",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false),
Name = table.Column<string>(type: "longtext", nullable: false),
ExternalErpId = table.Column<int>(type: "int", nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Categories", x => x.Id);
})
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable(
name: "Uoms",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false),
Name = table.Column<string>(type: "longtext", nullable: false),
ExternalErpId = table.Column<int>(type: "int", nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Uoms", x => x.Id);
})
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable(
name: "SubCategories",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false),
Name = table.Column<string>(type: "longtext", nullable: false),
FK_CategoryId = table.Column<Guid>(type: "char(36)", nullable: false),
ExternalErpId = table.Column<int>(type: "int", nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SubCategories", x => x.Id);
table.ForeignKey(
name: "FK_SubCategories_Categories_FK_CategoryId",
column: x => x.FK_CategoryId,
principalTable: "Categories",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable(
name: "UomConversions",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false),
FK_ItemId = table.Column<Guid>(type: "char(36)", nullable: false),
FK_FromUomId = table.Column<Guid>(type: "char(36)", nullable: false),
FK_ToUomId = table.Column<Guid>(type: "char(36)", nullable: false),
Factor = table.Column<decimal>(type: "decimal(18,2)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_UomConversions", x => x.Id);
table.ForeignKey(
name: "FK_UomConversions_Items_FK_ItemId",
column: x => x.FK_ItemId,
principalTable: "Items",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_UomConversions_Uoms_FK_FromUomId",
column: x => x.FK_FromUomId,
principalTable: "Uoms",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_UomConversions_Uoms_FK_ToUomId",
column: x => x.FK_ToUomId,
principalTable: "Uoms",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.InsertData(
table: "Permissions",
columns: new[] { "Id", "Code", "CreatedAt", "Description", "IsActive" },
values: new object[,]
{
{ new Guid("20000000-0000-0000-0000-000000000031"), "items.view", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "View items, brands and UOMs", true },
{ new Guid("20000000-0000-0000-0000-000000000032"), "items.create", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Create items, brands and UOMs", true },
{ new Guid("20000000-0000-0000-0000-000000000033"), "items.update", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Update items, brands and UOMs", true },
{ new Guid("20000000-0000-0000-0000-000000000034"), "items.delete", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Delete items, brands and UOMs", true },
{ new Guid("20000000-0000-0000-0000-000000000035"), "categories.view", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "View categories", true },
{ new Guid("20000000-0000-0000-0000-000000000036"), "categories.create", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Create categories and subcategories", true },
{ new Guid("20000000-0000-0000-0000-000000000037"), "categories.update", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Update categories and subcategories", true },
{ new Guid("20000000-0000-0000-0000-000000000038"), "categories.delete", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Delete categories and subcategories", true }
});
migrationBuilder.InsertData(
table: "RolePermissions",
columns: new[] { "FK_PermissionId", "FK_RoleId", "CreatedAt", "Id", "IsActive" },
values: new object[,]
{
{ new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000001"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("7596f703-c6ab-316c-7c80-3f4e3daa174e"), true },
{ new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000001"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("aa120c36-488e-34ba-e10a-2ac45e46e119"), true },
{ new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("b7dee5e3-f329-95a5-75c1-d02c3e40e229"), true },
{ new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("e5506b30-527d-08f9-6caa-8a45d3d02999"), true },
{ new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("7780d277-9597-84ff-1a1a-f63fc6b4d3cb"), true },
{ new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("8083c659-9506-0bd9-b793-3d6148ce9192"), true },
{ new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("d49f086e-30f0-6ce8-ae96-4395d5e04472"), true },
{ new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("1e7fe1b6-5150-4b99-38ed-088d9495cfd8"), true },
{ new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("a701da28-aa75-3c1d-abdb-4317404f8df6"), true },
{ new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("2c025310-765f-7d07-ab9a-50524c13d1d3"), true },
{ new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("7c392a13-1a57-0d85-dffa-2fb9e892abe9"), true },
{ new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("97708eed-6ce4-7710-abde-01d00c0c879b"), true },
{ new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("9afe507e-191e-bdee-0c5a-cfbcbf1efb3e"), true },
{ new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("9c278616-4289-96fb-944d-b8f1495ccb00"), true },
{ new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("d1dcef02-d4c3-caba-372f-dab82011e9c1"), true },
{ new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("9cc36229-dfba-49b9-1d6e-8929546e894d"), true },
{ new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("69f93a29-1df1-7b20-9d5c-94c264f8c4b8"), true },
{ new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("070813f7-4053-2889-0d14-d0d5834208c2"), true },
{ new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("0efa7259-aadb-0019-4be5-38d3f8f4cd54"), true },
{ new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("9e60e7d7-8bd6-c1c7-4bf1-4de2c9dcb91c"), true },
{ new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("5df82318-bc01-1947-09ef-60508e02b257"), true },
{ new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("34f478f4-6c96-4b31-b68a-28c7942c0789"), true },
{ new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("69598e0e-b7ad-477b-c587-7abf6189162d"), true },
{ new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("b36fb2d4-43df-44f8-ff51-de6d88644c94"), true },
{ new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("bd209f90-6671-8ea0-eb20-aa151faec6a0"), true },
{ new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("4cc32686-8391-0214-5747-db14f53c5b6f"), true },
{ new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("26c6d86a-835d-6f0d-3730-66a46ea26b11"), true },
{ new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("e8039d9c-a286-fb74-5abb-b5c6c67f243a"), true },
{ new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("d9e038cb-bcff-cce1-5d34-b4e63a9ce694"), true },
{ new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("343e71f0-da6b-2489-5571-f8d7c6378ee7"), true },
{ new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("ac894328-a935-8e6e-8427-6c0f36ac75da"), true },
{ new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("1d6273d4-db6a-d70e-4b5f-98cb634d668a"), true }
});
migrationBuilder.CreateIndex(
name: "IX_Items_FK_BaseUomId",
table: "Items",
column: "FK_BaseUomId");
migrationBuilder.CreateIndex(
name: "IX_Items_FK_BrandId",
table: "Items",
column: "FK_BrandId");
migrationBuilder.CreateIndex(
name: "IX_Items_FK_CategoryId",
table: "Items",
column: "FK_CategoryId");
migrationBuilder.CreateIndex(
name: "IX_Items_FK_SubCategoryId",
table: "Items",
column: "FK_SubCategoryId");
migrationBuilder.CreateIndex(
name: "IX_SubCategories_FK_CategoryId",
table: "SubCategories",
column: "FK_CategoryId");
migrationBuilder.CreateIndex(
name: "IX_UomConversions_FK_FromUomId",
table: "UomConversions",
column: "FK_FromUomId");
migrationBuilder.CreateIndex(
name: "IX_UomConversions_FK_ItemId",
table: "UomConversions",
column: "FK_ItemId");
migrationBuilder.CreateIndex(
name: "IX_UomConversions_FK_ToUomId",
table: "UomConversions",
column: "FK_ToUomId");
migrationBuilder.AddForeignKey(
name: "FK_Items_Brands_FK_BrandId",
table: "Items",
column: "FK_BrandId",
principalTable: "Brands",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Items_Categories_FK_CategoryId",
table: "Items",
column: "FK_CategoryId",
principalTable: "Categories",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Items_SubCategories_FK_SubCategoryId",
table: "Items",
column: "FK_SubCategoryId",
principalTable: "SubCategories",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Items_Uoms_FK_BaseUomId",
table: "Items",
column: "FK_BaseUomId",
principalTable: "Uoms",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Items_Brands_FK_BrandId",
table: "Items");
migrationBuilder.DropForeignKey(
name: "FK_Items_Categories_FK_CategoryId",
table: "Items");
migrationBuilder.DropForeignKey(
name: "FK_Items_SubCategories_FK_SubCategoryId",
table: "Items");
migrationBuilder.DropForeignKey(
name: "FK_Items_Uoms_FK_BaseUomId",
table: "Items");
migrationBuilder.DropTable(
name: "Brands");
migrationBuilder.DropTable(
name: "SubCategories");
migrationBuilder.DropTable(
name: "UomConversions");
migrationBuilder.DropTable(
name: "Categories");
migrationBuilder.DropTable(
name: "Uoms");
migrationBuilder.DropIndex(
name: "IX_Items_FK_BaseUomId",
table: "Items");
migrationBuilder.DropIndex(
name: "IX_Items_FK_BrandId",
table: "Items");
migrationBuilder.DropIndex(
name: "IX_Items_FK_CategoryId",
table: "Items");
migrationBuilder.DropIndex(
name: "IX_Items_FK_SubCategoryId",
table: "Items");
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000001") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000001") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000002") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000002") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000003") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000003") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000003") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000003") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000004") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000004") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000004") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000004") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000004") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000004") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000004") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000004") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000005") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000005") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000005") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000005") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000005") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000005") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000005") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000005") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000006") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000006") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000006") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000006") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000006") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000006") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000006") });
migrationBuilder.DeleteData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000006") });
migrationBuilder.DeleteData(
table: "Permissions",
keyColumn: "Id",
keyValue: new Guid("20000000-0000-0000-0000-000000000031"));
migrationBuilder.DeleteData(
table: "Permissions",
keyColumn: "Id",
keyValue: new Guid("20000000-0000-0000-0000-000000000032"));
migrationBuilder.DeleteData(
table: "Permissions",
keyColumn: "Id",
keyValue: new Guid("20000000-0000-0000-0000-000000000033"));
migrationBuilder.DeleteData(
table: "Permissions",
keyColumn: "Id",
keyValue: new Guid("20000000-0000-0000-0000-000000000034"));
migrationBuilder.DeleteData(
table: "Permissions",
keyColumn: "Id",
keyValue: new Guid("20000000-0000-0000-0000-000000000035"));
migrationBuilder.DeleteData(
table: "Permissions",
keyColumn: "Id",
keyValue: new Guid("20000000-0000-0000-0000-000000000036"));
migrationBuilder.DeleteData(
table: "Permissions",
keyColumn: "Id",
keyValue: new Guid("20000000-0000-0000-0000-000000000037"));
migrationBuilder.DeleteData(
table: "Permissions",
keyColumn: "Id",
keyValue: new Guid("20000000-0000-0000-0000-000000000038"));
migrationBuilder.DropColumn(
name: "UnitPrice",
table: "OrderItems");
migrationBuilder.DropColumn(
name: "Description",
table: "Items");
migrationBuilder.DropColumn(
name: "ExternalErpItemId",
table: "Items");
migrationBuilder.DropColumn(
name: "FK_BaseUomId",
table: "Items");
migrationBuilder.DropColumn(
name: "FK_BrandId",
table: "Items");
migrationBuilder.DropColumn(
name: "FK_CategoryId",
table: "Items");
migrationBuilder.DropColumn(
name: "FK_SubCategoryId",
table: "Items");
migrationBuilder.DropColumn(
name: "LastSyncedAt",
table: "Items");
migrationBuilder.DropColumn(
name: "Name",
table: "Items");
migrationBuilder.DropColumn(
name: "SalePrice",
table: "Items");
migrationBuilder.DropColumn(
name: "Sku",
table: "Items");
migrationBuilder.DropColumn(
name: "SyncSource",
table: "Items");
}
}
}
@@ -16,10 +16,57 @@ namespace Distribution.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Distribution.Models.Brand", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<int?>("ExternalErpId")
.HasColumnType("int");
b.Property<bool>("IsActive")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Brands");
});
modelBuilder.Entity("Distribution.Models.Category", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<int?>("ExternalErpId")
.HasColumnType("int");
b.Property<bool>("IsActive")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Categories");
});
modelBuilder.Entity("Distribution.Models.DeliveryRun", b =>
{
b.Property<Guid>("Id")
@@ -62,11 +109,54 @@ namespace Distribution.Migrations
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Description")
.HasColumnType("longtext");
b.Property<int?>("ExternalErpItemId")
.HasColumnType("int");
b.Property<Guid>("FK_BaseUomId")
.HasColumnType("char(36)");
b.Property<Guid?>("FK_BrandId")
.HasColumnType("char(36)");
b.Property<Guid>("FK_CategoryId")
.HasColumnType("char(36)");
b.Property<Guid?>("FK_SubCategoryId")
.HasColumnType("char(36)");
b.Property<bool>("IsActive")
.HasColumnType("tinyint(1)");
b.Property<DateTime?>("LastSyncedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<decimal?>("SalePrice")
.HasColumnType("decimal(18,2)");
b.Property<string>("Sku")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("SyncSource")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("FK_BaseUomId");
b.HasIndex("FK_BrandId");
b.HasIndex("FK_CategoryId");
b.HasIndex("FK_SubCategoryId");
b.ToTable("Items");
});
@@ -118,6 +208,9 @@ namespace Distribution.Migrations
b.Property<int>("Quantity")
.HasColumnType("int");
b.Property<decimal>("UnitPrice")
.HasColumnType("decimal(18,2)");
b.HasKey("FK_OrderId", "FK_ItemId");
b.HasIndex("FK_ItemId");
@@ -389,6 +482,70 @@ namespace Distribution.Migrations
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Description = "Assign region/route/store scope to a user",
IsActive = true
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000031"),
Code = "items.view",
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Description = "View items, brands and UOMs",
IsActive = true
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000032"),
Code = "items.create",
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Description = "Create items, brands and UOMs",
IsActive = true
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000033"),
Code = "items.update",
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Description = "Update items, brands and UOMs",
IsActive = true
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000034"),
Code = "items.delete",
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Description = "Delete items, brands and UOMs",
IsActive = true
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000035"),
Code = "categories.view",
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Description = "View categories",
IsActive = true
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000036"),
Code = "categories.create",
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Description = "Create categories and subcategories",
IsActive = true
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000037"),
Code = "categories.update",
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Description = "Update categories and subcategories",
IsActive = true
},
new
{
Id = new Guid("20000000-0000-0000-0000-000000000038"),
Code = "categories.delete",
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Description = "Delete categories and subcategories",
IsActive = true
});
});
@@ -552,6 +709,22 @@ namespace Distribution.Migrations
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000031"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("7596f703-c6ab-316c-7c80-3f4e3daa174e"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000035"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("aa120c36-488e-34ba-e10a-2ac45e46e119"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000002"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000021"),
@@ -592,6 +765,22 @@ namespace Distribution.Migrations
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000002"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000031"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("b7dee5e3-f329-95a5-75c1-d02c3e40e229"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000002"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000035"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("e5506b30-527d-08f9-6caa-8a45d3d02999"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000002"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000024"),
@@ -656,6 +845,22 @@ namespace Distribution.Migrations
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000003"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000031"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("7780d277-9597-84ff-1a1a-f63fc6b4d3cb"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000003"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000035"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("1e7fe1b6-5150-4b99-38ed-088d9495cfd8"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000003"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000024"),
@@ -696,6 +901,22 @@ namespace Distribution.Migrations
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000003"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000032"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("8083c659-9506-0bd9-b793-3d6148ce9192"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000003"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000033"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("d49f086e-30f0-6ce8-ae96-4395d5e04472"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000021"),
@@ -736,6 +957,22 @@ namespace Distribution.Migrations
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000031"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("a701da28-aa75-3c1d-abdb-4317404f8df6"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000035"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("9afe507e-191e-bdee-0c5a-cfbcbf1efb3e"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000024"),
@@ -776,6 +1013,22 @@ namespace Distribution.Migrations
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000032"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("2c025310-765f-7d07-ab9a-50524c13d1d3"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000033"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("7c392a13-1a57-0d85-dffa-2fb9e892abe9"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000006"),
@@ -792,6 +1045,38 @@ namespace Distribution.Migrations
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000034"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("97708eed-6ce4-7710-abde-01d00c0c879b"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000036"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("9c278616-4289-96fb-944d-b8f1495ccb00"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000037"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("d1dcef02-d4c3-caba-372f-dab82011e9c1"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000038"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("9cc36229-dfba-49b9-1d6e-8929546e894d"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000021"),
@@ -968,6 +1253,70 @@ namespace Distribution.Migrations
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000031"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("69f93a29-1df1-7b20-9d5c-94c264f8c4b8"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000032"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("070813f7-4053-2889-0d14-d0d5834208c2"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000033"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("0efa7259-aadb-0019-4be5-38d3f8f4cd54"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000034"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("9e60e7d7-8bd6-c1c7-4bf1-4de2c9dcb91c"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000035"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("5df82318-bc01-1947-09ef-60508e02b257"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000036"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("34f478f4-6c96-4b31-b68a-28c7942c0789"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000037"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("69598e0e-b7ad-477b-c587-7abf6189162d"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000038"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("b36fb2d4-43df-44f8-ff51-de6d88644c94"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000006"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000001"),
@@ -1206,6 +1555,70 @@ namespace Distribution.Migrations
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("ace990cf-6654-5e68-3dc8-1e62347e0946"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000006"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000031"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("bd209f90-6671-8ea0-eb20-aa151faec6a0"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000006"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000032"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("4cc32686-8391-0214-5747-db14f53c5b6f"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000006"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000033"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("26c6d86a-835d-6f0d-3730-66a46ea26b11"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000006"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000034"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("e8039d9c-a286-fb74-5abb-b5c6c67f243a"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000006"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000035"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("d9e038cb-bcff-cce1-5d34-b4e63a9ce694"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000006"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000036"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("343e71f0-da6b-2489-5571-f8d7c6378ee7"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000006"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000037"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("ac894328-a935-8e6e-8427-6c0f36ac75da"),
IsActive = true
},
new
{
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000006"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000038"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("1d6273d4-db6a-d70e-4b5f-98cb634d668a"),
IsActive = true
});
});
@@ -1309,6 +1722,88 @@ namespace Distribution.Migrations
b.ToTable("Stores");
});
modelBuilder.Entity("Distribution.Models.SubCategory", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<int?>("ExternalErpId")
.HasColumnType("int");
b.Property<Guid>("FK_CategoryId")
.HasColumnType("char(36)");
b.Property<bool>("IsActive")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("FK_CategoryId");
b.ToTable("SubCategories");
});
modelBuilder.Entity("Distribution.Models.Uom", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<int?>("ExternalErpId")
.HasColumnType("int");
b.Property<bool>("IsActive")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Uoms");
});
modelBuilder.Entity("Distribution.Models.UomConversion", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<Guid>("FK_FromUomId")
.HasColumnType("char(36)");
b.Property<Guid>("FK_ItemId")
.HasColumnType("char(36)");
b.Property<Guid>("FK_ToUomId")
.HasColumnType("char(36)");
b.Property<decimal>("Factor")
.HasColumnType("decimal(18,2)");
b.HasKey("Id");
b.HasIndex("FK_FromUomId");
b.HasIndex("FK_ItemId");
b.HasIndex("FK_ToUomId");
b.ToTable("UomConversions");
});
modelBuilder.Entity("Distribution.Models.User", b =>
{
b.Property<Guid>("Id")
@@ -1503,6 +1998,37 @@ namespace Distribution.Migrations
b.Navigation("Vehicle");
});
modelBuilder.Entity("Distribution.Models.Item", b =>
{
b.HasOne("Distribution.Models.Uom", "BaseUom")
.WithMany()
.HasForeignKey("FK_BaseUomId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Distribution.Models.Brand", "Brand")
.WithMany()
.HasForeignKey("FK_BrandId");
b.HasOne("Distribution.Models.Category", "Category")
.WithMany()
.HasForeignKey("FK_CategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Distribution.Models.SubCategory", "SubCategory")
.WithMany()
.HasForeignKey("FK_SubCategoryId");
b.Navigation("BaseUom");
b.Navigation("Brand");
b.Navigation("Category");
b.Navigation("SubCategory");
});
modelBuilder.Entity("Distribution.Models.Order", b =>
{
b.HasOne("Distribution.Models.Store", "Store")
@@ -1574,6 +2100,44 @@ namespace Distribution.Migrations
b.Navigation("Route");
});
modelBuilder.Entity("Distribution.Models.SubCategory", b =>
{
b.HasOne("Distribution.Models.Category", "Category")
.WithMany()
.HasForeignKey("FK_CategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Category");
});
modelBuilder.Entity("Distribution.Models.UomConversion", b =>
{
b.HasOne("Distribution.Models.Uom", "FromUom")
.WithMany()
.HasForeignKey("FK_FromUomId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Distribution.Models.Item", "Item")
.WithMany("Conversions")
.HasForeignKey("FK_ItemId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Distribution.Models.Uom", "ToUom")
.WithMany()
.HasForeignKey("FK_ToUomId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("FromUom");
b.Navigation("Item");
b.Navigation("ToUom");
});
modelBuilder.Entity("Distribution.Models.User", b =>
{
b.HasOne("Distribution.Models.User", "ReportsTo")
@@ -1657,6 +2221,11 @@ namespace Distribution.Migrations
b.Navigation("Vehicle");
});
modelBuilder.Entity("Distribution.Models.Item", b =>
{
b.Navigation("Conversions");
});
#pragma warning restore 612, 618
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace Distribution.Models
{
public class Brand : Template
{
public string Name { get; set; }
public int? ExternalErpId { get; set; }
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace Distribution.Models
{
public class Category : Template
{
public string Name { get; set; }
public int? ExternalErpId { get; set; }
}
}
+30 -2
View File
@@ -1,7 +1,35 @@
namespace Distribution.Models
using System.ComponentModel.DataAnnotations.Schema;
namespace Distribution.Models
{
public class Item : Template
{
public string Sku { get; set; }
public string Name { get; set; }
public string? Description { get; set; }
public Guid FK_CategoryId { get; set; }
[ForeignKey("FK_CategoryId")]
public Category Category { get; set; }
public Guid? FK_SubCategoryId { get; set; }
[ForeignKey("FK_SubCategoryId")]
public SubCategory? SubCategory { get; set; }
public Guid? FK_BrandId { get; set; }
[ForeignKey("FK_BrandId")]
public Brand? Brand { get; set; }
public Guid FK_BaseUomId { get; set; }
[ForeignKey("FK_BaseUomId")]
public Uom BaseUom { get; set; }
public decimal? SalePrice { get; set; }
public SyncSource SyncSource { get; set; } = SyncSource.Manual;
public int? ExternalErpItemId { get; set; }
public DateTime? LastSyncedAt { get; set; }
public ICollection<UomConversion> Conversions { get; set; } = [];
}
}
+1
View File
@@ -11,5 +11,6 @@ namespace Distribution.Models
[ForeignKey("FK_ItemId")]
public Item Item { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
}
}
+13
View File
@@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Distribution.Models
{
public class SubCategory : Template
{
public string Name { get; set; }
public Guid FK_CategoryId { get; set; }
[ForeignKey("FK_CategoryId")]
public Category Category { get; set; }
public int? ExternalErpId { get; set; }
}
}
+4
View File
@@ -0,0 +1,4 @@
namespace Distribution.Models
{
public enum SyncSource { Manual, Erp }
}
+8
View File
@@ -0,0 +1,8 @@
namespace Distribution.Models
{
public class Uom : Template
{
public string Name { get; set; }
public int? ExternalErpId { get; set; }
}
}
+23
View File
@@ -0,0 +1,23 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Distribution.Models
{
public class UomConversion
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid FK_ItemId { get; set; }
[ForeignKey("FK_ItemId")]
public Item Item { get; set; }
public Guid FK_FromUomId { get; set; }
[ForeignKey("FK_FromUomId")]
public Uom FromUom { get; set; }
public Guid FK_ToUomId { get; set; }
[ForeignKey("FK_ToUomId")]
public Uom ToUom { get; set; }
public decimal Factor { get; set; }
}
}
+15
View File
@@ -1,11 +1,16 @@
using System.Text;
using Distribution.Data;
using Distribution.Features.Auth;
using Distribution.Features.Brand;
using Distribution.Features.Category;
using Distribution.Features.Item;
using Distribution.Features.Order;
using Distribution.Features.Region;
using Distribution.Features.Role;
using Distribution.Features.Route;
using Distribution.Features.Store;
using Distribution.Features.Sync;
using Distribution.Features.Uom;
using Distribution.Features.User;
using Distribution.Features.Vehicle;
using Distribution.Features.Warehouse;
@@ -35,6 +40,11 @@ builder.Services.AddScoped<IWarehouseRepository, WarehouseRepository>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IRoleRepository, RoleRepository>();
builder.Services.AddScoped<ICategoryRepository, CategoryRepository>();
builder.Services.AddScoped<ISubCategoryRepository, SubCategoryRepository>();
builder.Services.AddScoped<IBrandRepository, BrandRepository>();
builder.Services.AddScoped<IUomRepository, UomRepository>();
builder.Services.AddScoped<IItemRepository, ItemRepository>();
builder.Services.AddScoped<RegionService>();
builder.Services.AddScoped<RouteService>();
@@ -45,6 +55,11 @@ builder.Services.AddScoped<OrderService>();
builder.Services.AddScoped<UserService>();
builder.Services.AddScoped<RoleService>();
builder.Services.AddScoped<AuthService>();
builder.Services.AddScoped<CategoryService>();
builder.Services.AddScoped<BrandService>();
builder.Services.AddScoped<UomService>();
builder.Services.AddScoped<ItemService>();
builder.Services.AddScoped<SyncService>();
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<ICurrentUserService, CurrentUserService>();
+235 -7
View File
@@ -1,52 +1,280 @@
# 01 — Item Catalog
**Status**: Not Started
**Depends On**: 00 — Existing Gap Fixes
**Status**: Ready
**Depends On**: 00 — Existing Gap Fixes (gaps 1 & 2 done)
---
## Goal
Define and implement a full item catalog with reference masters (Category, SubCategory, Brand, UOM)
and the Item entity itself. Support both standalone clients (full CRUD) and ERP-integrated clients
(sync endpoint that upserts from ERP). Everything downstream — stock ledger, orders, rides — reads
from the local item table regardless of how items got there.
---
## Background / Context
Two client types:
- **Standalone** — client manages their own catalog. Creates categories, brands, UOMs, and items
directly via CRUD APIs. `SyncSource = Manual`.
- **ERP + Distribution** — ERP pushes data into Distribution via sync endpoints. CRUD write
endpoints are blocked for synced records. `SyncSource = Erp`.
ERP uses `int` PKs. Distribution uses `Guid` PKs. Each reference master and item carries an
`ExternalErpId` (nullable int) so the sync can match and upsert by ERP ID and resolve local Guids.
Sync order is strict — ERP must push in this order:
1. UOMs
2. Categories + SubCategories
3. Brands
4. Items (sync resolves local Guid FKs via ExternalErpId lookups)
---
## Data Model Changes
### Item
### Category
```
Category
├── Id (Guid) — PK, from Template
├── Name (string)
├── ExternalErpId (int?) — null for manually created
├── IsActive, CreatedAt — from Template
```
### SubCategory
```
SubCategory
├── Id (Guid) — PK, from Template
├── Name (string)
├── FK_CategoryId (Guid) — required, parent Category
├── ExternalErpId (int?)
├── IsActive, CreatedAt — from Template
```
Rule: SubCategory must belong to the CategoryId it is assigned to. A subcategory cannot be
moved to a different category after creation.
### Brand
```
Brand
├── Id (Guid) — PK, from Template
├── Name (string)
├── ExternalErpId (int?)
├── IsActive, CreatedAt — from Template
```
### Related Changes to Existing Models
### Uom (Unit of Measure)
```
Uom
├── Id (Guid) — PK, from Template
├── Name (string) — e.g. "PC", "CASE", "KG", "BOTTLE"
├── ExternalErpId (int?)
├── IsActive, CreatedAt — from Template
```
### UomConversion
```
UomConversion
├── Id (Guid) — PK, from Template
├── FK_ItemId (Guid) — which item this conversion belongs to
├── FK_FromUomId (Guid) — source UOM
├── FK_ToUomId (Guid) — target UOM
├── Factor (decimal) — FromQty × Factor = ToQty
├── IsActive, CreatedAt — from Template
```
Example: Item = "Coca-Cola 330ml", FromUom = CASE, ToUom = BOTTLE, Factor = 24
→ 1 CASE × 24 = 24 BOTTLES
### Item (replaces empty Item.cs)
```
Item
├── Id (Guid) — PK, from Template
├── Sku (string, unique) — field identifier, unique across all items
├── Name (string)
├── Description (string?)
├── FK_CategoryId (Guid) — required
├── FK_SubCategoryId (Guid?) — optional
├── FK_BrandId (Guid?) — optional
├── FK_BaseUomId (Guid) — required, base unit for stock counting
├── SalePrice (decimal?) — NULL = price entered manually at order time
├── SyncSource (enum) — Manual | Erp
├── ExternalErpItemId (int?) — null for standalone items
├── LastSyncedAt (DateTime?) — null for standalone items
├── IsActive, CreatedAt — from Template
```
### OrderItem — add UnitPrice
```
OrderItem (existing, add one column)
├── UnitPrice (decimal) — price captured at time of order, not looked up live
```
This is a migration change on an existing table.
---
## Migrations
Two migrations needed:
1. **AddItemCatalogReferenceMasters** — creates Category, SubCategory, Brand, Uom, UomConversion tables
2. **UpdateItemAndOrderItem** — adds columns to existing Items table, adds UnitPrice to OrderItems
---
## API Endpoints
### Categories
| Method | Route | Permission | Notes |
|---|---|---|---|
| GET | /api/categories | categories.view | Returns all active, includes subcategory count |
| GET | /api/categories/{id} | categories.view | Includes subcategories list |
| POST | /api/categories | categories.create | Standalone only |
| PUT | /api/categories/{id} | categories.update | Blocked if SyncSource = Erp |
| DELETE | /api/categories/{id} | categories.delete | Soft delete, blocked if items reference it |
### SubCategories
| Method | Route | Permission | Notes |
|---|---|---|---|
| POST | /api/categories/{categoryId}/subcategories | categories.create | Validates parent exists |
| PUT | /api/subcategories/{id} | categories.update | Blocked if SyncSource = Erp |
| DELETE | /api/subcategories/{id} | categories.delete | Blocked if items reference it |
### Brands
| Method | Route | Permission | Notes |
|---|---|---|---|
| GET | /api/brands | items.view | |
| POST | /api/brands | items.create | Standalone only |
| PUT | /api/brands/{id} | items.update | Blocked if SyncSource = Erp |
| DELETE | /api/brands/{id} | items.delete | Blocked if items reference it |
### UOMs
| Method | Route | Permission | Notes |
|---|---|---|---|
| GET | /api/uoms | items.view | |
| POST | /api/uoms | items.create | Standalone only |
| PUT | /api/uoms/{id} | items.update | Blocked if SyncSource = Erp |
| DELETE | /api/uoms/{id} | items.delete | Blocked if items reference it |
### Items
| Method | Route | Permission | Notes |
|---|---|---|---|
| GET | /api/items | items.view | Filterable by category, brand, subcategory |
| GET | /api/items/{id} | items.view | Includes UOM conversions |
| POST | /api/items | items.create | Validates SKU unique, FK references exist |
| PUT | /api/items/{id} | items.update | Blocked if SyncSource = Erp |
| DELETE | /api/items/{id} | items.delete | Blocked if stock exists for item |
| PUT | /api/items/{id}/uom-conversions | items.update | Replace full conversion list |
### Sync (ERP → Distribution)
| Method | Route | Auth | Notes |
|---|---|---|---|
| POST | /api/sync/uoms | JWT (service user) | Upsert by ExternalErpId |
| POST | /api/sync/categories | JWT (service user) | Upsert by ExternalErpId, includes subcategories |
| POST | /api/sync/brands | JWT (service user) | Upsert by ExternalErpId |
| POST | /api/sync/items | JWT (service user) | Upsert by ExternalErpItemId, resolves local FK Guids via ExternalErpId |
---
## Business Rules
1. SKU must be unique across all items.
2. SubCategory must belong to the specified CategoryId — cannot assign a subcategory from a
different parent category.
3. Items with `SyncSource = Erp` cannot be modified via standard CRUD endpoints (PUT/DELETE).
Only the sync endpoint can update them.
4. Reference masters (Category, Brand, UOM) with `SyncSource = Erp` cannot be modified via
standard CRUD endpoints.
5. A Category cannot be deleted if any SubCategory or Item references it.
6. A SubCategory cannot be deleted if any Item references it.
7. A Brand cannot be deleted if any Item references it.
8. A UOM cannot be deleted if any Item uses it as BaseUom or if any UomConversion references it.
9. An Item cannot be deleted (soft delete blocked) if stock exists for it. Hard delete never
allowed.
10. `UnitPrice` on OrderItem is captured at the moment the order is created from `Item.SalePrice`.
If `SalePrice` is null, the caller must supply the price explicitly.
---
## Permissions to Add to Seed
New permission codes to add to the existing seed:
```
items.view, items.create, items.update, items.delete
categories.view, categories.create, categories.update, categories.delete
```
Role assignments:
- **SalesRep (10)**: items.view, categories.view
- **Supervisor (20)**: items.view, categories.view
- **AreaManager (30)**: items.view, categories.view, items.create, items.update
- **AreaSupervisor (40)**: all item + category permissions
- **RegionalManager (50)**: all item + category permissions
- **RegionalAgency (60)**: all item + category permissions
---
## Open Questions
- None. Resolved in planning discussion.
---
## Implementation Notes
> Fill this section in during planning discussion before writing any code.
### File structure to create
```
Features/
Category/
CategoryController.cs
CategoryDTOs.cs
CategoryService.cs
ICategoryRepository.cs
CategoryRepository.cs
Brand/
BrandController.cs
BrandDTOs.cs
BrandService.cs
IBrandRepository.cs
BrandRepository.cs
Uom/
UomController.cs
UomDTOs.cs
UomService.cs
IUomRepository.cs
UomRepository.cs
Item/
ItemController.cs
ItemDTOs.cs
ItemService.cs
IItemRepository.cs
ItemRepository.cs
Sync/
SyncController.cs
SyncDTOs.cs
SyncService.cs
Models/
Category.cs (new)
SubCategory.cs (new)
Brand.cs (new)
Uom.cs (new)
UomConversion.cs (new)
Item.cs (replace empty)
Migrations/
AddItemCatalogReferenceMasters
UpdateItemAndOrderItem
```
### Sync upsert logic (pseudo)
```
POST /api/sync/items — for each item in payload:
1. Lookup local Category by ExternalErpId = item.categoryId → get local CategoryGuid
2. Lookup local SubCategory by ExternalErpId = item.subCategoryId (if present)
3. Lookup local Brand by ExternalErpId = item.brandId (if present)
4. Lookup local Uom by ExternalErpId = item.baseUomId → get local UomGuid
5. Find existing Item where ExternalErpItemId = item.itemId
→ if found: update fields, set LastSyncedAt = now
→ if not found: insert new, SyncSource = Erp
```
+1 -1
View File
@@ -35,7 +35,7 @@ They must be resolved before new feature steps begin because later steps depend
| # | Feature | Status | Depends On | Detail File |
|---|---|---|---|---|
| 00 | Existing Gap Fixes | Not Started | — | [00_existing_gaps.md](./00_existing_gaps.md) |
| 01 | Item Catalog | Not Started | 00 | [01_item_catalog.md](./01_item_catalog.md) |
| 01 | Item Catalog | Done | 00 | [01_item_catalog.md](./01_item_catalog.md) |
| 02 | Stock Ledger | Not Started | 01 | [02_stock_ledger.md](./02_stock_ledger.md) |
| 03 | Ride Lifecycle | Not Started | 02 | [03_ride_lifecycle.md](./03_ride_lifecycle.md) |
| 04 | Loading Event | Not Started | 03 | [04_loading_event.md](./04_loading_event.md) |
@@ -0,0 +1,266 @@
START TRANSACTION;
ALTER TABLE `OrderItems` ADD `UnitPrice` decimal(18,2) NOT NULL DEFAULT 0.0;
ALTER TABLE `Items` ADD `Description` longtext NULL;
ALTER TABLE `Items` ADD `ExternalErpItemId` int NULL;
ALTER TABLE `Items` ADD `FK_BaseUomId` char(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000';
ALTER TABLE `Items` ADD `FK_BrandId` char(36) NULL;
ALTER TABLE `Items` ADD `FK_CategoryId` char(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000';
ALTER TABLE `Items` ADD `FK_SubCategoryId` char(36) NULL;
ALTER TABLE `Items` ADD `LastSyncedAt` datetime(6) NULL;
ALTER TABLE `Items` ADD `Name` longtext NOT NULL;
ALTER TABLE `Items` ADD `SalePrice` decimal(18,2) NULL;
ALTER TABLE `Items` ADD `Sku` longtext NOT NULL;
ALTER TABLE `Items` ADD `SyncSource` int NOT NULL DEFAULT 0;
CREATE TABLE `Brands` (
`Id` char(36) NOT NULL,
`Name` longtext NOT NULL,
`ExternalErpId` int NULL,
`CreatedAt` datetime(6) NOT NULL,
`IsActive` tinyint(1) NOT NULL,
PRIMARY KEY (`Id`)
);
CREATE TABLE `Categories` (
`Id` char(36) NOT NULL,
`Name` longtext NOT NULL,
`ExternalErpId` int NULL,
`CreatedAt` datetime(6) NOT NULL,
`IsActive` tinyint(1) NOT NULL,
PRIMARY KEY (`Id`)
);
CREATE TABLE `Uoms` (
`Id` char(36) NOT NULL,
`Name` longtext NOT NULL,
`ExternalErpId` int NULL,
`CreatedAt` datetime(6) NOT NULL,
`IsActive` tinyint(1) NOT NULL,
PRIMARY KEY (`Id`)
);
CREATE TABLE `SubCategories` (
`Id` char(36) NOT NULL,
`Name` longtext NOT NULL,
`FK_CategoryId` char(36) NOT NULL,
`ExternalErpId` int NULL,
`CreatedAt` datetime(6) NOT NULL,
`IsActive` tinyint(1) NOT NULL,
PRIMARY KEY (`Id`),
CONSTRAINT `FK_SubCategories_Categories_FK_CategoryId` FOREIGN KEY (`FK_CategoryId`) REFERENCES `Categories` (`Id`) ON DELETE CASCADE
);
CREATE TABLE `UomConversions` (
`Id` char(36) NOT NULL,
`FK_ItemId` char(36) NOT NULL,
`FK_FromUomId` char(36) NOT NULL,
`FK_ToUomId` char(36) NOT NULL,
`Factor` decimal(18,2) NOT NULL,
PRIMARY KEY (`Id`),
CONSTRAINT `FK_UomConversions_Items_FK_ItemId` FOREIGN KEY (`FK_ItemId`) REFERENCES `Items` (`Id`) ON DELETE CASCADE,
CONSTRAINT `FK_UomConversions_Uoms_FK_FromUomId` FOREIGN KEY (`FK_FromUomId`) REFERENCES `Uoms` (`Id`) ON DELETE CASCADE,
CONSTRAINT `FK_UomConversions_Uoms_FK_ToUomId` FOREIGN KEY (`FK_ToUomId`) REFERENCES `Uoms` (`Id`) ON DELETE CASCADE
);
INSERT INTO `Permissions` (`Id`, `Code`, `CreatedAt`, `Description`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000031', 'items.view', '2026-08-18 00:00:00.000000', 'View items, brands and UOMs', TRUE);
SELECT ROW_COUNT();
INSERT INTO `Permissions` (`Id`, `Code`, `CreatedAt`, `Description`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000032', 'items.create', '2026-08-18 00:00:00.000000', 'Create items, brands and UOMs', TRUE);
SELECT ROW_COUNT();
INSERT INTO `Permissions` (`Id`, `Code`, `CreatedAt`, `Description`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000033', 'items.update', '2026-08-18 00:00:00.000000', 'Update items, brands and UOMs', TRUE);
SELECT ROW_COUNT();
INSERT INTO `Permissions` (`Id`, `Code`, `CreatedAt`, `Description`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000034', 'items.delete', '2026-08-18 00:00:00.000000', 'Delete items, brands and UOMs', TRUE);
SELECT ROW_COUNT();
INSERT INTO `Permissions` (`Id`, `Code`, `CreatedAt`, `Description`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000035', 'categories.view', '2026-08-18 00:00:00.000000', 'View categories', TRUE);
SELECT ROW_COUNT();
INSERT INTO `Permissions` (`Id`, `Code`, `CreatedAt`, `Description`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000036', 'categories.create', '2026-08-18 00:00:00.000000', 'Create categories and subcategories', TRUE);
SELECT ROW_COUNT();
INSERT INTO `Permissions` (`Id`, `Code`, `CreatedAt`, `Description`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000037', 'categories.update', '2026-08-18 00:00:00.000000', 'Update categories and subcategories', TRUE);
SELECT ROW_COUNT();
INSERT INTO `Permissions` (`Id`, `Code`, `CreatedAt`, `Description`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000038', 'categories.delete', '2026-08-18 00:00:00.000000', 'Delete categories and subcategories', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000031', '10000000-0000-0000-0000-000000000001', '2026-08-18 00:00:00.000000', '7596f703-c6ab-316c-7c80-3f4e3daa174e', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000035', '10000000-0000-0000-0000-000000000001', '2026-08-18 00:00:00.000000', 'aa120c36-488e-34ba-e10a-2ac45e46e119', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000031', '10000000-0000-0000-0000-000000000002', '2026-08-18 00:00:00.000000', 'b7dee5e3-f329-95a5-75c1-d02c3e40e229', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000035', '10000000-0000-0000-0000-000000000002', '2026-08-18 00:00:00.000000', 'e5506b30-527d-08f9-6caa-8a45d3d02999', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000031', '10000000-0000-0000-0000-000000000003', '2026-08-18 00:00:00.000000', '7780d277-9597-84ff-1a1a-f63fc6b4d3cb', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000032', '10000000-0000-0000-0000-000000000003', '2026-08-18 00:00:00.000000', '8083c659-9506-0bd9-b793-3d6148ce9192', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000033', '10000000-0000-0000-0000-000000000003', '2026-08-18 00:00:00.000000', 'd49f086e-30f0-6ce8-ae96-4395d5e04472', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000035', '10000000-0000-0000-0000-000000000003', '2026-08-18 00:00:00.000000', '1e7fe1b6-5150-4b99-38ed-088d9495cfd8', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000031', '10000000-0000-0000-0000-000000000004', '2026-08-18 00:00:00.000000', 'a701da28-aa75-3c1d-abdb-4317404f8df6', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000032', '10000000-0000-0000-0000-000000000004', '2026-08-18 00:00:00.000000', '2c025310-765f-7d07-ab9a-50524c13d1d3', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000033', '10000000-0000-0000-0000-000000000004', '2026-08-18 00:00:00.000000', '7c392a13-1a57-0d85-dffa-2fb9e892abe9', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000034', '10000000-0000-0000-0000-000000000004', '2026-08-18 00:00:00.000000', '97708eed-6ce4-7710-abde-01d00c0c879b', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000035', '10000000-0000-0000-0000-000000000004', '2026-08-18 00:00:00.000000', '9afe507e-191e-bdee-0c5a-cfbcbf1efb3e', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000036', '10000000-0000-0000-0000-000000000004', '2026-08-18 00:00:00.000000', '9c278616-4289-96fb-944d-b8f1495ccb00', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000037', '10000000-0000-0000-0000-000000000004', '2026-08-18 00:00:00.000000', 'd1dcef02-d4c3-caba-372f-dab82011e9c1', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000038', '10000000-0000-0000-0000-000000000004', '2026-08-18 00:00:00.000000', '9cc36229-dfba-49b9-1d6e-8929546e894d', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000031', '10000000-0000-0000-0000-000000000005', '2026-08-18 00:00:00.000000', '69f93a29-1df1-7b20-9d5c-94c264f8c4b8', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000032', '10000000-0000-0000-0000-000000000005', '2026-08-18 00:00:00.000000', '070813f7-4053-2889-0d14-d0d5834208c2', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000033', '10000000-0000-0000-0000-000000000005', '2026-08-18 00:00:00.000000', '0efa7259-aadb-0019-4be5-38d3f8f4cd54', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000034', '10000000-0000-0000-0000-000000000005', '2026-08-18 00:00:00.000000', '9e60e7d7-8bd6-c1c7-4bf1-4de2c9dcb91c', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000035', '10000000-0000-0000-0000-000000000005', '2026-08-18 00:00:00.000000', '5df82318-bc01-1947-09ef-60508e02b257', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000036', '10000000-0000-0000-0000-000000000005', '2026-08-18 00:00:00.000000', '34f478f4-6c96-4b31-b68a-28c7942c0789', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000037', '10000000-0000-0000-0000-000000000005', '2026-08-18 00:00:00.000000', '69598e0e-b7ad-477b-c587-7abf6189162d', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000038', '10000000-0000-0000-0000-000000000005', '2026-08-18 00:00:00.000000', 'b36fb2d4-43df-44f8-ff51-de6d88644c94', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000031', '10000000-0000-0000-0000-000000000006', '2026-08-18 00:00:00.000000', 'bd209f90-6671-8ea0-eb20-aa151faec6a0', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000032', '10000000-0000-0000-0000-000000000006', '2026-08-18 00:00:00.000000', '4cc32686-8391-0214-5747-db14f53c5b6f', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000033', '10000000-0000-0000-0000-000000000006', '2026-08-18 00:00:00.000000', '26c6d86a-835d-6f0d-3730-66a46ea26b11', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000034', '10000000-0000-0000-0000-000000000006', '2026-08-18 00:00:00.000000', 'e8039d9c-a286-fb74-5abb-b5c6c67f243a', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000035', '10000000-0000-0000-0000-000000000006', '2026-08-18 00:00:00.000000', 'd9e038cb-bcff-cce1-5d34-b4e63a9ce694', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000036', '10000000-0000-0000-0000-000000000006', '2026-08-18 00:00:00.000000', '343e71f0-da6b-2489-5571-f8d7c6378ee7', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000037', '10000000-0000-0000-0000-000000000006', '2026-08-18 00:00:00.000000', 'ac894328-a935-8e6e-8427-6c0f36ac75da', TRUE);
SELECT ROW_COUNT();
INSERT INTO `RolePermissions` (`FK_PermissionId`, `FK_RoleId`, `CreatedAt`, `Id`, `IsActive`)
VALUES ('20000000-0000-0000-0000-000000000038', '10000000-0000-0000-0000-000000000006', '2026-08-18 00:00:00.000000', '1d6273d4-db6a-d70e-4b5f-98cb634d668a', TRUE);
SELECT ROW_COUNT();
CREATE INDEX `IX_Items_FK_BaseUomId` ON `Items` (`FK_BaseUomId`);
CREATE INDEX `IX_Items_FK_BrandId` ON `Items` (`FK_BrandId`);
CREATE INDEX `IX_Items_FK_CategoryId` ON `Items` (`FK_CategoryId`);
CREATE INDEX `IX_Items_FK_SubCategoryId` ON `Items` (`FK_SubCategoryId`);
CREATE INDEX `IX_SubCategories_FK_CategoryId` ON `SubCategories` (`FK_CategoryId`);
CREATE INDEX `IX_UomConversions_FK_FromUomId` ON `UomConversions` (`FK_FromUomId`);
CREATE INDEX `IX_UomConversions_FK_ItemId` ON `UomConversions` (`FK_ItemId`);
CREATE INDEX `IX_UomConversions_FK_ToUomId` ON `UomConversions` (`FK_ToUomId`);
ALTER TABLE `Items` ADD CONSTRAINT `FK_Items_Brands_FK_BrandId` FOREIGN KEY (`FK_BrandId`) REFERENCES `Brands` (`Id`);
ALTER TABLE `Items` ADD CONSTRAINT `FK_Items_Categories_FK_CategoryId` FOREIGN KEY (`FK_CategoryId`) REFERENCES `Categories` (`Id`) ON DELETE CASCADE;
ALTER TABLE `Items` ADD CONSTRAINT `FK_Items_SubCategories_FK_SubCategoryId` FOREIGN KEY (`FK_SubCategoryId`) REFERENCES `SubCategories` (`Id`);
ALTER TABLE `Items` ADD CONSTRAINT `FK_Items_Uoms_FK_BaseUomId` FOREIGN KEY (`FK_BaseUomId`) REFERENCES `Uoms` (`Id`) ON DELETE CASCADE;
INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`)
VALUES ('20260819070136_AddItemCatalog', '10.0.9');
COMMIT;