item mng with sync

This commit is contained in:
Kalana B. Thilakarathna
2026-08-19 12:49:04 +05:30
parent 45f374133f
commit 397af8fd59
42 changed files with 5191 additions and 16 deletions
@@ -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);
}
}