Add smoke tests for UOM conversions and enhance frontend UOM management

- Implemented smoke tests for UOM directionality, ensuring conversions are one-directional and correctly validated.
- Added tests for receiving and selling items in different UOMs, verifying correct quantity handling and error responses.
- Created a UOM conversions panel in the frontend to allow users to manage UOM conversions for items.
- Introduced hooks for allowed UOMs to optimize fetching and caching of UOM data for document line forms.
- Developed utility functions for consistent UOM formatting and conversion handling across the application.
This commit is contained in:
2026-08-10 15:12:51 +05:30
parent d37824cecc
commit 8d5a05a419
63 changed files with 1539 additions and 136 deletions
@@ -12,6 +12,8 @@ public sealed class BundleSaleLineConfiguration : IEntityTypeConfiguration<Bundl
builder.HasKey(x => x.BundleSaleLineId);
builder.Property(x => x.Description).IsRequired().HasMaxLength(200);
builder.Property(x => x.Qty).HasPrecision(18, 4);
builder.Property(x => x.QtyBase).HasPrecision(18, 4);
builder.Property(x => x.ConversionFactor).HasPrecision(18, 6).HasDefaultValue(1m);
builder.Property(x => x.UnitPrice).HasPrecision(18, 4);
builder.Property(x => x.LineTotal).HasPrecision(18, 4);
builder.Property(x => x.IncludeInBundle).HasDefaultValue(true);
@@ -36,6 +36,8 @@ public sealed class GrnLineConfiguration : IEntityTypeConfiguration<GrnLine>
builder.HasKey(l => l.GrnLineId);
builder.Property(l => l.Qty).HasPrecision(18, 4);
builder.Property(l => l.QtyBase).HasPrecision(18, 4);
builder.Property(l => l.ConversionFactor).HasPrecision(18, 6).HasDefaultValue(1m);
builder.Property(l => l.UnitCost).HasPrecision(18, 6);
builder.Property(l => l.PoUnitPrice).HasPrecision(18, 6);
builder.Property(l => l.DiscountPct).HasPrecision(9, 4);
@@ -49,6 +49,9 @@ public sealed class PoLineConfiguration : IEntityTypeConfiguration<PoLine>
builder.HasKey(l => l.PoLineId);
builder.Property(l => l.Qty).HasPrecision(18, 4);
builder.Property(l => l.QtyBase).HasPrecision(18, 4);
builder.Property(l => l.QtyReceivedBase).HasPrecision(18, 4);
builder.Property(l => l.ConversionFactor).HasPrecision(18, 6).HasDefaultValue(1m);
builder.Property(l => l.UnitPrice).HasPrecision(18, 4);
builder.Property(l => l.Tax).HasPrecision(9, 4);
builder.Property(l => l.QtyReceived).HasPrecision(18, 4);
@@ -70,6 +70,9 @@ public sealed class SalesInvoiceLineConfiguration : IEntityTypeConfiguration<Sal
builder.Property(x => x.Qty).HasPrecision(18, 4);
builder.Property(x => x.FreeQty).HasPrecision(18, 4);
builder.Property(x => x.QtyBase).HasPrecision(18, 4);
builder.Property(x => x.FreeQtyBase).HasPrecision(18, 4);
builder.Property(x => x.ConversionFactor).HasPrecision(18, 6).HasDefaultValue(1m);
builder.Property(x => x.UnitPrice).HasPrecision(18, 4);
builder.Property(x => x.BaseCost).HasPrecision(18, 4);
builder.Property(x => x.DiscountPct).HasPrecision(9, 4);
@@ -70,6 +70,9 @@ public sealed class SalesSlipLineConfiguration : IEntityTypeConfiguration<SalesS
builder.Property(x => x.Qty).HasPrecision(18, 4);
builder.Property(x => x.FreeQty).HasPrecision(18, 4);
builder.Property(x => x.QtyBase).HasPrecision(18, 4);
builder.Property(x => x.FreeQtyBase).HasPrecision(18, 4);
builder.Property(x => x.ConversionFactor).HasPrecision(18, 6).HasDefaultValue(1m);
builder.Property(x => x.UnitPrice).HasPrecision(18, 4);
builder.Property(x => x.BaseCost).HasPrecision(18, 4);
builder.Property(x => x.DiscountPct).HasPrecision(9, 4);
@@ -30,5 +30,9 @@ public sealed class UomConversionConfiguration : IEntityTypeConfiguration<UomCon
// One conversion per (item, from, to) triple.
builder.HasIndex(c => new { c.ItemId, c.FromUomId, c.ToUomId }).IsUnique();
// The API guards this too, but UomConverter divides unit cost by the factor — a zero
// reaching the table from a seeder or direct SQL would be a divide-by-zero at post time.
builder.ToTable(t => t.HasCheckConstraint("ck_uom_conversions_factor_positive", "\"Factor\" > 0"));
}
}