Files
ERP_Distribution/Distribution/Features/Item/ItemService.cs

186 lines
8.2 KiB
C#

using Distribution.Infra.UoW;
namespace Distribution.Features.Item;
public class ItemService(IItemRepository repo, IUnitOfWork uow, SysConfig.SysConfigService sysConfig)
{
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.ReorderLevel,
i.TracksSerialNumbers, i.TracksBatches, i.TracksExpiryDate,
i.HasWarranty, i.WarrantyPeriodDays, i.AllowCustomWarrantyPeriod,
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.ReorderLevel,
i.TracksSerialNumbers, i.TracksBatches, i.TracksExpiryDate,
i.HasWarranty, i.WarrantyPeriodDays, i.AllowCustomWarrantyPeriod,
i.SyncSource, i.IsActive, i.CreatedAt,
i.Conversions.Select(MapConversion).ToList());
/// <summary>
/// Rejects per-item tracking flags that are off system-wide, and warranty-period values that
/// don't make sense given HasWarranty/AllowCustomWarrantyPeriod.
/// </summary>
private async Task ValidateTrackingFlagsAsync(bool tracksSerialNumbers, bool tracksBatches,
bool tracksExpiryDate, bool hasWarranty, int? warrantyPeriodDays, bool allowCustomWarrantyPeriod)
{
if (tracksSerialNumbers && !await sysConfig.GetSerialNumberTrackingEnabledAsync())
throw new InvalidOperationException("Serial number tracking is disabled system-wide.");
if (tracksBatches && !await sysConfig.GetBatchTrackingEnabledAsync())
throw new InvalidOperationException("Batch tracking is disabled system-wide.");
if (tracksExpiryDate && !await sysConfig.GetExpiryDateTrackingEnabledAsync())
throw new InvalidOperationException("Expiry date tracking is disabled system-wide.");
if (hasWarranty && !await sysConfig.GetWarrantyTrackingEnabledAsync())
throw new InvalidOperationException("Warranty tracking is disabled system-wide.");
if (allowCustomWarrantyPeriod && !await sysConfig.GetCustomWarrantyOverrideEnabledAsync())
throw new InvalidOperationException("Custom warranty period override is disabled system-wide.");
if (!hasWarranty && (warrantyPeriodDays is not null || allowCustomWarrantyPeriod))
throw new InvalidOperationException("Warranty period/override can only be set when HasWarranty is true.");
if (warrantyPeriodDays is not null && !allowCustomWarrantyPeriod)
throw new InvalidOperationException("WarrantyPeriodDays can only be set when AllowCustomWarrantyPeriod is true.");
if (warrantyPeriodDays is < 0)
throw new InvalidOperationException("WarrantyPeriodDays cannot be negative.");
}
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) 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.");
await ValidateTrackingFlagsAsync(request.TracksSerialNumbers, request.TracksBatches,
request.TracksExpiryDate, request.HasWarranty, request.WarrantyPeriodDays, request.AllowCustomWarrantyPeriod);
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,
ReorderLevel = request.ReorderLevel,
TracksSerialNumbers = request.TracksSerialNumbers,
TracksBatches = request.TracksBatches,
TracksExpiryDate = request.TracksExpiryDate,
HasWarranty = request.HasWarranty,
WarrantyPeriodDays = request.WarrantyPeriodDays,
AllowCustomWarrantyPeriod = request.AllowCustomWarrantyPeriod,
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) return null;
if (item.SyncSource == Models.SyncSource.Erp) return null;
await ValidateTrackingFlagsAsync(request.TracksSerialNumbers, request.TracksBatches,
request.TracksExpiryDate, request.HasWarranty, request.WarrantyPeriodDays, request.AllowCustomWarrantyPeriod);
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;
item.ReorderLevel = request.ReorderLevel;
item.TracksSerialNumbers = request.TracksSerialNumbers;
item.TracksBatches = request.TracksBatches;
item.TracksExpiryDate = request.TracksExpiryDate;
item.HasWarranty = request.HasWarranty;
item.WarrantyPeriodDays = request.WarrantyPeriodDays;
item.AllowCustomWarrantyPeriod = request.AllowCustomWarrantyPeriod;
item.IsActive = request.IsActive;
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;
}
}