item mng with sync
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user