Repair botched rebase resolutions from ui-bug-fixers
The rebase onto Dev resolved five conflicts; four were wrong. In the service files Dev's constructor parameter lists won while the branch's field declarations and constructor bodies stayed, so the bodies assigned parameters that no longer existed. - AdjustmentService: restore fifo, approvals, db and serialTracking parameters (Dev's glAccounts kept, configuration dropped). - FifoCostingService: restore the LowStockTracker field, parameter and assignment. - CountService: restore _serialTracking and _adjustments, and delete Dev's PostAdjustmentJournalAsync. Dev's call site did not survive the rebase, so the method was orphaned dead code referencing fields this class no longer has. The branch routes count variances through AdjustmentService.SubmitAsync, which posts via Dev's role-keyed glCodes, so nothing is lost by removing it. - PurchaseInvoicePaymentService: this one compiled. paymentDay was computed but the GL period and EntryDate still used now, so a back-dated payment posted its journal to today's period. Use paymentDay, and resolve "today" through BusinessClock, matching SalesInvoicePaymentService. - ErpDbContextModelSnapshot: the resolution dropped the whole Expense module from the definition and relationship sections while leaving its navigation entries behind, so the snapshot no longer loaded. Rebuilt as the branch's snapshot plus Dev's GlAccountConfig blocks; EF now reports no pending model changes. Tests: BankDepositTestSupport's FakeGeneralLedger gains the ListAccountsAsync member Dev added to IGeneralLedgerService, and DualApprovalTests passes a StubGlAccountConfigService in place of the removed IConfiguration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ using ERPCore.Infra.Auth;
|
||||
using ERPCore.Infra.Persistence;
|
||||
using ERPCore.Repositories;
|
||||
using ERPCore.Services;
|
||||
using ERPCore.Services.Gl;
|
||||
using ERPCore.Services.Interfaces;
|
||||
using ERPCore.Services.Stock;
|
||||
using ERPCore.System.Errors;
|
||||
@@ -300,7 +301,10 @@ public class DualApprovalTests
|
||||
new Repository<StockAdjustment>(db), new Repository<Warehouse>(db), new Repository<Item>(db),
|
||||
new Repository<ReasonCode>(db), new Repository<StockLedger>(db), mutator, new FixedValuation(),
|
||||
numbers, user, new FakeBranchConfig(), notifications, approvals, uow, db, gl,
|
||||
new ConfigurationBuilder().Build(),
|
||||
new StubGlAccountConfigService(
|
||||
(GlAccountRole.Inventory, "1200"),
|
||||
(GlAccountRole.InventoryGain, "4900"),
|
||||
(GlAccountRole.InventoryLoss, "5900")),
|
||||
new SerialTrackingService(new Repository<Serial>(db), new Repository<SerialMovement>(db), new Repository<StockLayer>(db),
|
||||
new Repository<ProductConfig>(db), user));
|
||||
var prices = new PriceAdjustmentService(
|
||||
|
||||
@@ -45,6 +45,10 @@ internal sealed class FakeGeneralLedger : IGeneralLedgerService
|
||||
public Task<GlPeriod> GetPeriodByDateAsync(DateOnly date, CancellationToken ct) => Task.FromResult(new GlPeriod(1, 1));
|
||||
|
||||
public Task<IReadOnlyList<GlBankAccount>> ListBankAccountsAsync(CancellationToken ct) => Task.FromResult(Accounts);
|
||||
|
||||
/// <summary>Not implemented on purpose — nothing here picks a GL account by code; see
|
||||
/// <see cref="StubGeneralLedgerService"/> for the fake that does.</summary>
|
||||
public Task<IReadOnlyList<GlAccount>> ListAccountsAsync(CancellationToken ct) => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>The in-memory provider has no transactions: run the work, then save — what the real one commits.</summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using ERPCore.Infra.Persistence;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -1651,6 +1651,564 @@ namespace ERPCore.Migrations
|
||||
b.ToTable("hr_employment_types", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b =>
|
||||
{
|
||||
b.Property<int>("ExpenseId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("ExpenseId"));
|
||||
|
||||
b.Property<decimal>("Amount")
|
||||
.HasPrecision(18, 4)
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<decimal>("BalanceAmount")
|
||||
.HasPrecision(18, 4)
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<string>("BillReference")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar(50)");
|
||||
|
||||
b.Property<int>("BranchId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("CancelReason")
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("varchar(250)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int>("CreatedBy")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("varchar(500)");
|
||||
|
||||
b.Property<string>("DocNo")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("varchar(30)");
|
||||
|
||||
b.Property<DateOnly?>("DueDate")
|
||||
.HasColumnType("date");
|
||||
|
||||
b.Property<int>("ExpenseCategoryId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateOnly>("ExpenseDate")
|
||||
.HasColumnType("date");
|
||||
|
||||
b.Property<int>("ExpenseTitleId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("GlExpenseAccountCode")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)");
|
||||
|
||||
b.Property<string>("GlJournalNo")
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("varchar(30)");
|
||||
|
||||
b.Property<DateTime?>("GlPostedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("GlReversalJournalNo")
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("varchar(30)");
|
||||
|
||||
b.Property<decimal>("PaidAmount")
|
||||
.HasPrecision(18, 4)
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<string>("Payee")
|
||||
.HasMaxLength(150)
|
||||
.HasColumnType("varchar(150)");
|
||||
|
||||
b.Property<string>("PaymentStatus")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)")
|
||||
.HasDefaultValue("Unpaid");
|
||||
|
||||
b.Property<string>("PeriodMonth")
|
||||
.HasMaxLength(7)
|
||||
.HasColumnType("varchar(7)");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("int unsigned");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)")
|
||||
.HasDefaultValue("Draft");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int?>("VendorId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("ExpenseId");
|
||||
|
||||
b.HasIndex("BranchId");
|
||||
|
||||
b.HasIndex("CreatedBy");
|
||||
|
||||
b.HasIndex("DocNo")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("ExpenseCategoryId");
|
||||
|
||||
b.HasIndex("ExpenseDate");
|
||||
|
||||
b.HasIndex("ExpenseTitleId");
|
||||
|
||||
b.HasIndex("PaymentStatus");
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.HasIndex("VendorId");
|
||||
|
||||
b.ToTable("expenses", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_expenses_amount", "`Amount` > 0");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseCategory", b =>
|
||||
{
|
||||
b.Property<int>("ExpenseCategoryId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("ExpenseCategoryId"));
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("varchar(30)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<bool>("IsSystem")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("int unsigned");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)")
|
||||
.HasDefaultValue("Active");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("ExpenseCategoryId");
|
||||
|
||||
b.HasIndex("Code")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Name")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("expense_categories", (string)null);
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
ExpenseCategoryId = 1,
|
||||
Code = "UTILITY",
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
IsSystem = true,
|
||||
Name = "Utility",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 1,
|
||||
Status = "Active"
|
||||
},
|
||||
new
|
||||
{
|
||||
ExpenseCategoryId = 2,
|
||||
Code = "RENTAL",
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
IsSystem = true,
|
||||
Name = "Rental",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 2,
|
||||
Status = "Active"
|
||||
},
|
||||
new
|
||||
{
|
||||
ExpenseCategoryId = 3,
|
||||
Code = "SALARIES",
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
IsSystem = true,
|
||||
Name = "Salaries",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 3,
|
||||
Status = "Active"
|
||||
},
|
||||
new
|
||||
{
|
||||
ExpenseCategoryId = 4,
|
||||
Code = "OTHER",
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
IsSystem = true,
|
||||
Name = "Other",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 4,
|
||||
Status = "Active"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.ExpensePayment", b =>
|
||||
{
|
||||
b.Property<int>("ExpensePaymentId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("ExpensePaymentId"));
|
||||
|
||||
b.Property<decimal>("Amount")
|
||||
.HasPrecision(18, 4)
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<string>("BankAccountName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("varchar(200)");
|
||||
|
||||
b.Property<string>("ChequeNo")
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("varchar(30)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int>("CreatedBy")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("ExpenseId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<long>("GlBankAccountId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("GlJournalNo")
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("varchar(30)");
|
||||
|
||||
b.Property<DateTime>("PaymentDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("PaymentMethod")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)");
|
||||
|
||||
b.Property<string>("Reference")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)");
|
||||
|
||||
b.HasKey("ExpensePaymentId");
|
||||
|
||||
b.HasIndex("CreatedBy");
|
||||
|
||||
b.HasIndex("ExpenseId");
|
||||
|
||||
b.ToTable("expense_payments", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseTitle", b =>
|
||||
{
|
||||
b.Property<int>("ExpenseTitleId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("ExpenseTitleId"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int>("ExpenseCategoryId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("GlExpenseAccountCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)");
|
||||
|
||||
b.Property<bool>("IsSystem")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("int unsigned");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)")
|
||||
.HasDefaultValue("Active");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("ExpenseTitleId");
|
||||
|
||||
b.HasIndex("ExpenseCategoryId", "Name")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("expense_titles", (string)null);
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
ExpenseTitleId = 1,
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
ExpenseCategoryId = 1,
|
||||
GlExpenseAccountCode = "5200-01",
|
||||
IsSystem = true,
|
||||
Name = "Electricity",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 1,
|
||||
Status = "Active"
|
||||
},
|
||||
new
|
||||
{
|
||||
ExpenseTitleId = 2,
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
ExpenseCategoryId = 1,
|
||||
GlExpenseAccountCode = "5200-01",
|
||||
IsSystem = true,
|
||||
Name = "Water",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 2,
|
||||
Status = "Active"
|
||||
},
|
||||
new
|
||||
{
|
||||
ExpenseTitleId = 3,
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
ExpenseCategoryId = 1,
|
||||
GlExpenseAccountCode = "5200-01",
|
||||
IsSystem = true,
|
||||
Name = "Telephone",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 3,
|
||||
Status = "Active"
|
||||
},
|
||||
new
|
||||
{
|
||||
ExpenseTitleId = 4,
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
ExpenseCategoryId = 1,
|
||||
GlExpenseAccountCode = "5200-01",
|
||||
IsSystem = true,
|
||||
Name = "Internet",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 4,
|
||||
Status = "Active"
|
||||
},
|
||||
new
|
||||
{
|
||||
ExpenseTitleId = 5,
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
ExpenseCategoryId = 1,
|
||||
GlExpenseAccountCode = "5200-01",
|
||||
IsSystem = true,
|
||||
Name = "Gas",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 5,
|
||||
Status = "Active"
|
||||
},
|
||||
new
|
||||
{
|
||||
ExpenseTitleId = 6,
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
ExpenseCategoryId = 2,
|
||||
GlExpenseAccountCode = "5200-02",
|
||||
IsSystem = true,
|
||||
Name = "Building Rent",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 1,
|
||||
Status = "Active"
|
||||
},
|
||||
new
|
||||
{
|
||||
ExpenseTitleId = 7,
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
ExpenseCategoryId = 2,
|
||||
GlExpenseAccountCode = "5200-02",
|
||||
IsSystem = true,
|
||||
Name = "Vehicle Rent",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 2,
|
||||
Status = "Active"
|
||||
},
|
||||
new
|
||||
{
|
||||
ExpenseTitleId = 8,
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
ExpenseCategoryId = 2,
|
||||
GlExpenseAccountCode = "5200-02",
|
||||
IsSystem = true,
|
||||
Name = "Equipment Rent",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 3,
|
||||
Status = "Active"
|
||||
},
|
||||
new
|
||||
{
|
||||
ExpenseTitleId = 9,
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
ExpenseCategoryId = 3,
|
||||
GlExpenseAccountCode = "5200-03",
|
||||
IsSystem = true,
|
||||
Name = "Staff Salaries",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 1,
|
||||
Status = "Active"
|
||||
},
|
||||
new
|
||||
{
|
||||
ExpenseTitleId = 10,
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
ExpenseCategoryId = 3,
|
||||
GlExpenseAccountCode = "5200-03",
|
||||
IsSystem = true,
|
||||
Name = "Wages",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 2,
|
||||
Status = "Active"
|
||||
},
|
||||
new
|
||||
{
|
||||
ExpenseTitleId = 11,
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
ExpenseCategoryId = 3,
|
||||
GlExpenseAccountCode = "5200-03",
|
||||
IsSystem = true,
|
||||
Name = "Overtime",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 3,
|
||||
Status = "Active"
|
||||
},
|
||||
new
|
||||
{
|
||||
ExpenseTitleId = 12,
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
ExpenseCategoryId = 3,
|
||||
GlExpenseAccountCode = "5200-03",
|
||||
IsSystem = true,
|
||||
Name = "Bonus",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 4,
|
||||
Status = "Active"
|
||||
},
|
||||
new
|
||||
{
|
||||
ExpenseTitleId = 13,
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
ExpenseCategoryId = 3,
|
||||
GlExpenseAccountCode = "5200-03",
|
||||
IsSystem = true,
|
||||
Name = "EPF / ETF Contribution",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 5,
|
||||
Status = "Active"
|
||||
},
|
||||
new
|
||||
{
|
||||
ExpenseTitleId = 14,
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
ExpenseCategoryId = 4,
|
||||
GlExpenseAccountCode = "5200-04",
|
||||
IsSystem = true,
|
||||
Name = "Stationery",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 1,
|
||||
Status = "Active"
|
||||
},
|
||||
new
|
||||
{
|
||||
ExpenseTitleId = 15,
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
ExpenseCategoryId = 4,
|
||||
GlExpenseAccountCode = "5200-04",
|
||||
IsSystem = true,
|
||||
Name = "Transport",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 2,
|
||||
Status = "Active"
|
||||
},
|
||||
new
|
||||
{
|
||||
ExpenseTitleId = 16,
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
ExpenseCategoryId = 4,
|
||||
GlExpenseAccountCode = "5200-04",
|
||||
IsSystem = true,
|
||||
Name = "Repairs & Maintenance",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 3,
|
||||
Status = "Active"
|
||||
},
|
||||
new
|
||||
{
|
||||
ExpenseTitleId = 17,
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
ExpenseCategoryId = 4,
|
||||
GlExpenseAccountCode = "5200-04",
|
||||
IsSystem = true,
|
||||
Name = "Cleaning",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 4,
|
||||
Status = "Active"
|
||||
},
|
||||
new
|
||||
{
|
||||
ExpenseTitleId = 18,
|
||||
CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
ExpenseCategoryId = 4,
|
||||
GlExpenseAccountCode = "5200-04",
|
||||
IsSystem = true,
|
||||
Name = "Miscellaneous",
|
||||
RowVersion = 1u,
|
||||
SortOrder = 5,
|
||||
Status = "Active"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.GlAccountConfig", b =>
|
||||
{
|
||||
b.Property<int>("ConfigId")
|
||||
@@ -8139,6 +8697,78 @@ namespace ERPCore.Migrations
|
||||
b.Navigation("SalaryComponent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b =>
|
||||
{
|
||||
b.HasOne("ERPCore.Domain.Entities.Branch", "Branch")
|
||||
.WithMany()
|
||||
.HasForeignKey("BranchId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("ERPCore.Domain.Entities.User", "Creator")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatedBy")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("ERPCore.Domain.Entities.ExpenseCategory", "Category")
|
||||
.WithMany()
|
||||
.HasForeignKey("ExpenseCategoryId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("ERPCore.Domain.Entities.ExpenseTitle", "Title")
|
||||
.WithMany()
|
||||
.HasForeignKey("ExpenseTitleId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor")
|
||||
.WithMany()
|
||||
.HasForeignKey("VendorId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.Navigation("Branch");
|
||||
|
||||
b.Navigation("Category");
|
||||
|
||||
b.Navigation("Creator");
|
||||
|
||||
b.Navigation("Title");
|
||||
|
||||
b.Navigation("Vendor");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.ExpensePayment", b =>
|
||||
{
|
||||
b.HasOne("ERPCore.Domain.Entities.User", "Creator")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatedBy")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("ERPCore.Domain.Entities.Expense", "Expense")
|
||||
.WithMany("Payments")
|
||||
.HasForeignKey("ExpenseId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Creator");
|
||||
|
||||
b.Navigation("Expense");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseTitle", b =>
|
||||
{
|
||||
b.HasOne("ERPCore.Domain.Entities.ExpenseCategory", "Category")
|
||||
.WithMany("Titles")
|
||||
.HasForeignKey("ExpenseCategoryId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Category");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.GlAccountConfig", b =>
|
||||
{
|
||||
b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser")
|
||||
|
||||
@@ -53,9 +53,10 @@ public sealed class AdjustmentService : IAdjustmentService
|
||||
|
||||
public AdjustmentService(
|
||||
IRepository<StockAdjustment> adjustments, IRepository<Warehouse> warehouses, IRepository<Item> items,
|
||||
IRepository<ReasonCode> reasonCodes, IRepository<StockLedger> ledger, IStockMutator mutator,
|
||||
INumberSequenceService numbers, ICurrentUser currentUser, IBranchConfigService branchConfig, INotificationService notifications, IUnitOfWork uow,
|
||||
IGeneralLedgerService gl, IGlAccountConfigService glAccounts)
|
||||
IRepository<ReasonCode> reasonCodes, IRepository<StockLedger> ledger, IStockMutator mutator, IFifoCostingService fifo,
|
||||
INumberSequenceService numbers, ICurrentUser currentUser, IBranchConfigService branchConfig, INotificationService notifications,
|
||||
IApprovalService approvals, IUnitOfWork uow, ErpDbContext db,
|
||||
IGeneralLedgerService gl, IGlAccountConfigService glAccounts, ISerialTrackingService serialTracking)
|
||||
{
|
||||
_serialTracking = serialTracking;
|
||||
_adjustments = adjustments;
|
||||
|
||||
@@ -38,14 +38,15 @@ public sealed class CountService : ICountService
|
||||
private readonly IRepository<StockLayer> _stockLayers;
|
||||
private readonly IRepository<Batch> _batches;
|
||||
private readonly IRepository<Serial> _serials;
|
||||
private readonly IGlAccountConfigService _glAccounts;
|
||||
private readonly ISerialTrackingService _serialTracking;
|
||||
private readonly AdjustmentService _adjustments;
|
||||
|
||||
public CountService(
|
||||
IRepository<StockCount> counts, IRepository<Warehouse> warehouses,
|
||||
IRepository<Item> items, IRepository<ReasonCode> reasonCodes, IFifoCostingService fifo,
|
||||
INumberSequenceService numbers, ICurrentUser currentUser, IBranchConfigService branchConfig, IUnitOfWork uow,
|
||||
IGeneralLedgerService gl, IRepository<StockLayer> stockLayers, IRepository<Batch> batches,
|
||||
IRepository<Serial> serials, IGlAccountConfigService glAccounts)
|
||||
IRepository<StockLayer> stockLayers, IRepository<Batch> batches,
|
||||
IRepository<Serial> serials, ISerialTrackingService serialTracking, AdjustmentService adjustments)
|
||||
{
|
||||
_serialTracking = serialTracking;
|
||||
_counts = counts;
|
||||
@@ -60,42 +61,7 @@ public sealed class CountService : ICountService
|
||||
_stockLayers = stockLayers;
|
||||
_batches = batches;
|
||||
_serials = serials;
|
||||
_glAccounts = glAccounts;
|
||||
}
|
||||
|
||||
/// <summary>Same logic as <c>AdjustmentService</c>'s private method of the same name — see there for why
|
||||
/// gains/losses post as separate lines instead of a net.</summary>
|
||||
private async Task<string?> PostAdjustmentJournalAsync(
|
||||
string docNo, DateTime now, IReadOnlyList<StockLedger> refs, CancellationToken ct)
|
||||
{
|
||||
var gain = refs.Where(r => r.Direction == Direction.In).Sum(r => r.Value);
|
||||
var loss = refs.Where(r => r.Direction == Direction.Out).Sum(r => r.Value);
|
||||
if (gain <= 0 && loss <= 0) return null;
|
||||
|
||||
var glCodes = await _glAccounts.GetCodesAsync(ct);
|
||||
var period = await _gl.GetPeriodByDateAsync(BusinessClock.ToBusinessDate(now), ct);
|
||||
var lines = new List<GlJournalEntryLineRequest>();
|
||||
if (gain > 0)
|
||||
{
|
||||
lines.Add(new GlJournalEntryLineRequest(glCodes[GlAccountRole.Inventory], gain, 0m, $"Adjustment {docNo} — stock increase"));
|
||||
lines.Add(new GlJournalEntryLineRequest(glCodes[GlAccountRole.InventoryGain], 0m, gain, $"Adjustment {docNo} — inventory gain"));
|
||||
}
|
||||
if (loss > 0)
|
||||
{
|
||||
lines.Add(new GlJournalEntryLineRequest(glCodes[GlAccountRole.InventoryLoss], loss, 0m, $"Adjustment {docNo} — inventory loss"));
|
||||
lines.Add(new GlJournalEntryLineRequest(glCodes[GlAccountRole.Inventory], 0m, loss, $"Adjustment {docNo} — stock decrease"));
|
||||
}
|
||||
|
||||
var result = await _gl.PostJournalEntryAsync(new GlJournalEntryRequest
|
||||
{
|
||||
PeriodId = period.PeriodId,
|
||||
EntryDate = BusinessClock.ToBusinessDate(now),
|
||||
SourceModule = "ADJUSTMENT",
|
||||
Reference = docNo,
|
||||
Narration = $"Stock adjustment {docNo}",
|
||||
Lines = lines
|
||||
}, ct);
|
||||
return result.JournalNo;
|
||||
_adjustments = adjustments;
|
||||
}
|
||||
|
||||
public async Task<PagedResponse<CountSummaryDto>> ListAsync(
|
||||
|
||||
@@ -64,11 +64,11 @@ public sealed class PurchaseInvoicePaymentService : IPurchaseInvoicePaymentServi
|
||||
var payment = await _uow.ExecuteInTransactionAsync(async token =>
|
||||
{
|
||||
var glCodes = await _glAccounts.GetCodesAsync(token);
|
||||
var period = await _gl.GetPeriodByDateAsync(BusinessClock.ToBusinessDate(now), token);
|
||||
var period = await _gl.GetPeriodByDateAsync(paymentDay, token);
|
||||
var posted = await _gl.PostJournalEntryAsync(new GlJournalEntryRequest
|
||||
{
|
||||
PeriodId = period.PeriodId,
|
||||
EntryDate = BusinessClock.ToBusinessDate(now),
|
||||
EntryDate = paymentDay,
|
||||
SourceModule = "PURCHASE_INVOICE_PAYMENT",
|
||||
Reference = invoice.DocNo,
|
||||
Narration = $"Payment against invoice {invoice.DocNo}",
|
||||
@@ -104,13 +104,14 @@ public sealed class PurchaseInvoicePaymentService : IPurchaseInvoicePaymentServi
|
||||
|
||||
/// <summary>
|
||||
/// The payment's calendar day (also the GL entry date) and the timestamp stored on it — same
|
||||
/// rule as <see cref="SalesInvoicePaymentService"/>. Omitted ⇒ today. One day of slack on
|
||||
/// rule as <see cref="SalesInvoicePaymentService"/>. Omitted ⇒ today — the <em>business</em> day
|
||||
/// (<see cref="BusinessClock"/>), not the UTC one. One day of slack on
|
||||
/// "future": the client sends its local date, which runs ahead of UTC for users east of it.
|
||||
/// Today keeps the actual time of entry; a back-dated payment is stamped at that day's start.
|
||||
/// </summary>
|
||||
private static (DateOnly Day, DateTime Stamp) ResolvePaymentDate(DateOnly? requested, DateTime now)
|
||||
{
|
||||
var today = DateOnly.FromDateTime(now);
|
||||
var today = BusinessClock.ToBusinessDate(now);
|
||||
var day = requested ?? today;
|
||||
if (day > today.AddDays(1))
|
||||
throw new DomainException(ErrorCodes.Validation, "Payment date cannot be in the future.", 400);
|
||||
|
||||
@@ -27,12 +27,13 @@ public sealed class FifoCostingService : IFifoCostingService
|
||||
private readonly ErpDbContext _db;
|
||||
private readonly IGeneralLedgerService _gl;
|
||||
private readonly IGlAccountConfigService _glAccounts;
|
||||
private readonly LowStockTracker? _lowStock;
|
||||
|
||||
/// <param name="lowStock">Records each issue for the after-request low-stock check; optional so callers outside a request (tests) can omit it.</param>
|
||||
public FifoCostingService(
|
||||
IRepository<StockLayer> layers, IRepository<StockLedger> ledger,
|
||||
IRepository<JournalEntryStub> journal, ErpDbContext db,
|
||||
IGeneralLedgerService gl, IGlAccountConfigService glAccounts)
|
||||
IGeneralLedgerService gl, IGlAccountConfigService glAccounts, LowStockTracker? lowStock = null)
|
||||
{
|
||||
_layers = layers;
|
||||
_ledger = ledger;
|
||||
@@ -40,6 +41,7 @@ public sealed class FifoCostingService : IFifoCostingService
|
||||
_db = db;
|
||||
_gl = gl;
|
||||
_glAccounts = glAccounts;
|
||||
_lowStock = lowStock;
|
||||
}
|
||||
|
||||
public async Task<StockLayer> CreateInboundLayerAsync(
|
||||
|
||||
Reference in New Issue
Block a user