Add migration to include AccruedExpensesCode in gl_account_config table
- Introduced a new column 'AccruedExpensesCode' of type varchar(20) to the 'gl_account_config' table. - Added SQL command to update existing records with a default value of '2300' for the 'AccruedExpensesCode' where it is currently null.
This commit is contained in:
@@ -2,6 +2,7 @@ using ERPCore.Domain;
|
||||
using ERPCore.Domain.Entities;
|
||||
using ERPCore.Domain.Enums;
|
||||
using ERPCore.Dtos.Approvals;
|
||||
using ERPCore.Dtos.Common;
|
||||
using ERPCore.Dtos.Notifications;
|
||||
using ERPCore.Dtos.Products;
|
||||
using ERPCore.Dtos.Stock;
|
||||
@@ -28,7 +29,8 @@ public class DualApprovalTests
|
||||
// Requester, two approvers, and a clerk who can approve but isn't a manager.
|
||||
private const int Requester = 10, ApproverA = 11, ApproverB = 12, Clerk = 13;
|
||||
private const int ApproverRoleId = 2, ClerkRoleId = 3;
|
||||
private const int ItemId = 1, WarehouseId = 1, ReasonId = 1;
|
||||
// ReasonId is an ordinary adjustment reason (VAR); WastageReasonId is a wastage one (DMG), which has its own approval switch.
|
||||
private const int ItemId = 1, WarehouseId = 1, ReasonId = 1, WastageReasonId = 2;
|
||||
|
||||
// --- Stock adjustments ------------------------------------------------------------------
|
||||
|
||||
@@ -226,11 +228,95 @@ public class DualApprovalTests
|
||||
public void RequiredLevels_FollowTheSettings(int configured, double threshold, double measure, int expected)
|
||||
=> Assert.Equal(expected, ApprovalService.Levels(configured, (decimal)threshold, (decimal)measure));
|
||||
|
||||
// --- Wastage: its own approval switch ------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public async Task Wastage_WithTheSwitchOff_ReducesStockAtOnce_EvenWhenAdjustmentsNeedTwoApprovals()
|
||||
{
|
||||
var h = await Harness.CreateAsync(stockLevels: 2, wastageLevels: 0);
|
||||
|
||||
var dto = await h.Stock.CreateAsync(Request(-3, WastageReasonId));
|
||||
|
||||
Assert.Equal(AdjustmentStatus.Posted, dto.Status);
|
||||
Assert.Equal(0, dto.RequiredApprovals);
|
||||
Assert.Single(h.Mutator.Calls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Wastage_WithTheSwitchOn_IsARequest_AndMovesNothingUntilApproved()
|
||||
{
|
||||
var h = await Harness.CreateAsync(stockLevels: 0, wastageLevels: 2);
|
||||
|
||||
var dto = await h.Stock.CreateAsync(Request(-3, WastageReasonId));
|
||||
|
||||
Assert.Equal(AdjustmentStatus.PendingApproval, dto.Status);
|
||||
Assert.Equal(2, dto.RequiredApprovals);
|
||||
Assert.Empty(h.Mutator.Calls);
|
||||
|
||||
h.User.Be(ApproverA);
|
||||
await h.Stock.ApproveAsync(dto.AdjustmentId, null);
|
||||
Assert.Empty(h.Mutator.Calls); // one of two: still nothing
|
||||
h.User.Be(ApproverB);
|
||||
var posted = await h.Stock.ApproveAsync(dto.AdjustmentId, null);
|
||||
Assert.Equal(AdjustmentStatus.Posted, posted.Status);
|
||||
Assert.Single(h.Mutator.Calls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TheWastageSwitch_DoesNotLoosenOrdinaryAdjustments_AndTheirLevelsDoNotTouchWastage()
|
||||
{
|
||||
var h = await Harness.CreateAsync(stockLevels: 2, wastageLevels: 0);
|
||||
|
||||
// An ordinary adjustment (not a wastage reason) still needs two approvals.
|
||||
var ordinary = await h.Stock.CreateAsync(Request(-3));
|
||||
Assert.Equal(AdjustmentStatus.PendingApproval, ordinary.Status);
|
||||
|
||||
// A stock GAIN with a wastage reason is not wastage, so it follows the adjustment levels too.
|
||||
var gain = await h.Stock.CreateAsync(Request(+3, WastageReasonId));
|
||||
Assert.Equal(AdjustmentStatus.PendingApproval, gain.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WastageList_ReturnsOnlyAdjustmentsWithAWastageReason()
|
||||
{
|
||||
var h = await Harness.CreateAsync(stockLevels: 0, wastageLevels: 0);
|
||||
await h.Stock.CreateAsync(Request(-1, WastageReasonId));
|
||||
await h.Stock.CreateAsync(Request(-2));
|
||||
|
||||
var all = await h.Stock.ListAsync(new PageQuery { Page = 1, PageSize = 50 }, null, null, null);
|
||||
var wastage = await h.Stock.ListAsync(new PageQuery { Page = 1, PageSize = 50 }, null, null, null, null, CancellationToken.None, wastageOnly: true);
|
||||
|
||||
Assert.Equal(2, all.Items.Count);
|
||||
var only = Assert.Single(wastage.Items);
|
||||
Assert.Equal(WastageReasonId, only.ReasonCodeId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WastageList_CanBeNarrowedToOneItem()
|
||||
{
|
||||
var h = await Harness.CreateAsync(stockLevels: 0, wastageLevels: 0);
|
||||
await h.Stock.CreateAsync(Request(-1, WastageReasonId));
|
||||
|
||||
var forItem = await h.Stock.ListAsync(new PageQuery { Page = 1, PageSize = 50 }, null, null, null, null, CancellationToken.None, wastageOnly: true, itemId: ItemId);
|
||||
var otherItem = await h.Stock.ListAsync(new PageQuery { Page = 1, PageSize = 50 }, null, null, null, null, CancellationToken.None, wastageOnly: true, itemId: 999);
|
||||
|
||||
Assert.Single(forItem.Items);
|
||||
Assert.Empty(otherItem.Items);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WastageApproval_IsOffByDefault_AndTheSettingIsReadBack()
|
||||
{
|
||||
Assert.Equal(0, new ApprovalConfig().WastageLevels);
|
||||
Assert.True(ERPCore.Domain.WastageReasonCodes.IsWastage("dmg"));
|
||||
Assert.False(ERPCore.Domain.WastageReasonCodes.IsWastage("VAR"));
|
||||
}
|
||||
|
||||
// --- helpers ------------------------------------------------------------------------------
|
||||
|
||||
private static CreateAdjustmentRequest Request(decimal qty) => new()
|
||||
private static CreateAdjustmentRequest Request(decimal qty, int reasonId = ReasonId) => new()
|
||||
{
|
||||
WarehouseId = WarehouseId, BranchId = 1, ReasonCodeId = ReasonId,
|
||||
WarehouseId = WarehouseId, BranchId = 1, ReasonCodeId = reasonId,
|
||||
Lines = [new CreateAdjustmentLineInput { ItemId = ItemId, QtyDelta = qty }]
|
||||
};
|
||||
|
||||
@@ -250,7 +336,7 @@ public class DualApprovalTests
|
||||
public required AdjustmentService Stock { get; init; }
|
||||
public required PriceAdjustmentService Prices { get; init; }
|
||||
|
||||
public static async Task<Harness> CreateAsync(int stockLevels = 2, decimal stockThreshold = 0, string? level2RoleIds = null)
|
||||
public static async Task<Harness> CreateAsync(int stockLevels = 2, decimal stockThreshold = 0, string? level2RoleIds = null, int wastageLevels = 0)
|
||||
{
|
||||
var db = new ErpDbContext(
|
||||
new DbContextOptionsBuilder<ErpDbContext>().UseInMemoryDatabase(Guid.NewGuid().ToString("N"), Root).Options,
|
||||
@@ -279,11 +365,14 @@ public class DualApprovalTests
|
||||
db.Uoms.Add(new Uom { UomId = 1, Name = "pcs" });
|
||||
db.Items.Add(new Item { ItemId = ItemId, Sku = "SKU-1", Name = "Cola", BaseUomId = 1, CategoryId = 1,
|
||||
SellingMinPrice = 90m, SellingMaxPrice = 150m, SellingFlatPrice = 100m });
|
||||
db.ReasonCodes.Add(new ReasonCode { ReasonCodeId = ReasonId, Code = "DMG", Description = "Damage", Context = ReasonContext.Adjustment });
|
||||
db.ReasonCodes.AddRange(
|
||||
new ReasonCode { ReasonCodeId = ReasonId, Code = "VAR", Description = "Variance", Context = ReasonContext.Adjustment },
|
||||
new ReasonCode { ReasonCodeId = WastageReasonId, Code = "DMG", Description = "Damage", Context = ReasonContext.Adjustment });
|
||||
db.ApprovalConfig.Add(new ApprovalConfig
|
||||
{
|
||||
ApprovalConfigId = ApprovalConfig.SingletonId, StockAdjustmentLevels = stockLevels,
|
||||
StockAdjustmentLevel2ThresholdValue = stockThreshold, PriceAdjustmentLevels = 2, Level2RoleIds = level2RoleIds
|
||||
StockAdjustmentLevel2ThresholdValue = stockThreshold, PriceAdjustmentLevels = 2, Level2RoleIds = level2RoleIds,
|
||||
WastageLevels = wastageLevels
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user