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:
@@ -29,8 +29,8 @@ public sealed class StockAdjustmentsController : ApiControllerBase
|
||||
[ProducesResponseType(typeof(PagedResponse<AdjustmentSummaryDto>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedResponse<AdjustmentSummaryDto>>> List(
|
||||
[FromQuery] PageQuery query, [FromQuery] int? warehouseId, [FromQuery] int? branchId, [FromQuery] int? reasonCodeId,
|
||||
[FromQuery] AdjustmentStatus? status, CancellationToken ct)
|
||||
=> Ok(await _adjustments.ListAsync(query, warehouseId, branchId, reasonCodeId, status, ct));
|
||||
[FromQuery] AdjustmentStatus? status, CancellationToken ct, [FromQuery] bool wastage = false, [FromQuery] int? itemId = null)
|
||||
=> Ok(await _adjustments.ListAsync(query, warehouseId, branchId, reasonCodeId, status, ct, wastage, itemId));
|
||||
|
||||
/// <summary>Get one adjustment with its lines, approval trail and the ledger entries it posted.</summary>
|
||||
[HttpGet("{adjustmentId:int}")]
|
||||
|
||||
@@ -35,6 +35,10 @@ public class ApprovalConfig
|
||||
/// <summary>With 2 levels: an adjustment whose stock value is below this needs only one approval. 0 = always two.</summary>
|
||||
public decimal StockAdjustmentLevel2ThresholdValue { get; set; }
|
||||
|
||||
/// <summary>Approvals wastage (damage / theft / expiry write-offs) needs: 0 = none, stock reduces on save (default);
|
||||
/// 2 = two different approvers first. Separate from <see cref="StockAdjustmentLevels"/>, which governs every other adjustment.</summary>
|
||||
public int WastageLevels { get; set; }
|
||||
|
||||
public int PriceAdjustmentLevels { get; set; } = 2;
|
||||
/// <summary>With 2 levels: a change smaller than this percent (largest across its lines) needs only one approval. 0 = always two.</summary>
|
||||
public decimal PriceAdjustmentLevel2ThresholdPercent { get; set; }
|
||||
|
||||
@@ -60,6 +60,7 @@ public class GlAccountConfig
|
||||
|
||||
/// <summary>Cash takings at day-end (was <c>SalesDayEnd:GlCashAccountCode</c>).</summary>
|
||||
public string? CashAccountCode { get; set; }
|
||||
public string? AccruedExpensesCode { get; set; }
|
||||
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace ERPCore.Domain;
|
||||
|
||||
/// <summary>
|
||||
/// Which Adjustment reason codes make a stock adjustment "wastage" (damage, theft, expiry). Wastage is stored as a stock
|
||||
/// adjustment; this is what tells the two apart. Matches the frontend's <c>isWastageReasonCode</c> (lib/api/wastage.ts).
|
||||
/// </summary>
|
||||
public static class WastageReasonCodes
|
||||
{
|
||||
public static readonly IReadOnlyCollection<string> Codes = new[] { "DMG", "THEFT", "EXP" };
|
||||
|
||||
public static bool IsWastage(string? reasonCode) =>
|
||||
reasonCode is not null && Codes.Contains(reasonCode.Trim(), StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
@@ -10,7 +10,7 @@ public sealed record ApprovalStepDto(int Level, ApprovalDecision Decision, int D
|
||||
public sealed record ApprovalConfigDto(
|
||||
int StockAdjustmentLevels, decimal StockAdjustmentLevel2ThresholdValue,
|
||||
int PriceAdjustmentLevels, decimal PriceAdjustmentLevel2ThresholdPercent,
|
||||
IReadOnlyList<int> Level2RoleIds, DateTime? UpdatedAt);
|
||||
IReadOnlyList<int> Level2RoleIds, DateTime? UpdatedAt, int WastageLevels = 0);
|
||||
|
||||
public sealed class UpdateApprovalConfigRequest
|
||||
{
|
||||
@@ -20,6 +20,9 @@ public sealed class UpdateApprovalConfigRequest
|
||||
[Range(0, 1000)] public decimal PriceAdjustmentLevel2ThresholdPercent { get; set; }
|
||||
/// <summary>Roles allowed to give the second approval; empty = anyone with the Approve action.</summary>
|
||||
public List<int> Level2RoleIds { get; set; } = new();
|
||||
|
||||
/// <summary>0 = wastage posts on save, 2 = two approvals first. Null keeps the stored value (an older client that doesn't know the setting).</summary>
|
||||
[Range(0, 2)] public int? WastageLevels { get; set; }
|
||||
}
|
||||
|
||||
public sealed class ApproveRequest
|
||||
|
||||
@@ -47,4 +47,6 @@ public sealed class UpdateGlAccountConfigRequest
|
||||
[MaxLength(GlAccountConfig.CodeMaxLength)] public string? CogsAccountCode { get; set; }
|
||||
[MaxLength(GlAccountConfig.CodeMaxLength)] public string? InventoryLossAccountCode { get; set; }
|
||||
[MaxLength(GlAccountConfig.CodeMaxLength)] public string? CashAccountCode { get; set; }
|
||||
/// <summary>Optional so an older client that doesn't know this role can't blank it out: null keeps the stored code.</summary>
|
||||
[MaxLength(GlAccountConfig.CodeMaxLength)] public string? AccruedExpensesCode { get; set; }
|
||||
}
|
||||
|
||||
@@ -35,7 +35,8 @@ public sealed class GlAccountConfigConfiguration : IEntityTypeConfiguration<GlAc
|
||||
nameof(GlAccountConfig.InventoryGainAccountCode),
|
||||
nameof(GlAccountConfig.CogsAccountCode),
|
||||
nameof(GlAccountConfig.InventoryLossAccountCode),
|
||||
nameof(GlAccountConfig.CashAccountCode)
|
||||
nameof(GlAccountConfig.CashAccountCode),
|
||||
nameof(GlAccountConfig.AccruedExpensesCode)
|
||||
})
|
||||
{
|
||||
builder.Property(code).HasMaxLength(GlAccountConfig.CodeMaxLength);
|
||||
|
||||
@@ -304,7 +304,8 @@ public static class DataSeeder
|
||||
InventoryGainAccountCode = Legacy("Adjustment:GlGainAccountCode"),
|
||||
CogsAccountCode = Legacy("SalesDayEnd:GlCogsAccountCode"),
|
||||
InventoryLossAccountCode = Legacy("Adjustment:GlLossAccountCode"),
|
||||
CashAccountCode = Legacy("SalesDayEnd:GlCashAccountCode")
|
||||
CashAccountCode = Legacy("SalesDayEnd:GlCashAccountCode"),
|
||||
AccruedExpensesCode = Legacy("Expense:GlAccruedExpensesCode")
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
+11370
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ERPCore.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddWastageApprovalSetting : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "WastageLevels",
|
||||
table: "approval_config",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "approval_config",
|
||||
keyColumn: "ApprovalConfigId",
|
||||
keyValue: 1,
|
||||
column: "WastageLevels",
|
||||
value: 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "WastageLevels",
|
||||
table: "approval_config");
|
||||
}
|
||||
}
|
||||
}
|
||||
+11374
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,34 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ERPCore.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddAccruedExpensesGlRole : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "AccruedExpensesCode",
|
||||
table: "gl_account_config",
|
||||
type: "varchar(20)",
|
||||
maxLength: 20,
|
||||
nullable: true,
|
||||
collation: "utf8mb4_unicode_ci");
|
||||
|
||||
// Until now the accrued-expenses account lived only in appsettings (Expense:GlAccruedExpensesCode, "2300"); carry it
|
||||
// over so an existing deployment keeps working and the code now shows - and is checked - on Settings -> GL Accounts.
|
||||
migrationBuilder.Sql("UPDATE `gl_account_config` SET `AccruedExpensesCode` = '2300' WHERE `ConfigId` = 1 AND `AccruedExpensesCode` IS NULL;");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "AccruedExpensesCode",
|
||||
table: "gl_account_config");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,6 +86,9 @@ namespace ERPCore.Migrations
|
||||
b.Property<int?>("UpdatedBy")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("WastageLevels")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("ApprovalConfigId");
|
||||
|
||||
b.ToTable("approval_config", null, t =>
|
||||
@@ -101,7 +104,8 @@ namespace ERPCore.Migrations
|
||||
PriceAdjustmentLevels = 2,
|
||||
RowVersion = 1u,
|
||||
StockAdjustmentLevel2ThresholdValue = 0m,
|
||||
StockAdjustmentLevels = 2
|
||||
StockAdjustmentLevels = 2,
|
||||
WastageLevels = 0
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2252,6 +2256,10 @@ namespace ERPCore.Migrations
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)");
|
||||
|
||||
b.Property<string>("AccruedExpensesCode")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)");
|
||||
|
||||
b.Property<string>("CashAccountCode")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)");
|
||||
|
||||
@@ -112,9 +112,19 @@ public sealed class AdjustmentService : IAdjustmentService
|
||||
}
|
||||
|
||||
public async Task<PagedResponse<AdjustmentSummaryDto>> ListAsync(
|
||||
PageQuery query, int? warehouseId, int? branchId, int? reasonCodeId, AdjustmentStatus? status = null, CancellationToken ct = default)
|
||||
PageQuery query, int? warehouseId, int? branchId, int? reasonCodeId, AdjustmentStatus? status = null,
|
||||
CancellationToken ct = default, bool wastageOnly = false, int? itemId = null)
|
||||
{
|
||||
var q = _adjustments.Query().AsNoTracking();
|
||||
// Adjustments that touch this item (the product page's activity).
|
||||
if (itemId is not null) q = q.Where(a => a.Lines.Any(l => l.ItemId == itemId));
|
||||
if (wastageOnly)
|
||||
{
|
||||
// Wastage = an adjustment with a damage / theft / expiry reason code.
|
||||
var wastageCodes = WastageReasonCodes.Codes.Select(c => c.ToUpper()).ToList();
|
||||
var wastageReasonIds = _reasonCodes.Query().Where(r => wastageCodes.Contains(r.Code.ToUpper())).Select(r => r.ReasonCodeId);
|
||||
q = q.Where(a => wastageReasonIds.Contains(a.ReasonCodeId));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.Q))
|
||||
{
|
||||
@@ -189,7 +199,12 @@ public sealed class AdjustmentService : IAdjustmentService
|
||||
// Validated only: posting resolves them again and creates any new ones exactly once.
|
||||
DiscardUnsavedSerials();
|
||||
|
||||
var required = await _approvals.RequiredForStockAdjustmentAsync(value, ct);
|
||||
// Wastage (a write-off with a damage / theft / expiry reason) has its own switch; every other adjustment and a
|
||||
// stock-count variance keeps the stock-adjustment levels and threshold.
|
||||
var isWastage = WastageReasonCodes.IsWastage(reason.Code) && request.Lines.All(l => l.QtyDelta < 0);
|
||||
var required = isWastage
|
||||
? await _approvals.RequiredForWastageAsync(ct)
|
||||
: await _approvals.RequiredForStockAdjustmentAsync(value, ct);
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var (adjustment, refs) = await _uow.ExecuteInTransactionAsync(async token =>
|
||||
|
||||
@@ -55,6 +55,7 @@ public sealed class ApprovalService : IApprovalService
|
||||
|
||||
config.StockAdjustmentLevels = Math.Clamp(request.StockAdjustmentLevels, 0, 2);
|
||||
config.StockAdjustmentLevel2ThresholdValue = Math.Max(0m, request.StockAdjustmentLevel2ThresholdValue);
|
||||
if (request.WastageLevels is int wastageLevels) config.WastageLevels = Math.Clamp(wastageLevels, 0, 2);
|
||||
config.PriceAdjustmentLevels = Math.Clamp(request.PriceAdjustmentLevels, 0, 2);
|
||||
config.PriceAdjustmentLevel2ThresholdPercent = Math.Max(0m, request.PriceAdjustmentLevel2ThresholdPercent);
|
||||
var roleIds = request.Level2RoleIds.Where(id => id > 0).Distinct().ToList();
|
||||
@@ -79,6 +80,9 @@ public sealed class ApprovalService : IApprovalService
|
||||
return Levels(c.StockAdjustmentLevels, c.StockAdjustmentLevel2ThresholdValue, absoluteValue);
|
||||
}
|
||||
|
||||
public async Task<int> RequiredForWastageAsync(CancellationToken ct = default)
|
||||
=> Math.Clamp((await LoadConfigAsync(ct)).WastageLevels, 0, 2);
|
||||
|
||||
public async Task<int> RequiredForPriceAdjustmentAsync(decimal largestChangePercent, CancellationToken ct = default)
|
||||
{
|
||||
var c = await LoadConfigAsync(ct);
|
||||
@@ -188,5 +192,5 @@ public sealed class ApprovalService : IApprovalService
|
||||
private static ApprovalConfigDto Map(ApprovalConfig c) => new(
|
||||
c.StockAdjustmentLevels, c.StockAdjustmentLevel2ThresholdValue,
|
||||
c.PriceAdjustmentLevels, c.PriceAdjustmentLevel2ThresholdPercent,
|
||||
Level2RoleIds(c).Order().ToList(), c.UpdatedAt);
|
||||
Level2RoleIds(c).Order().ToList(), c.UpdatedAt, c.WastageLevels);
|
||||
}
|
||||
|
||||
@@ -27,11 +27,11 @@ public sealed class ExpensePaymentService : IExpensePaymentService
|
||||
private readonly IGeneralLedgerService _gl;
|
||||
private readonly ICurrentUser _currentUser;
|
||||
private readonly IUnitOfWork _uow;
|
||||
private readonly string _glAccruedExpensesCode;
|
||||
private readonly IGlAccountConfigService _glAccounts;
|
||||
|
||||
public ExpensePaymentService(
|
||||
IRepository<Expense> expenses, IRepository<ExpensePayment> payments, ExpenseService expenseService,
|
||||
IGeneralLedgerService gl, ICurrentUser currentUser, IUnitOfWork uow, IConfiguration configuration)
|
||||
IGeneralLedgerService gl, ICurrentUser currentUser, IUnitOfWork uow, IGlAccountConfigService glAccounts)
|
||||
{
|
||||
_expenses = expenses;
|
||||
_payments = payments;
|
||||
@@ -39,7 +39,7 @@ public sealed class ExpensePaymentService : IExpensePaymentService
|
||||
_gl = gl;
|
||||
_currentUser = currentUser;
|
||||
_uow = uow;
|
||||
_glAccruedExpensesCode = configuration["Expense:GlAccruedExpensesCode"] ?? string.Empty;
|
||||
_glAccounts = glAccounts;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<ExpensePaymentDto>> PayAsync(int expenseId, CreateExpensePaymentRequest request, CancellationToken ct = default)
|
||||
@@ -110,7 +110,8 @@ public sealed class ExpensePaymentService : IExpensePaymentService
|
||||
{
|
||||
var total = request.Lines.Sum(l => l.Amount);
|
||||
var narration = $"Payment of expense {expense.DocNo}";
|
||||
var glLines = new List<GlJournalEntryLineRequest> { new(_glAccruedExpensesCode, total, 0m, narration) };
|
||||
var accruedExpensesCode = (await _glAccounts.GetCodesAsync(ct))[GlAccountRole.AccruedExpenses];
|
||||
var glLines = new List<GlJournalEntryLineRequest> { new(accruedExpensesCode, total, 0m, narration) };
|
||||
glLines.AddRange(request.Lines.Select(l => new GlJournalEntryLineRequest(accounts[l.GlBankAccountId].GlAccountCode, 0m, l.Amount, narration)));
|
||||
|
||||
var period = await _gl.GetPeriodByDateAsync(paymentDay, ct);
|
||||
|
||||
@@ -32,12 +32,12 @@ public sealed class ExpenseService : IExpenseService
|
||||
private readonly IBranchConfigService _branchConfig;
|
||||
private readonly IUnitOfWork _uow;
|
||||
private readonly IGeneralLedgerService _gl;
|
||||
private readonly string _glAccruedExpensesCode;
|
||||
private readonly IGlAccountConfigService _glAccounts;
|
||||
|
||||
public ExpenseService(
|
||||
IRepository<Expense> expenses, IRepository<ExpenseTitle> titles, IRepository<Vendor> vendors,
|
||||
ICurrentUser currentUser, INumberSequenceService numbers, IBranchConfigService branchConfig,
|
||||
IUnitOfWork uow, IGeneralLedgerService gl, IConfiguration configuration)
|
||||
IUnitOfWork uow, IGeneralLedgerService gl, IGlAccountConfigService glAccounts)
|
||||
{
|
||||
_expenses = expenses;
|
||||
_titles = titles;
|
||||
@@ -47,7 +47,7 @@ public sealed class ExpenseService : IExpenseService
|
||||
_branchConfig = branchConfig;
|
||||
_uow = uow;
|
||||
_gl = gl;
|
||||
_glAccruedExpensesCode = configuration["Expense:GlAccruedExpensesCode"] ?? string.Empty;
|
||||
_glAccounts = glAccounts;
|
||||
}
|
||||
|
||||
public async Task<PagedResponse<ExpenseSummaryDto>> ListAsync(PageQuery query, ExpenseListFilter filter, CancellationToken ct = default)
|
||||
@@ -174,6 +174,7 @@ public sealed class ExpenseService : IExpenseService
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var narration = $"{expense.Category?.Name} - {title.Name} ({expense.DocNo})";
|
||||
var accruedExpensesCode = (await _glAccounts.GetCodesAsync(ct))[GlAccountRole.AccruedExpenses];
|
||||
var period = await _gl.GetPeriodByDateAsync(expense.ExpenseDate, ct);
|
||||
var posted = await _gl.PostJournalEntryAsync(new GlJournalEntryRequest
|
||||
{
|
||||
@@ -185,7 +186,7 @@ public sealed class ExpenseService : IExpenseService
|
||||
Lines =
|
||||
[
|
||||
new GlJournalEntryLineRequest(title.GlExpenseAccountCode, expense.Amount, 0m, narration),
|
||||
new GlJournalEntryLineRequest(_glAccruedExpensesCode, 0m, expense.Amount, narration)
|
||||
new GlJournalEntryLineRequest(accruedExpensesCode, 0m, expense.Amount, narration)
|
||||
]
|
||||
}, ct);
|
||||
|
||||
@@ -215,6 +216,7 @@ public sealed class ExpenseService : IExpenseService
|
||||
{
|
||||
var today = DateOnly.FromDateTime(now);
|
||||
var narration = $"Cancellation of expense {expense.DocNo}";
|
||||
var accruedExpensesCode = (await _glAccounts.GetCodesAsync(token))[GlAccountRole.AccruedExpenses];
|
||||
var period = await _gl.GetPeriodByDateAsync(today, token);
|
||||
var reversal = await _gl.PostJournalEntryAsync(new GlJournalEntryRequest
|
||||
{
|
||||
@@ -225,7 +227,7 @@ public sealed class ExpenseService : IExpenseService
|
||||
Narration = narration,
|
||||
Lines =
|
||||
[
|
||||
new GlJournalEntryLineRequest(_glAccruedExpensesCode, expense.Amount, 0m, narration),
|
||||
new GlJournalEntryLineRequest(accruedExpensesCode, expense.Amount, 0m, narration),
|
||||
new GlJournalEntryLineRequest(expense.GlExpenseAccountCode ?? string.Empty, 0m, expense.Amount, narration)
|
||||
]
|
||||
}, token);
|
||||
|
||||
@@ -54,7 +54,11 @@ public enum GlAccountRole
|
||||
InventoryLoss,
|
||||
|
||||
/// <summary>Cash takings at day-end close.</summary>
|
||||
Cash
|
||||
Cash,
|
||||
|
||||
/// <summary>Expenses booked but not yet paid (a liability): credited when an expense is posted, debited when it is paid.
|
||||
/// Used to live only in appsettings (<c>Expense:GlAccruedExpensesCode</c>), where nobody could see or check it.</summary>
|
||||
AccruedExpenses
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -105,6 +105,8 @@ public sealed class GlAccountConfigService : IGlAccountConfigService
|
||||
config.CogsAccountCode = submitted[GlAccountRole.Cogs];
|
||||
config.InventoryLossAccountCode = submitted[GlAccountRole.InventoryLoss];
|
||||
config.CashAccountCode = submitted[GlAccountRole.Cash];
|
||||
// An older client that does not send this role keeps what is stored (it must never blank a mapping it can't see).
|
||||
if (request.AccruedExpensesCode is not null) config.AccruedExpensesCode = submitted[GlAccountRole.AccruedExpenses];
|
||||
config.UpdatedAt = DateTime.UtcNow;
|
||||
config.UpdatedBy = _currentUser.AuditUserId;
|
||||
|
||||
@@ -146,7 +148,8 @@ public sealed class GlAccountConfigService : IGlAccountConfigService
|
||||
[GlAccountRole.InventoryGain] = Trim(c.InventoryGainAccountCode),
|
||||
[GlAccountRole.Cogs] = Trim(c.CogsAccountCode),
|
||||
[GlAccountRole.InventoryLoss] = Trim(c.InventoryLossAccountCode),
|
||||
[GlAccountRole.Cash] = Trim(c.CashAccountCode)
|
||||
[GlAccountRole.Cash] = Trim(c.CashAccountCode),
|
||||
[GlAccountRole.AccruedExpenses] = Trim(c.AccruedExpensesCode)
|
||||
};
|
||||
|
||||
private static Dictionary<GlAccountRole, string?> Submitted(UpdateGlAccountConfigRequest r) => new()
|
||||
@@ -162,7 +165,8 @@ public sealed class GlAccountConfigService : IGlAccountConfigService
|
||||
[GlAccountRole.InventoryGain] = Trim(r.InventoryGainAccountCode),
|
||||
[GlAccountRole.Cogs] = Trim(r.CogsAccountCode),
|
||||
[GlAccountRole.InventoryLoss] = Trim(r.InventoryLossAccountCode),
|
||||
[GlAccountRole.Cash] = Trim(r.CashAccountCode)
|
||||
[GlAccountRole.Cash] = Trim(r.CashAccountCode),
|
||||
[GlAccountRole.AccruedExpenses] = Trim(r.AccruedExpensesCode)
|
||||
};
|
||||
|
||||
private static string? Trim(string? code) => string.IsNullOrWhiteSpace(code) ? null : code.Trim();
|
||||
|
||||
@@ -8,7 +8,8 @@ namespace ERPCore.Services.Interfaces;
|
||||
public interface IAdjustmentService
|
||||
{
|
||||
Task<PagedResponse<AdjustmentSummaryDto>> ListAsync(
|
||||
PageQuery query, int? warehouseId, int? branchId, int? reasonCodeId, AdjustmentStatus? status = null, CancellationToken ct = default);
|
||||
PageQuery query, int? warehouseId, int? branchId, int? reasonCodeId, AdjustmentStatus? status = null,
|
||||
CancellationToken ct = default, bool wastageOnly = false, int? itemId = null);
|
||||
|
||||
Task<AdjustmentDto?> GetAsync(int adjustmentId, CancellationToken ct = default);
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@ public interface IApprovalService
|
||||
/// <summary>Approvals a stock adjustment worth <paramref name="absoluteValue"/> needs (0, 1 or 2).</summary>
|
||||
Task<int> RequiredForStockAdjustmentAsync(decimal absoluteValue, CancellationToken ct = default);
|
||||
|
||||
/// <summary>Approvals a wastage write-off needs (Settings -> Approvals "Wastage needs dual approval"): 0 or 2.</summary>
|
||||
Task<int> RequiredForWastageAsync(CancellationToken ct = default);
|
||||
|
||||
/// <summary>Approvals a price change of <paramref name="largestChangePercent"/> needs (0, 1 or 2).</summary>
|
||||
Task<int> RequiredForPriceAdjustmentAsync(decimal largestChangePercent, CancellationToken ct = default);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user