Harden paid registration flow and add unit tests
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using PowderCoating.Application.DTOs.Quote;
|
||||
using PowderCoating.Application.Services;
|
||||
using PowderCoating.Core.Entities;
|
||||
using PowderCoating.Core.Interfaces;
|
||||
using Xunit;
|
||||
|
||||
namespace PowderCoating.UnitTests;
|
||||
|
||||
public class PricingCalculationServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task CalculateCoatPriceAsync_CustomPowder_ChargesFullOrderQuantity()
|
||||
{
|
||||
var costs = CreateOperatingCosts();
|
||||
var unitOfWork = CreateUnitOfWorkMock(costs);
|
||||
var tenantContext = new Mock<ITenantContext>();
|
||||
tenantContext.Setup(x => x.UseMetricSystemAsync()).ReturnsAsync(false);
|
||||
|
||||
var service = new PricingCalculationService(
|
||||
unitOfWork.Object,
|
||||
Mock.Of<ILogger<PricingCalculationService>>(),
|
||||
new MeasurementConversionService(),
|
||||
tenantContext.Object);
|
||||
|
||||
var coat = new CreateQuoteItemCoatDto
|
||||
{
|
||||
CoatName = "Custom Red",
|
||||
PowderCostPerLb = 10m,
|
||||
PowderToOrder = 3m,
|
||||
CoverageSqFtPerLb = 30m,
|
||||
TransferEfficiency = 65m
|
||||
};
|
||||
|
||||
var result = await service.CalculateCoatPriceAsync(
|
||||
coat,
|
||||
itemSurfaceAreaSqFt: 5m,
|
||||
quantity: 2m,
|
||||
coatIndex: 0,
|
||||
estimatedMinutesBase: 15,
|
||||
companyId: 1);
|
||||
|
||||
Assert.Equal(30m, result.CoatMaterialCost);
|
||||
Assert.Equal(30m, result.CoatLaborCost);
|
||||
Assert.Equal(60m, result.CoatTotalCost);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CalculateQuoteItemPriceAsync_LaborItem_UsesStandardLaborRate()
|
||||
{
|
||||
var costs = CreateOperatingCosts();
|
||||
costs.StandardLaborRate = 80m;
|
||||
|
||||
var unitOfWork = CreateUnitOfWorkMock(costs);
|
||||
var tenantContext = new Mock<ITenantContext>();
|
||||
tenantContext.Setup(x => x.UseMetricSystemAsync()).ReturnsAsync(false);
|
||||
|
||||
var service = new PricingCalculationService(
|
||||
unitOfWork.Object,
|
||||
Mock.Of<ILogger<PricingCalculationService>>(),
|
||||
new MeasurementConversionService(),
|
||||
tenantContext.Object);
|
||||
|
||||
var item = new CreateQuoteItemDto
|
||||
{
|
||||
Description = "Shop labor",
|
||||
IsLaborItem = true,
|
||||
Quantity = 2.5m
|
||||
};
|
||||
|
||||
var result = await service.CalculateQuoteItemPriceAsync(item, companyId: 1);
|
||||
|
||||
Assert.Equal(0m, result.MaterialCost);
|
||||
Assert.Equal(200m, result.LaborCost);
|
||||
Assert.Equal(80m, result.UnitPrice);
|
||||
Assert.Equal(200m, result.TotalPrice);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CalculateQuoteItemPriceAsync_AiItem_UsesManualUnitPriceWithoutAdditionalCosts()
|
||||
{
|
||||
var unitOfWork = CreateUnitOfWorkMock(CreateOperatingCosts());
|
||||
var tenantContext = new Mock<ITenantContext>();
|
||||
tenantContext.Setup(x => x.UseMetricSystemAsync()).ReturnsAsync(false);
|
||||
|
||||
var service = new PricingCalculationService(
|
||||
unitOfWork.Object,
|
||||
Mock.Of<ILogger<PricingCalculationService>>(),
|
||||
new MeasurementConversionService(),
|
||||
tenantContext.Object);
|
||||
|
||||
var item = new CreateQuoteItemDto
|
||||
{
|
||||
Description = "AI wheel estimate",
|
||||
IsAiItem = true,
|
||||
ManualUnitPrice = 123m,
|
||||
Quantity = 2m
|
||||
};
|
||||
|
||||
var result = await service.CalculateQuoteItemPriceAsync(item, companyId: 1);
|
||||
|
||||
Assert.Equal(0m, result.MaterialCost);
|
||||
Assert.Equal(0m, result.LaborCost);
|
||||
Assert.Equal(123m, result.UnitPrice);
|
||||
Assert.Equal(246m, result.TotalPrice);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CalculateQuoteTotalsAsync_AppliesTierDiscount_QuoteDiscount_RushFee_AndTax()
|
||||
{
|
||||
var costs = CreateOperatingCosts();
|
||||
costs.StandardLaborRate = 100m;
|
||||
costs.ShopSuppliesRate = 10m;
|
||||
costs.RushChargeType = "Percentage";
|
||||
costs.RushChargePercentage = 20m;
|
||||
costs.TaxPercent = 5m;
|
||||
costs.OvenOperatingCostPerHour = 0m;
|
||||
costs.MonthlyRent = 0m;
|
||||
costs.MonthlyUtilities = 0m;
|
||||
|
||||
var customerRepo = new Mock<IRepository<Customer>>();
|
||||
customerRepo
|
||||
.Setup(x => x.FindAsync(It.IsAny<System.Linq.Expressions.Expression<Func<Customer, bool>>>(), false, It.IsAny<System.Linq.Expressions.Expression<Func<Customer, object>>[]>()))
|
||||
.ReturnsAsync(new[]
|
||||
{
|
||||
new Customer { Id = 1, CompanyId = 1, PricingTierId = 10 }
|
||||
});
|
||||
|
||||
var pricingTierRepo = new Mock<IRepository<PricingTier>>();
|
||||
pricingTierRepo
|
||||
.Setup(x => x.GetByIdAsync(10, false, It.IsAny<System.Linq.Expressions.Expression<Func<PricingTier, object>>[]>()))
|
||||
.ReturnsAsync(new PricingTier { Id = 10, CompanyId = 1, DiscountPercent = 10m });
|
||||
|
||||
var unitOfWork = CreateUnitOfWorkMock(costs);
|
||||
unitOfWork.SetupGet(x => x.Customers).Returns(customerRepo.Object);
|
||||
unitOfWork.SetupGet(x => x.PricingTiers).Returns(pricingTierRepo.Object);
|
||||
|
||||
var tenantContext = new Mock<ITenantContext>();
|
||||
tenantContext.Setup(x => x.UseMetricSystemAsync()).ReturnsAsync(false);
|
||||
|
||||
var service = new PricingCalculationService(
|
||||
unitOfWork.Object,
|
||||
Mock.Of<ILogger<PricingCalculationService>>(),
|
||||
new MeasurementConversionService(),
|
||||
tenantContext.Object);
|
||||
|
||||
var items = new List<CreateQuoteItemDto>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Description = "Labor item",
|
||||
IsLaborItem = true,
|
||||
Quantity = 2m
|
||||
}
|
||||
};
|
||||
|
||||
var result = await service.CalculateQuoteTotalsAsync(
|
||||
items,
|
||||
companyId: 1,
|
||||
customerId: 1,
|
||||
discountType: "FixedAmount",
|
||||
discountValue: 5m,
|
||||
isRushJob: true);
|
||||
|
||||
Assert.Equal(200m, result.ItemsSubtotal);
|
||||
Assert.Equal(20m, result.ShopSuppliesAmount);
|
||||
Assert.Equal(220m, result.SubtotalBeforeDiscount);
|
||||
Assert.Equal(22m, result.PricingTierDiscountAmount);
|
||||
Assert.Equal(5m, result.QuoteDiscountAmount);
|
||||
Assert.Equal(193m, result.SubtotalAfterDiscount);
|
||||
Assert.Equal(38.6m, result.RushFee);
|
||||
Assert.Equal(11.58m, result.TaxAmount);
|
||||
Assert.Equal(243.18m, result.Total);
|
||||
}
|
||||
|
||||
private static Mock<IUnitOfWork> CreateUnitOfWorkMock(CompanyOperatingCosts costs)
|
||||
{
|
||||
var unitOfWork = new Mock<IUnitOfWork>();
|
||||
|
||||
var companyOperatingCostsRepo = new Mock<IRepository<CompanyOperatingCosts>>();
|
||||
companyOperatingCostsRepo
|
||||
.Setup(x => x.FindAsync(It.IsAny<System.Linq.Expressions.Expression<Func<CompanyOperatingCosts, bool>>>(), false, It.IsAny<System.Linq.Expressions.Expression<Func<CompanyOperatingCosts, object>>[]>()))
|
||||
.ReturnsAsync(new[] { costs });
|
||||
|
||||
var inventoryRepo = new Mock<IRepository<InventoryItem>>();
|
||||
inventoryRepo
|
||||
.Setup(x => x.GetByIdAsync(It.IsAny<int>(), false, It.IsAny<System.Linq.Expressions.Expression<Func<InventoryItem, object>>[]>()))
|
||||
.ReturnsAsync((InventoryItem?)null);
|
||||
|
||||
var catalogRepo = new Mock<IRepository<CatalogItem>>();
|
||||
catalogRepo
|
||||
.Setup(x => x.GetByIdAsync(It.IsAny<int>(), false, It.IsAny<System.Linq.Expressions.Expression<Func<CatalogItem, object>>[]>()))
|
||||
.ReturnsAsync((CatalogItem?)null);
|
||||
|
||||
var customerRepo = new Mock<IRepository<Customer>>();
|
||||
customerRepo
|
||||
.Setup(x => x.FindAsync(It.IsAny<System.Linq.Expressions.Expression<Func<Customer, bool>>>(), false, It.IsAny<System.Linq.Expressions.Expression<Func<Customer, object>>[]>()))
|
||||
.ReturnsAsync(Array.Empty<Customer>());
|
||||
|
||||
var pricingTierRepo = new Mock<IRepository<PricingTier>>();
|
||||
pricingTierRepo
|
||||
.Setup(x => x.GetByIdAsync(It.IsAny<int>(), false, It.IsAny<System.Linq.Expressions.Expression<Func<PricingTier, object>>[]>()))
|
||||
.ReturnsAsync((PricingTier?)null);
|
||||
|
||||
unitOfWork.SetupGet(x => x.CompanyOperatingCosts).Returns(companyOperatingCostsRepo.Object);
|
||||
unitOfWork.SetupGet(x => x.InventoryItems).Returns(inventoryRepo.Object);
|
||||
unitOfWork.SetupGet(x => x.CatalogItems).Returns(catalogRepo.Object);
|
||||
unitOfWork.SetupGet(x => x.Customers).Returns(customerRepo.Object);
|
||||
unitOfWork.SetupGet(x => x.PricingTiers).Returns(pricingTierRepo.Object);
|
||||
|
||||
return unitOfWork;
|
||||
}
|
||||
|
||||
private static CompanyOperatingCosts CreateOperatingCosts()
|
||||
{
|
||||
return new CompanyOperatingCosts
|
||||
{
|
||||
Id = 1,
|
||||
CompanyId = 1,
|
||||
StandardLaborRate = 60m,
|
||||
AdditionalCoatLaborPercent = 50m,
|
||||
OvenOperatingCostPerHour = 25m,
|
||||
SandblasterCostPerHour = 20m,
|
||||
CoatingBoothCostPerHour = 10m,
|
||||
PowderCoatingCostPerSqFt = 1m,
|
||||
PricingMode = PowderCoating.Core.Enums.PricingMode.MarkupOnMaterial,
|
||||
GeneralMarkupPercentage = 20m,
|
||||
TargetMarginPercent = 40m,
|
||||
TaxPercent = 5m,
|
||||
ShopSuppliesRate = 10m,
|
||||
DefaultOvenCycleMinutes = 60,
|
||||
RushChargeType = "Percentage",
|
||||
RushChargePercentage = 15m,
|
||||
RushChargeFixedAmount = 50m,
|
||||
ShopMinimumCharge = 0m,
|
||||
ComplexitySimplePercent = 0m,
|
||||
ComplexityModeratePercent = 5m,
|
||||
ComplexityComplexPercent = 15m,
|
||||
ComplexityExtremePercent = 25m,
|
||||
MonthlyBillableHours = 160
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user