Add pricing and supplier master data
This commit is contained in:
@@ -30,6 +30,9 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
public DbSet<StockBalance> StockBalances { get; set; }
|
||||
public DbSet<StockMovement> StockMovements { get; set; }
|
||||
public DbSet<StoreKycDocument> StoreKycDocuments { get; set; }
|
||||
public DbSet<PriceList> PriceLists { get; set; }
|
||||
public DbSet<PriceListItem> PriceListItems { get; set; }
|
||||
public DbSet<Supplier> Suppliers { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
@@ -58,6 +61,10 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
.HasIndex(sb => new { sb.FK_ItemId, sb.LocationId })
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<PriceListItem>()
|
||||
.HasIndex(pli => new { pli.FK_PriceListId, pli.FK_ItemId })
|
||||
.IsUnique();
|
||||
|
||||
Seed.RoleAndPermissionSeed.Apply(modelBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ namespace Distribution.Features.Item;
|
||||
public interface IItemRepository
|
||||
{
|
||||
Task<Models.Item?> GetByIdAsync(Guid id);
|
||||
Task<Models.Item?> GetBySkuAsync(string sku);
|
||||
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);
|
||||
|
||||
@@ -15,6 +15,9 @@ public class ItemRepository(AppDBContext context) : IItemRepository
|
||||
.Include(i => i.Conversions).ThenInclude(c => c.ToUom)
|
||||
.FirstOrDefaultAsync(i => i.Id == id);
|
||||
|
||||
public async Task<Models.Item?> GetBySkuAsync(string sku) =>
|
||||
await context.Items.FirstOrDefaultAsync(i => i.Sku == sku && i.IsActive);
|
||||
|
||||
public async Task<List<Models.Item>> GetAllAsync(Guid? categoryId = null, Guid? subCategoryId = null, Guid? brandId = null)
|
||||
{
|
||||
var query = context.Items
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
namespace Distribution.Features.Order;
|
||||
|
||||
public record OrderItemRequest(Guid ItemId, int Quantity);
|
||||
public record OrderItemRequest(Guid ItemId, int Quantity, decimal? UnitPrice = null);
|
||||
|
||||
public record CreateOrderRequest(Guid StoreId, DateTime OrderDate, decimal TotalAmount, List<OrderItemRequest> Items);
|
||||
|
||||
public record UpdateOrderRequest(DateTime OrderDate, decimal TotalAmount);
|
||||
|
||||
public record OrderItemResponse(Guid ItemId, int Quantity);
|
||||
public record OrderItemResponse(Guid ItemId, int Quantity, decimal UnitPrice);
|
||||
|
||||
public record OrderResponse(
|
||||
Guid Id,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Distribution.Features.Item;
|
||||
using Distribution.Features.PriceList;
|
||||
using Distribution.Features.Store;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.UoW;
|
||||
@@ -9,6 +11,8 @@ namespace Distribution.Features.Order;
|
||||
public class OrderService(
|
||||
IOrderRepository repo,
|
||||
IStoreRepository storeRepo,
|
||||
IItemRepository itemRepo,
|
||||
PriceListService priceListService,
|
||||
IUnitOfWork uow,
|
||||
ICurrentUserService currentUser,
|
||||
IScopeAccessService scopeAccess,
|
||||
@@ -18,7 +22,7 @@ public class OrderService(
|
||||
|
||||
private static OrderResponse ToResponse(Models.Order o, List<Models.OrderItem> items) =>
|
||||
new(o.Id, o.OrderDate, o.FK_StoreId, o.TotalAmount,
|
||||
items.Select(i => new OrderItemResponse(i.FK_ItemId, i.Quantity)).ToList(),
|
||||
items.Select(i => new OrderItemResponse(i.FK_ItemId, i.Quantity, i.UnitPrice)).ToList(),
|
||||
o.IsActive, o.CreatedAt);
|
||||
|
||||
private bool HasGlobalScope => currentUser.RoleLevel >= GlobalScopeRoleLevel;
|
||||
@@ -29,6 +33,29 @@ public class OrderService(
|
||||
return await scopeAccess.HasStoreScopeAsync(currentUser.UserId.Value, storeId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// applyPricingAndDiscounts (price-list half, 14_pricing_and_suppliers.md): resolves the
|
||||
/// region-scoped active price list, then global, then Item.SalePrice, and validates any
|
||||
/// caller-supplied override falls within [MinPrice ?? Price, Price]. Returns null if no price
|
||||
/// can be resolved, or if the requested price is outside the allowed band.
|
||||
/// </summary>
|
||||
private async Task<decimal?> ResolveUnitPriceAsync(Guid itemId, decimal? requestedUnitPrice, Models.Store store, DateTime orderDate)
|
||||
{
|
||||
var item = await itemRepo.GetByIdAsync(itemId);
|
||||
if (item is null) return null;
|
||||
var regionId = store.Route?.FK_RegionId;
|
||||
var resolution = await priceListService.ResolvePriceAsync(itemId, regionId, item.SalePrice, orderDate);
|
||||
if (resolution is null) return null;
|
||||
|
||||
if (!requestedUnitPrice.HasValue) return resolution.Price;
|
||||
|
||||
var min = resolution.MinPrice ?? resolution.Price;
|
||||
var max = resolution.Price;
|
||||
if (requestedUnitPrice.Value < min || requestedUnitPrice.Value > max) return null;
|
||||
|
||||
return requestedUnitPrice.Value;
|
||||
}
|
||||
|
||||
public async Task<List<OrderResponse>> GetAllAsync()
|
||||
{
|
||||
var orders = (await repo.GetAllAsync()).Where(o => o.IsActive).ToList();
|
||||
@@ -72,7 +99,7 @@ public class OrderService(
|
||||
{
|
||||
if (!await HasStoreScopeAsync(request.StoreId)) return null;
|
||||
|
||||
var store = await storeRepo.GetByIdAsync(request.StoreId);
|
||||
var store = await storeRepo.GetByIdWithRegionAsync(request.StoreId);
|
||||
if (store is null || !store.IsActive) return null;
|
||||
if (store.KycStatus != KycStatus.Verified)
|
||||
{
|
||||
@@ -86,12 +113,20 @@ public class OrderService(
|
||||
OrderDate = request.OrderDate,
|
||||
TotalAmount = request.TotalAmount
|
||||
};
|
||||
var orderItems = request.Items.Select(i => new Models.OrderItem
|
||||
var orderItems = new List<Models.OrderItem>();
|
||||
foreach (var i in request.Items)
|
||||
{
|
||||
var unitPrice = await ResolveUnitPriceAsync(i.ItemId, i.UnitPrice, store, request.OrderDate);
|
||||
if (unitPrice is null) return null;
|
||||
|
||||
orderItems.Add(new Models.OrderItem
|
||||
{
|
||||
FK_OrderId = order.Id,
|
||||
FK_ItemId = i.ItemId,
|
||||
Quantity = i.Quantity
|
||||
}).ToList();
|
||||
Quantity = i.Quantity,
|
||||
UnitPrice = unitPrice.Value
|
||||
});
|
||||
}
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace Distribution.Features.PriceList;
|
||||
|
||||
public interface IPriceListRepository
|
||||
{
|
||||
Task<Models.PriceList?> GetByIdAsync(Guid id);
|
||||
Task<List<Models.PriceList>> GetAllAsync(Guid? regionId = null);
|
||||
Task AddAsync(Models.PriceList entity);
|
||||
void Update(Models.PriceList entity);
|
||||
void Remove(Models.PriceList entity);
|
||||
|
||||
Task<Models.PriceListItem?> GetItemAsync(Guid priceListId, Guid itemId);
|
||||
Task AddItemAsync(Models.PriceListItem entity);
|
||||
void UpdateItem(Models.PriceListItem entity);
|
||||
void RemoveItem(Models.PriceListItem entity);
|
||||
|
||||
/// <summary>
|
||||
/// Active PriceListItem rows for the given item and region (region null = global lists only),
|
||||
/// whose parent PriceList is active and effective on orderDate. Most recently created first.
|
||||
/// </summary>
|
||||
Task<List<Models.PriceListItem>> GetActiveItemsAsync(Guid itemId, Guid? regionId, DateTime orderDate);
|
||||
|
||||
/// <summary>
|
||||
/// Active PriceListItem rows for itemId under any PriceList with the given RegionId (null = global),
|
||||
/// whose date range overlaps [effectiveFrom, effectiveTo], excluding a given PriceList.
|
||||
/// </summary>
|
||||
Task<bool> HasOverlapAsync(Guid itemId, Guid? regionId, DateTime effectiveFrom, DateTime effectiveTo, Guid? excludePriceListId);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.PriceList;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/price-lists")]
|
||||
[Authorize]
|
||||
public class PriceListController(PriceListService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll([FromQuery] Guid? regionId) =>
|
||||
Ok(await service.GetAllAsync(regionId));
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(CreatePriceListRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdatePriceListRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Conflict(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
return deleted ? NoContent() : NotFound();
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/items/{sku}")]
|
||||
public async Task<IActionResult> UpsertItem(Guid id, string sku, UpsertPriceListItemRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.UpsertItemAsync(id, sku, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Conflict(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}/items/{sku}")]
|
||||
public async Task<IActionResult> RemoveItem(Guid id, string sku, CancellationToken ct)
|
||||
{
|
||||
var result = await service.RemoveItemAsync(id, sku, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace Distribution.Features.PriceList;
|
||||
|
||||
public record CreatePriceListRequest(string Name, Guid? RegionId, DateTime EffectiveFrom, DateTime EffectiveTo);
|
||||
|
||||
public record UpdatePriceListRequest(string Name, Guid? RegionId, DateTime EffectiveFrom, DateTime EffectiveTo);
|
||||
|
||||
public record UpsertPriceListItemRequest(decimal Price, decimal? MinPrice = null);
|
||||
|
||||
public record PriceListItemResponse(Guid Id, Guid ItemId, string Sku, string ItemName, decimal Price, decimal? MinPrice);
|
||||
|
||||
/// <summary>
|
||||
/// A resolved price with its negotiable floor. MinPrice null = no negotiation, Price is fixed.
|
||||
/// </summary>
|
||||
public record PriceResolution(decimal Price, decimal? MinPrice);
|
||||
|
||||
public record PriceListResponse(
|
||||
Guid Id,
|
||||
string Name,
|
||||
Guid? RegionId,
|
||||
string? RegionName,
|
||||
DateTime EffectiveFrom,
|
||||
DateTime EffectiveTo,
|
||||
bool IsActive,
|
||||
DateTime CreatedAt,
|
||||
List<PriceListItemResponse> Items);
|
||||
@@ -0,0 +1,76 @@
|
||||
using Distribution.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.PriceList;
|
||||
|
||||
public class PriceListRepository(AppDBContext context) : IPriceListRepository
|
||||
{
|
||||
public async Task<Models.PriceList?> GetByIdAsync(Guid id) =>
|
||||
await context.PriceLists
|
||||
.Include(pl => pl.Region)
|
||||
.Include(pl => pl.Items.Where(i => i.IsActive)).ThenInclude(i => i.Item)
|
||||
.FirstOrDefaultAsync(pl => pl.Id == id);
|
||||
|
||||
public async Task<List<Models.PriceList>> GetAllAsync(Guid? regionId = null)
|
||||
{
|
||||
var query = context.PriceLists
|
||||
.Include(pl => pl.Region)
|
||||
.Where(pl => pl.IsActive);
|
||||
|
||||
if (regionId.HasValue) query = query.Where(pl => pl.FK_RegionId == regionId);
|
||||
|
||||
return await query.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task AddAsync(Models.PriceList entity) =>
|
||||
await context.PriceLists.AddAsync(entity);
|
||||
|
||||
public void Update(Models.PriceList entity) =>
|
||||
context.PriceLists.Update(entity);
|
||||
|
||||
public void Remove(Models.PriceList entity)
|
||||
{
|
||||
entity.IsActive = false;
|
||||
context.PriceLists.Update(entity);
|
||||
}
|
||||
|
||||
public async Task<Models.PriceListItem?> GetItemAsync(Guid priceListId, Guid itemId) =>
|
||||
await context.PriceListItems
|
||||
.FirstOrDefaultAsync(i => i.FK_PriceListId == priceListId && i.FK_ItemId == itemId && i.IsActive);
|
||||
|
||||
public async Task AddItemAsync(Models.PriceListItem entity) =>
|
||||
await context.PriceListItems.AddAsync(entity);
|
||||
|
||||
public void UpdateItem(Models.PriceListItem entity) =>
|
||||
context.PriceListItems.Update(entity);
|
||||
|
||||
public void RemoveItem(Models.PriceListItem entity)
|
||||
{
|
||||
entity.IsActive = false;
|
||||
context.PriceListItems.Update(entity);
|
||||
}
|
||||
|
||||
public async Task<List<Models.PriceListItem>> GetActiveItemsAsync(Guid itemId, Guid? regionId, DateTime orderDate) =>
|
||||
await context.PriceListItems
|
||||
.Include(i => i.PriceList)
|
||||
.Where(i => i.IsActive
|
||||
&& i.FK_ItemId == itemId
|
||||
&& i.PriceList.IsActive
|
||||
&& i.PriceList.FK_RegionId == regionId
|
||||
&& i.PriceList.EffectiveFrom <= orderDate
|
||||
&& i.PriceList.EffectiveTo >= orderDate)
|
||||
.OrderByDescending(i => i.PriceList.CreatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<bool> HasOverlapAsync(Guid itemId, Guid? regionId, DateTime effectiveFrom, DateTime effectiveTo, Guid? excludePriceListId) =>
|
||||
await context.PriceListItems
|
||||
.Include(i => i.PriceList)
|
||||
.Where(i => i.IsActive
|
||||
&& i.FK_ItemId == itemId
|
||||
&& i.PriceList.IsActive
|
||||
&& i.PriceList.FK_RegionId == regionId
|
||||
&& (excludePriceListId == null || i.FK_PriceListId != excludePriceListId)
|
||||
&& i.PriceList.EffectiveFrom <= effectiveTo
|
||||
&& i.PriceList.EffectiveTo >= effectiveFrom)
|
||||
.AnyAsync();
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
using Distribution.Features.Item;
|
||||
using Distribution.Infra.UoW;
|
||||
|
||||
namespace Distribution.Features.PriceList;
|
||||
|
||||
public class PriceListService(IPriceListRepository repo, IItemRepository itemRepo, IUnitOfWork uow)
|
||||
{
|
||||
private static PriceListItemResponse MapItem(Models.PriceListItem i) =>
|
||||
new(i.Id, i.FK_ItemId, i.Item.Sku, i.Item.Name, i.Price, i.MinPrice);
|
||||
|
||||
private static PriceListResponse MapDetail(Models.PriceList pl) =>
|
||||
new(pl.Id, pl.Name, pl.FK_RegionId, pl.Region?.Name,
|
||||
pl.EffectiveFrom, pl.EffectiveTo, pl.IsActive, pl.CreatedAt,
|
||||
pl.Items.Where(i => i.IsActive).Select(MapItem).ToList());
|
||||
|
||||
public async Task<List<PriceListResponse>> GetAllAsync(Guid? regionId = null)
|
||||
{
|
||||
var lists = await repo.GetAllAsync(regionId);
|
||||
return lists.Select(pl => new PriceListResponse(
|
||||
pl.Id, pl.Name, pl.FK_RegionId, pl.Region?.Name,
|
||||
pl.EffectiveFrom, pl.EffectiveTo, pl.IsActive, pl.CreatedAt, [])).ToList();
|
||||
}
|
||||
|
||||
public async Task<PriceListResponse?> GetByIdAsync(Guid id)
|
||||
{
|
||||
var pl = await repo.GetByIdAsync(id);
|
||||
if (pl is null || !pl.IsActive) return null;
|
||||
return MapDetail(pl);
|
||||
}
|
||||
|
||||
public async Task<PriceListResponse> CreateAsync(CreatePriceListRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var priceList = new Models.PriceList
|
||||
{
|
||||
Name = request.Name,
|
||||
FK_RegionId = request.RegionId,
|
||||
EffectiveFrom = request.EffectiveFrom,
|
||||
EffectiveTo = request.EffectiveTo
|
||||
};
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try { await repo.AddAsync(priceList); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return (await GetByIdAsync(priceList.Id))!;
|
||||
}
|
||||
|
||||
public async Task<PriceListResponse?> UpdateAsync(Guid id, UpdatePriceListRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var priceList = await repo.GetByIdAsync(id);
|
||||
if (priceList is null || !priceList.IsActive) return null;
|
||||
|
||||
foreach (var item in priceList.Items.Where(i => i.IsActive))
|
||||
{
|
||||
if (await repo.HasOverlapAsync(item.FK_ItemId, request.RegionId, request.EffectiveFrom, request.EffectiveTo, id))
|
||||
throw new InvalidOperationException(
|
||||
$"Overlapping price list already exists for item '{item.Item.Sku}' in the same region for the requested date range.");
|
||||
}
|
||||
|
||||
priceList.Name = request.Name;
|
||||
priceList.FK_RegionId = request.RegionId;
|
||||
priceList.EffectiveFrom = request.EffectiveFrom;
|
||||
priceList.EffectiveTo = request.EffectiveTo;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try { repo.Update(priceList); 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 priceList = await repo.GetByIdAsync(id);
|
||||
if (priceList is null || !priceList.IsActive) return false;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
foreach (var item in priceList.Items.Where(i => i.IsActive))
|
||||
repo.RemoveItem(item);
|
||||
repo.Remove(priceList);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<PriceListResponse?> UpsertItemAsync(Guid priceListId, string sku, UpsertPriceListItemRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var priceList = await repo.GetByIdAsync(priceListId);
|
||||
if (priceList is null || !priceList.IsActive) return null;
|
||||
|
||||
var item = await itemRepo.GetBySkuAsync(sku);
|
||||
if (item is null) return null;
|
||||
|
||||
if (request.MinPrice.HasValue && request.MinPrice.Value > request.Price)
|
||||
throw new InvalidOperationException(
|
||||
$"MinPrice ({request.MinPrice}) cannot exceed Price ({request.Price}) for item '{sku}'.");
|
||||
|
||||
if (await repo.HasOverlapAsync(item.Id, priceList.FK_RegionId, priceList.EffectiveFrom, priceList.EffectiveTo, priceListId))
|
||||
throw new InvalidOperationException(
|
||||
$"Overlapping price list already exists for item '{sku}' in the same region for this date range.");
|
||||
|
||||
var existing = await repo.GetItemAsync(priceListId, item.Id);
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
if (existing is null)
|
||||
{
|
||||
await repo.AddItemAsync(new Models.PriceListItem
|
||||
{
|
||||
FK_PriceListId = priceListId,
|
||||
FK_ItemId = item.Id,
|
||||
Price = request.Price,
|
||||
MinPrice = request.MinPrice
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
existing.Price = request.Price;
|
||||
existing.MinPrice = request.MinPrice;
|
||||
repo.UpdateItem(existing);
|
||||
}
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return (await GetByIdAsync(priceListId))!;
|
||||
}
|
||||
|
||||
public async Task<PriceListResponse?> RemoveItemAsync(Guid priceListId, string sku, CancellationToken ct = default)
|
||||
{
|
||||
var priceList = await repo.GetByIdAsync(priceListId);
|
||||
if (priceList is null || !priceList.IsActive) return null;
|
||||
|
||||
var item = await itemRepo.GetBySkuAsync(sku);
|
||||
if (item is null) return null;
|
||||
|
||||
var existing = await repo.GetItemAsync(priceListId, item.Id);
|
||||
if (existing is null) return null;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try { repo.RemoveItem(existing); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return (await GetByIdAsync(priceListId))!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// applyPricingAndDiscounts (price-list half): region-scoped active price list -> global
|
||||
/// active price list -> Item.SalePrice -> null if none apply. The returned PriceResolution
|
||||
/// carries MinPrice so the caller can validate a negotiated UnitPrice against the band;
|
||||
/// the Item.SalePrice fallback has no MinPrice (fixed, non-negotiable).
|
||||
/// </summary>
|
||||
public async Task<PriceResolution?> ResolvePriceAsync(Guid itemId, Guid? regionId, decimal? itemSalePrice, DateTime orderDate)
|
||||
{
|
||||
if (regionId.HasValue)
|
||||
{
|
||||
var regionMatches = await repo.GetActiveItemsAsync(itemId, regionId, orderDate);
|
||||
if (regionMatches.Count > 0) return new PriceResolution(regionMatches[0].Price, regionMatches[0].MinPrice);
|
||||
}
|
||||
|
||||
var globalMatches = await repo.GetActiveItemsAsync(itemId, null, orderDate);
|
||||
if (globalMatches.Count > 0) return new PriceResolution(globalMatches[0].Price, globalMatches[0].MinPrice);
|
||||
|
||||
return itemSalePrice.HasValue ? new PriceResolution(itemSalePrice.Value, null) : null;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ namespace Distribution.Features.Store;
|
||||
public interface IStoreRepository
|
||||
{
|
||||
Task<Models.Store?> GetByIdAsync(Guid id);
|
||||
Task<Models.Store?> GetByIdWithRegionAsync(Guid id);
|
||||
Task<List<Models.Store>> GetAllAsync();
|
||||
Task AddAsync(Models.Store entity);
|
||||
void Update(Models.Store entity);
|
||||
|
||||
@@ -8,6 +8,11 @@ public class StoreRepository(AppDBContext context) : IStoreRepository
|
||||
public async Task<Models.Store?> GetByIdAsync(Guid id) =>
|
||||
await context.Stores.FindAsync(id);
|
||||
|
||||
public async Task<Models.Store?> GetByIdWithRegionAsync(Guid id) =>
|
||||
await context.Stores
|
||||
.Include(s => s.Route).ThenInclude(r => r.Region)
|
||||
.FirstOrDefaultAsync(s => s.Id == id);
|
||||
|
||||
public async Task<List<Models.Store>> GetAllAsync() =>
|
||||
await context.Stores.ToListAsync();
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Distribution.Features.Supplier;
|
||||
|
||||
public interface ISupplierRepository
|
||||
{
|
||||
Task<Models.Supplier?> GetByIdAsync(Guid id);
|
||||
Task<List<Models.Supplier>> GetAllAsync();
|
||||
Task AddAsync(Models.Supplier entity);
|
||||
void Update(Models.Supplier entity);
|
||||
void Remove(Models.Supplier entity);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Supplier;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/suppliers")]
|
||||
[Authorize]
|
||||
public class SupplierController(SupplierService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(CreateSupplierRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateSupplierRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
return deleted ? NoContent() : NotFound();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Distribution.Features.Supplier;
|
||||
|
||||
public record CreateSupplierRequest(string Name, string? ContactInfo);
|
||||
|
||||
public record UpdateSupplierRequest(string Name, string? ContactInfo);
|
||||
|
||||
public record SupplierResponse(
|
||||
Guid Id,
|
||||
string Name,
|
||||
string? ContactInfo,
|
||||
decimal? PerformanceScore,
|
||||
bool IsActive,
|
||||
DateTime CreatedAt);
|
||||
@@ -0,0 +1,25 @@
|
||||
using Distribution.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.Supplier;
|
||||
|
||||
public class SupplierRepository(AppDBContext context) : ISupplierRepository
|
||||
{
|
||||
public async Task<Models.Supplier?> GetByIdAsync(Guid id) =>
|
||||
await context.Suppliers.FindAsync(id);
|
||||
|
||||
public async Task<List<Models.Supplier>> GetAllAsync() =>
|
||||
await context.Suppliers.Where(s => s.IsActive).ToListAsync();
|
||||
|
||||
public async Task AddAsync(Models.Supplier entity) =>
|
||||
await context.Suppliers.AddAsync(entity);
|
||||
|
||||
public void Update(Models.Supplier entity) =>
|
||||
context.Suppliers.Update(entity);
|
||||
|
||||
public void Remove(Models.Supplier entity)
|
||||
{
|
||||
entity.IsActive = false;
|
||||
context.Suppliers.Update(entity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using Distribution.Infra.UoW;
|
||||
|
||||
namespace Distribution.Features.Supplier;
|
||||
|
||||
public class SupplierService(ISupplierRepository repo, IUnitOfWork uow)
|
||||
{
|
||||
private static SupplierResponse Map(Models.Supplier s) =>
|
||||
new(s.Id, s.Name, s.ContactInfo, s.PerformanceScore, s.IsActive, s.CreatedAt);
|
||||
|
||||
public async Task<List<SupplierResponse>> GetAllAsync() =>
|
||||
(await repo.GetAllAsync()).Select(Map).ToList();
|
||||
|
||||
public async Task<SupplierResponse?> GetByIdAsync(Guid id)
|
||||
{
|
||||
var supplier = await repo.GetByIdAsync(id);
|
||||
if (supplier is null || !supplier.IsActive) return null;
|
||||
return Map(supplier);
|
||||
}
|
||||
|
||||
public async Task<SupplierResponse> CreateAsync(CreateSupplierRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var supplier = new Models.Supplier
|
||||
{
|
||||
Name = request.Name,
|
||||
ContactInfo = request.ContactInfo
|
||||
};
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try { await repo.AddAsync(supplier); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return Map(supplier);
|
||||
}
|
||||
|
||||
public async Task<SupplierResponse?> UpdateAsync(Guid id, UpdateSupplierRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var supplier = await repo.GetByIdAsync(id);
|
||||
if (supplier is null || !supplier.IsActive) return null;
|
||||
|
||||
supplier.Name = request.Name;
|
||||
supplier.ContactInfo = request.ContactInfo;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try { repo.Update(supplier); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return Map(supplier);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(Guid id, CancellationToken ct = default)
|
||||
{
|
||||
var supplier = await repo.GetByIdAsync(id);
|
||||
if (supplier is null || !supplier.IsActive) return false;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try { repo.Remove(supplier); 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,116 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPricingAndSuppliers : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PriceLists",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
Name = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
FK_RegionId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
|
||||
EffectiveFrom = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
EffectiveTo = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PriceLists", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_PriceLists_Regions_FK_RegionId",
|
||||
column: x => x.FK_RegionId,
|
||||
principalTable: "Regions",
|
||||
principalColumn: "Id");
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Suppliers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
Name = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
ContactInfo = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
PerformanceScore = table.Column<decimal>(type: "decimal(65,30)", 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_Suppliers", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PriceListItems",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
FK_PriceListId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
FK_ItemId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
Price = table.Column<decimal>(type: "decimal(65,30)", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PriceListItems", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_PriceListItems_Items_FK_ItemId",
|
||||
column: x => x.FK_ItemId,
|
||||
principalTable: "Items",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_PriceListItems_PriceLists_FK_PriceListId",
|
||||
column: x => x.FK_PriceListId,
|
||||
principalTable: "PriceLists",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PriceListItems_FK_ItemId",
|
||||
table: "PriceListItems",
|
||||
column: "FK_ItemId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PriceListItems_FK_PriceListId_FK_ItemId",
|
||||
table: "PriceListItems",
|
||||
columns: new[] { "FK_PriceListId", "FK_ItemId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PriceLists_FK_RegionId",
|
||||
table: "PriceLists",
|
||||
column: "FK_RegionId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "PriceListItems");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Suppliers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PriceLists");
|
||||
}
|
||||
}
|
||||
}
|
||||
+2616
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPriceListItemMinPrice : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "MinPrice",
|
||||
table: "PriceListItems",
|
||||
type: "decimal(65,30)",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MinPrice",
|
||||
table: "PriceListItems");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -571,6 +571,72 @@ namespace Distribution.Migrations
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.PriceList", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateTime>("EffectiveFrom")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateTime>("EffectiveTo")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid?>("FK_RegionId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FK_RegionId");
|
||||
|
||||
b.ToTable("PriceLists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.PriceListItem", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("FK_ItemId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("FK_PriceListId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<decimal?>("MinPrice")
|
||||
.HasColumnType("decimal(65,30)");
|
||||
|
||||
b.Property<decimal>("Price")
|
||||
.HasColumnType("decimal(65,30)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FK_ItemId");
|
||||
|
||||
b.HasIndex("FK_PriceListId", "FK_ItemId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("PriceListItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.Region", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -1973,6 +2039,33 @@ namespace Distribution.Migrations
|
||||
b.ToTable("SubCategories");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.Supplier", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("ContactInfo")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<decimal?>("PerformanceScore")
|
||||
.HasColumnType("decimal(65,30)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Suppliers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.Uom", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -2281,6 +2374,34 @@ namespace Distribution.Migrations
|
||||
b.Navigation("Order");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.PriceList", b =>
|
||||
{
|
||||
b.HasOne("Distribution.Models.Region", "Region")
|
||||
.WithMany()
|
||||
.HasForeignKey("FK_RegionId");
|
||||
|
||||
b.Navigation("Region");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.PriceListItem", b =>
|
||||
{
|
||||
b.HasOne("Distribution.Models.Item", "Item")
|
||||
.WithMany()
|
||||
.HasForeignKey("FK_ItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Distribution.Models.PriceList", "PriceList")
|
||||
.WithMany("Items")
|
||||
.HasForeignKey("FK_PriceListId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Item");
|
||||
|
||||
b.Navigation("PriceList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.RolePermission", b =>
|
||||
{
|
||||
b.HasOne("Distribution.Models.Permission", "Permission")
|
||||
@@ -2481,6 +2602,11 @@ namespace Distribution.Migrations
|
||||
{
|
||||
b.Navigation("Conversions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.PriceList", b =>
|
||||
{
|
||||
b.Navigation("Items");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class PriceList : Template
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public Guid? FK_RegionId { get; set; }
|
||||
[ForeignKey("FK_RegionId")]
|
||||
public Region? Region { get; set; }
|
||||
|
||||
public DateTime EffectiveFrom { get; set; }
|
||||
public DateTime EffectiveTo { get; set; }
|
||||
|
||||
public ICollection<PriceListItem> Items { get; set; } = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class PriceListItem : Template
|
||||
{
|
||||
public Guid FK_PriceListId { get; set; }
|
||||
[ForeignKey("FK_PriceListId")]
|
||||
public PriceList PriceList { get; set; }
|
||||
|
||||
public Guid FK_ItemId { get; set; }
|
||||
[ForeignKey("FK_ItemId")]
|
||||
public Item Item { get; set; }
|
||||
|
||||
public decimal Price { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Floor price for negotiation. Null = no negotiation allowed, Price is fixed.
|
||||
/// Must be less than or equal to Price when set.
|
||||
/// </summary>
|
||||
public decimal? MinPrice { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class Supplier : Template
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string? ContactInfo { get; set; }
|
||||
public decimal? PerformanceScore { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,13 @@ using Distribution.Features.Brand;
|
||||
using Distribution.Features.Category;
|
||||
using Distribution.Features.Item;
|
||||
using Distribution.Features.Order;
|
||||
using Distribution.Features.PriceList;
|
||||
using Distribution.Features.Region;
|
||||
using Distribution.Features.Role;
|
||||
using Distribution.Features.Route;
|
||||
using Distribution.Features.Store;
|
||||
using Distribution.Features.Store.Kyc;
|
||||
using Distribution.Features.Supplier;
|
||||
using Distribution.Features.Sync;
|
||||
using Distribution.Features.Uom;
|
||||
using Distribution.Features.User;
|
||||
@@ -48,6 +50,8 @@ builder.Services.AddScoped<IUomRepository, UomRepository>();
|
||||
builder.Services.AddScoped<IItemRepository, ItemRepository>();
|
||||
builder.Services.AddScoped<IStockRepository, StockRepository>();
|
||||
builder.Services.AddScoped<IStoreKycDocumentRepository, StoreKycDocumentRepository>();
|
||||
builder.Services.AddScoped<IPriceListRepository, PriceListRepository>();
|
||||
builder.Services.AddScoped<ISupplierRepository, SupplierRepository>();
|
||||
|
||||
builder.Services.AddScoped<RegionService>();
|
||||
builder.Services.AddScoped<RouteService>();
|
||||
@@ -65,6 +69,8 @@ builder.Services.AddScoped<ItemService>();
|
||||
builder.Services.AddScoped<SyncService>();
|
||||
builder.Services.AddScoped<StockService>();
|
||||
builder.Services.AddScoped<StoreKycDocumentService>();
|
||||
builder.Services.AddScoped<PriceListService>();
|
||||
builder.Services.AddScoped<SupplierService>();
|
||||
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddScoped<ICurrentUserService, CurrentUserService>();
|
||||
|
||||
Reference in New Issue
Block a user