item mng with sync
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class Brand : Template
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int? ExternalErpId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class Category : Template
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int? ExternalErpId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -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; } = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public enum SyncSource { Manual, Erp }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class Uom : Template
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int? ExternalErpId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user