Add Phase B: Inventory COGS auto-posting to GL on JobUsage transactions
When powder is consumed via a job (JobsController) or scan (InventoryController.LogUsage), debit the item's CogsAccountId and credit its InventoryAccountId for the cost of the quantity consumed (using AverageCost if available, else UnitCost). No-op when either GL account is not configured on the InventoryItem. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,7 @@ public class InventoryController : Controller
|
|||||||
private readonly IInventoryAiLookupService _aiLookupService;
|
private readonly IInventoryAiLookupService _aiLookupService;
|
||||||
private readonly ISubscriptionService _subscriptionService;
|
private readonly ISubscriptionService _subscriptionService;
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
|
private readonly IAccountBalanceService _accountBalanceService;
|
||||||
|
|
||||||
public InventoryController(
|
public InventoryController(
|
||||||
IUnitOfWork unitOfWork,
|
IUnitOfWork unitOfWork,
|
||||||
@@ -39,7 +40,8 @@ public class InventoryController : Controller
|
|||||||
IMeasurementConversionService measurementService,
|
IMeasurementConversionService measurementService,
|
||||||
IInventoryAiLookupService aiLookupService,
|
IInventoryAiLookupService aiLookupService,
|
||||||
ISubscriptionService subscriptionService,
|
ISubscriptionService subscriptionService,
|
||||||
UserManager<ApplicationUser> userManager)
|
UserManager<ApplicationUser> userManager,
|
||||||
|
IAccountBalanceService accountBalanceService)
|
||||||
{
|
{
|
||||||
_unitOfWork = unitOfWork;
|
_unitOfWork = unitOfWork;
|
||||||
_mapper = mapper;
|
_mapper = mapper;
|
||||||
@@ -49,6 +51,7 @@ public class InventoryController : Controller
|
|||||||
_aiLookupService = aiLookupService;
|
_aiLookupService = aiLookupService;
|
||||||
_subscriptionService = subscriptionService;
|
_subscriptionService = subscriptionService;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
|
_accountBalanceService = accountBalanceService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1552,6 +1555,14 @@ public class InventoryController : Controller
|
|||||||
await _unitOfWork.InventoryTransactions.AddAsync(txn);
|
await _unitOfWork.InventoryTransactions.AddAsync(txn);
|
||||||
await _unitOfWork.SaveChangesAsync();
|
await _unitOfWork.SaveChangesAsync();
|
||||||
|
|
||||||
|
// GL: DR COGS, CR Inventory Asset — no-op if accounts not configured on the item
|
||||||
|
if (item.CogsAccountId.HasValue && item.InventoryAccountId.HasValue)
|
||||||
|
{
|
||||||
|
var cost = quantity * (item.AverageCost > 0 ? item.AverageCost : item.UnitCost);
|
||||||
|
await _accountBalanceService.DebitAsync(item.CogsAccountId, cost);
|
||||||
|
await _accountBalanceService.CreditAsync(item.InventoryAccountId, cost);
|
||||||
|
}
|
||||||
|
|
||||||
// PowderUsageLog requires a specific JobItem + Coat FK — scan-based logging
|
// PowderUsageLog requires a specific JobItem + Coat FK — scan-based logging
|
||||||
// doesn't have that context, so we rely on the InventoryTransaction alone
|
// doesn't have that context, so we rely on the InventoryTransaction alone
|
||||||
// for the audit trail. Coat-level PowderUsageLogs are created by the job workflow.
|
// for the audit trail. Coat-level PowderUsageLogs are created by the job workflow.
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ public class JobsController : Controller
|
|||||||
private readonly IJobItemAssemblyService _jobItemAssemblyService;
|
private readonly IJobItemAssemblyService _jobItemAssemblyService;
|
||||||
private readonly IHubContext<NotificationHub> _hub;
|
private readonly IHubContext<NotificationHub> _hub;
|
||||||
private readonly IHubContext<ShopHub> _shopHub;
|
private readonly IHubContext<ShopHub> _shopHub;
|
||||||
|
private readonly IAccountBalanceService _accountBalanceService;
|
||||||
|
|
||||||
public JobsController(
|
public JobsController(
|
||||||
IUnitOfWork unitOfWork,
|
IUnitOfWork unitOfWork,
|
||||||
@@ -52,7 +53,8 @@ public class JobsController : Controller
|
|||||||
IPricingCalculationService pricingService,
|
IPricingCalculationService pricingService,
|
||||||
IJobItemAssemblyService jobItemAssemblyService,
|
IJobItemAssemblyService jobItemAssemblyService,
|
||||||
IHubContext<NotificationHub> hub,
|
IHubContext<NotificationHub> hub,
|
||||||
IHubContext<ShopHub> shopHub)
|
IHubContext<ShopHub> shopHub,
|
||||||
|
IAccountBalanceService accountBalanceService)
|
||||||
{
|
{
|
||||||
_unitOfWork = unitOfWork;
|
_unitOfWork = unitOfWork;
|
||||||
_mapper = mapper;
|
_mapper = mapper;
|
||||||
@@ -68,6 +70,7 @@ public class JobsController : Controller
|
|||||||
_jobItemAssemblyService = jobItemAssemblyService;
|
_jobItemAssemblyService = jobItemAssemblyService;
|
||||||
_hub = hub;
|
_hub = hub;
|
||||||
_shopHub = shopHub;
|
_shopHub = shopHub;
|
||||||
|
_accountBalanceService = accountBalanceService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -2726,6 +2729,14 @@ public class JobsController : Controller
|
|||||||
inventoryItem.QuantityOnHand -= deductNow;
|
inventoryItem.QuantityOnHand -= deductNow;
|
||||||
await _unitOfWork.InventoryItems.UpdateAsync(inventoryItem);
|
await _unitOfWork.InventoryItems.UpdateAsync(inventoryItem);
|
||||||
|
|
||||||
|
// GL: DR COGS, CR Inventory Asset (accrual) — no-op if accounts not configured
|
||||||
|
if (inventoryItem.CogsAccountId.HasValue && inventoryItem.InventoryAccountId.HasValue)
|
||||||
|
{
|
||||||
|
var cost = deductNow * (inventoryItem.AverageCost > 0 ? inventoryItem.AverageCost : inventoryItem.UnitCost);
|
||||||
|
await _accountBalanceService.DebitAsync(inventoryItem.CogsAccountId, cost);
|
||||||
|
await _accountBalanceService.CreditAsync(inventoryItem.InventoryAccountId, cost);
|
||||||
|
}
|
||||||
|
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
"Deducted {Lbs} lbs (net of pre-logged) of {Item} from inventory for Job {JobNumber}. New quantity: {NewQty}",
|
"Deducted {Lbs} lbs (net of pre-logged) of {Item} from inventory for Job {JobNumber}. New quantity: {NewQty}",
|
||||||
deductNow, inventoryItem.Name, job.JobNumber, inventoryItem.QuantityOnHand);
|
deductNow, inventoryItem.Name, job.JobNumber, inventoryItem.QuantityOnHand);
|
||||||
|
|||||||
@@ -138,7 +138,8 @@ public class QuoteAndReworkControllerFlowTests
|
|||||||
Mock.Of<IPricingCalculationService>(),
|
Mock.Of<IPricingCalculationService>(),
|
||||||
new JobItemAssemblyService(),
|
new JobItemAssemblyService(),
|
||||||
Mock.Of<IHubContext<NotificationHub>>(),
|
Mock.Of<IHubContext<NotificationHub>>(),
|
||||||
Mock.Of<IHubContext<ShopHub>>());
|
Mock.Of<IHubContext<ShopHub>>(),
|
||||||
|
Mock.Of<IAccountBalanceService>());
|
||||||
|
|
||||||
var result = await controller.AddReworkRecord(new CreateReworkRecordDto
|
var result = await controller.AddReworkRecord(new CreateReworkRecordDto
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user