Prechádzať zdrojové kódy

"Allocate Stock" now loads stock of any dimension size if the dimension is convertible, and converts holdings when allocating from them

Kenric Nugteren 3 týždňov pred
rodič
commit
c3c2ac655b

+ 60 - 57
prs.desktop/Panels/Reservation Management/Holdings/ReservationManagementHoldingsGrid.xaml.cs

@@ -39,6 +39,8 @@ public partial class ReservationManagementHoldingsGrid : INotifyPropertyChanged
 
     public event HoldingsReviewRefresh? OnHoldingsReviewRefresh;
 
+    public StockDimensions? TargetDimensions { get; set; }
+
     private JobRequisitionItem? item;
     public JobRequisitionItem? Item
     {
@@ -51,6 +53,7 @@ public partial class ReservationManagementHoldingsGrid : INotifyPropertyChanged
             if(item?.ID != value?.ID)
             {
                 item = value;
+                TargetDimensions = item?.Dimensions.Copy();
                 CalculateHoldings();
                 SetStyle();
                 SetProductName();
@@ -95,9 +98,28 @@ public partial class ReservationManagementHoldingsGrid : INotifyPropertyChanged
         };
     }
 
-    private readonly ObservableCollection<ReservationManagementHoldingsModel<StockMovement>> greenList = new();
-    private readonly ObservableCollection<ReservationManagementHoldingsModel<StockMovement>> yellowList = new();
-    private readonly ObservableCollection<ReservationManagementHoldingsModel<StockMovement>> redList = new();
+    private class StockMovementModel
+    {
+        public StockMovement Movement { get; set; }
+
+        public StockDimensions ConvertedDimensions { get; private set; }
+
+        public double ConvertedUnits { get; private set; }
+
+        public double ConvertedCost { get; private set; }
+
+        public StockMovementModel(StockMovement movement)
+        {
+            Movement = movement;
+
+            ConvertedDimensions = movement.Dimensions.Copy();
+            (ConvertedUnits, ConvertedCost) = DimensionUtils.ConvertDimensions(ConvertedDimensions, movement.Units, movement.Cost, Client<ProductDimensionUnit>.Provider);
+        }
+    }
+
+    private readonly ObservableCollection<ReservationManagementHoldingsModel<StockMovementModel>> greenList = new();
+    private readonly ObservableCollection<ReservationManagementHoldingsModel<StockMovementModel>> yellowList = new();
+    private readonly ObservableCollection<ReservationManagementHoldingsModel<StockMovementModel>> redList = new();
     private readonly ObservableCollection<ReservationManagementHoldingsModel<PurchaseOrderItem>> purchasedList = new();
 
     private void DistributeItems<T>(Dictionary<Guid, List<T>>? items, Guid selectedStyleID, ReservationManagementHoldingsModel<T> model,
@@ -127,11 +149,22 @@ public partial class ReservationManagementHoldingsGrid : INotifyPropertyChanged
 
     private void CalculateHoldings()
     {
-        if(item is not null)
+        if(item is not null && TargetDimensions is not null)
         {
+            Filter<StockEntity> dimensionsFilter;
+            var hasConversion = DimensionUtils.HasConversion(TargetDimensions, Client<ProductDimensionUnit>.Provider);
+            if (hasConversion)
+            {
+                dimensionsFilter = Filter<StockEntity>.Where(x => x.Dimensions.Unit.ID).IsEqualTo(TargetDimensions.Unit.ID);
+            }
+            else
+            {
+                dimensionsFilter = Filter<StockEntity>.Where(x => x.Dimensions).DimensionEquals(TargetDimensions);
+            }
+
             var results = Client.Query(
                 Filter<StockMovement>.Where(x => x.Product.ID).IsEqualTo(item.Product.ID)
-                    .And(x => x.Dimensions).DimensionEquals(item.Dimensions)
+                    .And(dimensionsFilter.Cast<StockMovement>())
                     .And(x => x.Location.ID).IsNotEqualTo(Guid.Empty),
                 Columns.None<StockMovement>().Add(
                     x => x.ID,
@@ -148,34 +181,21 @@ public partial class ReservationManagementHoldingsGrid : INotifyPropertyChanged
                     x => x.Cost)
                     .AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.Data))
                 .ToObjects<StockMovement>();
-            var stockMovements = new Dictionary<(Guid job, bool allocated), Dictionary<Guid, List<StockMovement>>>();
+            var stockMovements = new Dictionary<(Guid job, bool allocated), Dictionary<Guid, List<StockMovementModel>>>();
             foreach(var mvt in results)
             {
                 stockMovements.GetValueOrAdd((mvt.Job.ID, mvt.JobRequisitionItem.ID != Guid.Empty))
                     .GetValueOrAdd(mvt.Style.ID)
-                    .Add(mvt);
-            }
-
-            Filter<PurchaseOrderItem> dimensionsFilter;
-            double jriDimensionsUnitValue;
-            if (item.Dimensions.Unit.Conversion.IsNullOrWhiteSpace())
-            {
-                dimensionsFilter = Filter<PurchaseOrderItem>.Where(x => x.Dimensions).DimensionEquals(item.Dimensions);
-                jriDimensionsUnitValue = 1;
-            }
-            else
-            {
-                dimensionsFilter = Filter<PurchaseOrderItem>.Where(x => x.Dimensions.Unit.ID).IsEqualTo(item.Dimensions.Unit.ID);
-                (jriDimensionsUnitValue, _) = DimensionUtils.ConvertDimensions(item.Dimensions.Copy(), 1, 0, Client<ProductDimensionUnit>.Provider);
+                    .Add(new(mvt));
             }
 
             var orderItemsResult = Client.Query(
                 Filter<PurchaseOrderItem>.Where(x => x.Product.ID).IsEqualTo(item.Product.ID)
-                    .And(dimensionsFilter)
+                    .And(dimensionsFilter.Cast<PurchaseOrderItem>())
                     .And(x => x.ReceivedDate).IsEqualTo(DateTime.MinValue),
                 Columns.None<PurchaseOrderItem>()
                     .Add(x => x.ID)
-                    .AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.All)
+                    .AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.Data)
                     .Add(x => x.Dimensions.Unit.Conversion)
                     .Add(x => x.Style.ID)
                     .Add(x => x.Style.Description)
@@ -193,7 +213,7 @@ public partial class ReservationManagementHoldingsGrid : INotifyPropertyChanged
                 {
                     // We need the unallocated quantity to be in terms of the dimensions of the JRI.
                     var (qty, cost) = DimensionUtils.ConvertDimensions(orderItem.Dimensions, orderItem.Unallocated, orderItem.Cost, Client<ProductDimensionUnit>.Provider);
-                    orderItemQuantities[orderItem.ID] = qty / jriDimensionsUnitValue;
+                    orderItemQuantities[orderItem.ID] = qty;
                     orderItem.Unallocated = qty;
                 }
                 else
@@ -214,11 +234,11 @@ public partial class ReservationManagementHoldingsGrid : INotifyPropertyChanged
 
             if(!stockMovements.ContainsKey((item.Job.ID, false)))
             {
-                stockMovements.Add((item.Job.ID, false), new Dictionary<Guid, List<StockMovement>>());
+                stockMovements.Add((item.Job.ID, false), new Dictionary<Guid, List<StockMovementModel>>());
             }
             if (!stockMovements.ContainsKey((Guid.Empty, false)))
             {
-                stockMovements.Add((Guid.Empty, false), new Dictionary<Guid, List<StockMovement>>());
+                stockMovements.Add((Guid.Empty, false), new Dictionary<Guid, List<StockMovementModel>>());
             }
 
             greenList.Clear();
@@ -230,14 +250,14 @@ public partial class ReservationManagementHoldingsGrid : INotifyPropertyChanged
             {
                 var job = jobs.GetValueOrDefault(key.job);
 
-                ReservationManagementHoldingsModel<StockMovement> holding;
+                ReservationManagementHoldingsModel<StockMovementModel> holding;
                 if (key.allocated)
                 {
                     holding = new(key.job, job?.JobNumber ?? "", job?.Name ?? "")
                     {
                         AlreadyAllocated = true
                     };
-                    DistributeItems(movements, item.Style.ID, holding, x => x.Units);
+                    DistributeItems(movements, item.Style.ID, holding, x => x.Movement.Units);
                     if (!holding.Empty)
                     {
                         redList.Add(holding);
@@ -246,19 +266,19 @@ public partial class ReservationManagementHoldingsGrid : INotifyPropertyChanged
                 else if (key.job == Guid.Empty)
                 {
                     holding = new(Guid.Empty, "Free Stock", "Free Stock");
-                    DistributeItems(movements, item.Style.ID, holding, x => x.Units);
+                    DistributeItems(movements, item.Style.ID, holding, x => x.ConvertedUnits);
                     greenList.Add(holding);
                 }
                 else if(key.job == item.Job.ID)
                 {
                     holding = new(item.Job.ID, item.Job.JobNumber, item.Job.Name);
-                    DistributeItems(movements, item.Style.ID, holding, x => x.Units);
+                    DistributeItems(movements, item.Style.ID, holding, x => x.ConvertedUnits);
                     greenList.Add(holding);
                 }
                 else
                 {
                     holding = new(key.job, job?.JobNumber ?? "", job?.Name ?? "");
-                    DistributeItems(movements, item.Style.ID, holding, x => x.Units);
+                    DistributeItems(movements, item.Style.ID, holding, x => x.ConvertedUnits);
                     if (!holding.Empty)
                     {
                         yellowList.Add(holding);
@@ -312,25 +332,6 @@ public partial class ReservationManagementHoldingsGrid : INotifyPropertyChanged
         listViewPurchased.ItemsSource = purchasedList;
     }
 
-    //private void Take_RightClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
-    //{
-    //    if (sender is not FrameworkElement element
-    //        || element.DataContext is not ReservationManagementHoldingsModel model
-    //        || element.Tag is not List<StockMovement> mvts) return;
-
-    //    if (model.AlreadyAllocated) return;
-    //    if (mvts == model.StockOfCurrentStyle && model.UnitsOfCurrentStyle > 0
-    //        || mvts == model.StockOfNoStyle && model.UnitsOfNoStyle > 0
-    //        || mvts == model.StockOfOtherStyles && model.UnitsOfOtherStyles > 0)
-    //    {
-    //        return;
-    //    }
-
-    //    var menu = new ContextMenu();
-    //    menu.AddItem("Take Anyway", null, () => LaunchStockSelectionPage(model, mvts));
-    //    menu.IsOpen = true;
-    //}
-
     private void Take_Click(object sender, RoutedEventArgs e)
     {
         if (sender is not FrameworkElement element
@@ -344,8 +345,8 @@ public partial class ReservationManagementHoldingsGrid : INotifyPropertyChanged
             return;
         }
 
-        if(model is ReservationManagementHoldingsModel<StockMovement> movementModel
-            && thisList is List<StockMovement> mvts)
+        if(model is ReservationManagementHoldingsModel<StockMovementModel> movementModel
+            && thisList is List<StockMovementModel> mvts)
         {
             LaunchStockSelectionPage(movementModel, mvts);
         }
@@ -372,15 +373,16 @@ public partial class ReservationManagementHoldingsGrid : INotifyPropertyChanged
             OnHoldingsReviewRefresh?.Invoke();
         }
     }
-    private void LaunchStockSelectionPage(ReservationManagementHoldingsModel<StockMovement> model, List<StockMovement> mvts)
+    private void LaunchStockSelectionPage(ReservationManagementHoldingsModel<StockMovementModel> model, List<StockMovementModel> mvts)
     {
-        if (Item is null) return;
+        if (Item is null || TargetDimensions is null) return;
 
-        var holdings = new Dictionary<(Guid locationID, Guid styleID, Guid requiItemID), StockHolding>();
+        var holdings = new Dictionary<(Guid locationID, Guid styleID, Guid requiItemID, StockDimensions dimensions), StockHolding>();
 
-        foreach (var mvt in mvts)
+        foreach (var mvtModel in mvts)
         {
-            if(!holdings.TryGetValue(new(mvt.Location.ID, mvt.Style.ID, mvt.JobRequisitionItem.ID), out var holding))
+            var mvt = mvtModel.Movement;
+            if(!holdings.TryGetValue(new(mvt.Location.ID, mvt.Style.ID, mvt.JobRequisitionItem.ID, mvt.Dimensions), out var holding))
             {
                 holding = new StockHolding();
 
@@ -388,7 +390,7 @@ public partial class ReservationManagementHoldingsGrid : INotifyPropertyChanged
                 holding.Style.CopyFrom(mvt.Style);
                 holding.Dimensions.CopyFrom(mvt.Dimensions);
                 
-                holdings.Add(new(mvt.Location.ID, mvt.Style.ID, mvt.JobRequisitionItem.ID), holding);
+                holdings.Add(new(mvt.Location.ID, mvt.Style.ID, mvt.JobRequisitionItem.ID, mvt.Dimensions), holding);
             }
             if (mvt.JobRequisitionItem.ID != Guid.Empty && model.AlreadyAllocated)
             {
@@ -414,6 +416,7 @@ public partial class ReservationManagementHoldingsGrid : INotifyPropertyChanged
             filteredHoldings,
             Item,
             new Job { ID = model.JobID, Name = model.JobName, JobNumber = model.JobNumber },
+            TargetDimensions,
             model.AlreadyAllocated);
 
         if (page.ShowDialog() == true)

+ 0 - 3
prs.desktop/Panels/Reservation Management/PurchaseOrderItemSelectionPage.xaml.cs

@@ -26,8 +26,6 @@ public class PurchaseOrderItemSelectionItem : JobRequisitionItemSelectionItem
     public string PONumber => OrderItem.PurchaseOrderLink.PONumber;
 
     public string Style => OrderItem.Style.Code;
-
-    public string UnitSize => OrderItem.Dimensions.UnitSize;
 }
 
 public class PurchaseOrderItemSelectionGrid : JobRequisitionItemSelectionGrid<PurchaseOrderItemSelectionItem>
@@ -37,7 +35,6 @@ public class PurchaseOrderItemSelectionGrid : JobRequisitionItemSelectionGrid<Pu
         var columns = new DynamicGridColumns();
         columns.Add<PurchaseOrderItemSelectionItem>(x => x.PONumber, 0, "Purchase Order", "", Alignment.MiddleCenter);
         columns.Add<PurchaseOrderItemSelectionItem>(x => x.Style, 0, "Style", "", Alignment.MiddleCenter);
-        columns.Add<PurchaseOrderItemSelectionItem>(x => x.UnitSize, 0, "Size", "", Alignment.MiddleCenter);
         return columns;
     }
 

+ 121 - 22
prs.desktop/Panels/Reservation Management/StockSelectionPage.xaml.cs

@@ -1,42 +1,89 @@
 using System;
 using System.Collections.Generic;
-using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Linq;
 using System.Runtime.CompilerServices;
 using System.Threading;
-using System.Threading.Tasks;
 using System.Windows;
-using System.Windows.Controls;
-using com.sun.tools.javac.util;
+using System.Windows.Media.Imaging;
 using Comal.Classes;
 using InABox.Clients;
 using InABox.Core;
 using InABox.DynamicGrid;
 using InABox.Wpf;
-using javax.swing;
+using InABox.WPF;
+using PRSDimensionUtils;
 
 namespace PRSDesktop;
 
 public class StockSelectionItem : JobRequisitionItemSelectionItem
 {
-    public string Area => Holding?.Location?.Area?.Code ?? "";
+    public string Area => Holding.Location.Area.Code;
 
     public string Requisition => JRI is null
         ? "Unrequisitioned Items"
         : $"{JRI.Job.JobNumber}: #{JRI.Requisition.Number} ({JRI.Requisition.Description})";
 
-    public string Location => Holding?.Location?.Code ?? "";
+    public string Location => Holding.Location.Code;
 
-    public string Style => Holding?.Style?.Code ?? "";
+    public string Style => Holding.Style.Code;
 
-    public StockHolding? Holding { get; set; }
+    // Must be set by something creating this, but cannot be required because it doesn't satisfy new() of the grid. So setting to null!.
+    public StockHolding Holding { get; set; } = null!;
+
+    public bool DimensionsMismatch { get; set; }
 }
 
 public class StockSelectionGrid : JobRequisitionItemSelectionGrid<StockSelectionItem>
 {
+    private static readonly BitmapImage _warningImage = PRSDesktop.Resources.warning.AsBitmapImage();
+
+    public StockDimensions? TargetDimensions { get; set; }
+
     public bool ShowRequisition { get; set; } = false;
 
+    private DynamicImageColumn WarningColumn;
+
+    public StockSelectionGrid()
+    {
+        WarningColumn = new DynamicImageColumn(Warning_Image)
+        {
+            ToolTip = Warning_ToolTip,
+        };
+    }
+
+    private FrameworkElement? Warning_ToolTip(DynamicActionColumn column, CoreRow? row)
+    {
+        if (row is null) return null;
+
+        var item = LoadItem(row);
+        if (item.DimensionsMismatch)
+        {
+            return column.TextToolTip($"The dimension size of this holding is '{item.Holding.Dimensions.UnitSize}'.\nIf you allocate from this holding, " +
+                $"the holding will be first converted into '{TargetDimensions?.UnitSize}'");
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    private BitmapImage? Warning_Image(CoreRow? row)
+    {
+        if (row is null) return null;
+        return LoadItem(row).DimensionsMismatch ? _warningImage : null;
+    }
+
+    protected override void OnColumnsLoaded(List<DynamicColumnBase> columns, DynamicGridColumnGroupings groupings)
+    {
+        base.OnColumnsLoaded(columns, groupings);
+
+        if(Items.Any(x => x.DimensionsMismatch))
+        {
+            columns.Insert(0, WarningColumn);
+        }
+    }
+
     public override DynamicGridColumns GenerateColumns()
     {
         var columns = new DynamicGridColumns();
@@ -63,8 +110,10 @@ public class StockSelectionGrid : JobRequisitionItemSelectionGrid<StockSelection
     {
         foreach(var item in Items)
         {
+            var (qty, _) = DimensionUtils.ConvertDimensions(item.Holding.Dimensions.Copy(), item.Holding.Units, item.Holding.AverageValue, Client<ProductDimensionUnit>.Provider);
+
             item.Issued = 0;
-            item.Quantity = item.Holding.Units;
+            item.Quantity = qty; // item.Holding.Units;
             item.MaxValue = item.Quantity;
         }
 
@@ -88,6 +137,8 @@ public partial class StockSelectionPage : ThemableWindow, INotifyPropertyChanged
 
     public JobRequisitionItem Item { get; set; }
 
+    public StockDimensions TargetDimensions { get; set; }
+
     public Guid IssuingJobID { get; set; }
 
     private bool Allocated = false;
@@ -117,11 +168,12 @@ public partial class StockSelectionPage : ThemableWindow, INotifyPropertyChanged
         set => Grid.ShowRequisition = value;
     }
 
-    public StockSelectionPage(IEnumerable<Holding> holdings, JobRequisitionItem item, Job issuingJob, bool allocated = false)
+    public StockSelectionPage(IEnumerable<Holding> holdings, JobRequisitionItem item, Job issuingJob, StockDimensions targetDimensions, bool allocated = false)
     {
         InitializeComponent();
 
         Item = item;
+        TargetDimensions = targetDimensions;
 
         var jris = Client.Query(
             Filter<JobRequisitionItem>.Where(x => x.ID).InList(holdings.Select(x => x.JobRequisitionItemID).Where(x => x != Guid.Empty).ToArray()),
@@ -136,8 +188,10 @@ public partial class StockSelectionPage : ThemableWindow, INotifyPropertyChanged
         {
             JRI = jris.GetValueOrDefault(x.JobRequisitionItemID)
                 ?? new(),
-            Holding = x.StockHolding
+            Holding = x.StockHolding,
+            DimensionsMismatch = !x.StockHolding.Dimensions.Equals(TargetDimensions)
         }).ToList();
+        Grid.TargetDimensions = targetDimensions;
 
         IssuingJobID = issuingJob.ID;
         jobLbl.Text = $"Taking stock from Job: {issuingJob.Name} ({issuingJob.JobNumber})";
@@ -186,23 +240,67 @@ public partial class StockSelectionPage : ThemableWindow, INotifyPropertyChanged
             }
         }
 
+        var batch = new StockMovementBatch
+        {
+            TimeStamp = DateTime.Now,
+            Type = StockMovementBatchType.Transfer,
+            Notes = $"Allocated to Job {Item.Job.JobNumber}"
+        };
+        Client.Save(batch, "Created for requisitioning stock");
+
+        var movements = new List<StockMovement>();
         foreach (var item in Grid.Items.Where(x => x.Issued > 0))
         {
-            CreateStockMovements(item);
+            CreateStockMovements(item, movements, batch);
         }
+        Client.Save(movements, "Created from Reservation Management Screen");
 
         DialogResult = true;
     }
 
-    private void CreateStockMovements(StockSelectionItem item)
+    private void CreateStockMovements(StockSelectionItem item, List<StockMovement> movements, StockMovementBatch batch)
     {
-        var batch = new StockMovementBatch
+        var transaction = Guid.NewGuid();
+
+        if (!item.Holding.Dimensions.Equals(TargetDimensions))
         {
-            TimeStamp = DateTime.Now,
-            Type = StockMovementBatchType.Transfer,
-            Notes = $"Allocated to Job {Item.Job.JobNumber}"
-        };
-        Client.Save(batch, "Created for requisitioning stock");
+            // First convert the holding into the correct dimensions.
+            var oldDimensionsMvt = item.Holding.CreateMovement();
+            oldDimensionsMvt.Dimensions = item.Holding.Dimensions.Copy();
+            oldDimensionsMvt.JobRequisitionItem.ID = item.JRI?.ID ?? Guid.Empty;
+            oldDimensionsMvt.Job.ID = IssuingJobID;
+
+            oldDimensionsMvt.Batch.ID = batch.ID;
+            oldDimensionsMvt.Employee.ID = App.EmployeeID;
+            oldDimensionsMvt.Date = DateTime.Now;
+            oldDimensionsMvt.Product.ID = Item.Product.ID;
+            oldDimensionsMvt.Notes = $"Reservation Management Screen - converting dimensions of holding to allocate to a job requisition item.";
+            oldDimensionsMvt.System = true;
+
+            oldDimensionsMvt.Issued = item.Holding.Units;
+            oldDimensionsMvt.Cost = item.Holding.AverageValue;
+
+            var (qty, cost) = DimensionUtils.ConvertDimensions(item.Holding.Dimensions, item.Holding.Units, item.Holding.AverageValue, Client<ProductDimensionUnit>.Provider);
+
+            var newDimensionsMvt = item.Holding.CreateMovement();
+            newDimensionsMvt.JobRequisitionItem.ID = item.JRI?.ID ?? Guid.Empty;
+            newDimensionsMvt.Job.ID = IssuingJobID;
+
+            newDimensionsMvt.Batch.ID = batch.ID;
+            newDimensionsMvt.Employee.ID = App.EmployeeID;
+            newDimensionsMvt.Date = DateTime.Now;
+            newDimensionsMvt.Product.ID = Item.Product.ID;
+            newDimensionsMvt.Notes = $"Reservation Management Screen - converting dimensions of holding to allocate to a job requisition item.";
+            newDimensionsMvt.System = true;
+
+            newDimensionsMvt.Received = qty;
+            newDimensionsMvt.Cost = cost;
+
+            StockMovement.LinkTransfers(oldDimensionsMvt, newDimensionsMvt, transaction);
+
+            movements.Add(oldDimensionsMvt);
+            movements.Add(newDimensionsMvt);
+        }
 
         var issuing = CreateBaseMovement(item, batch.ID, item.JRI);
         issuing.Job.ID = IssuingJobID;
@@ -214,9 +312,10 @@ public partial class StockSelectionPage : ThemableWindow, INotifyPropertyChanged
         receiving.Received = item.Issued;
         receiving.JobRequisitionItem.ID = Item.ID;
 
-        StockMovement.LinkTransfers(issuing, receiving);
+        StockMovement.LinkTransfers(issuing, receiving, transaction);
 
-        Client.Save(new StockMovement[] { issuing, receiving }, "Created from Reservation Management Screen");
+        movements.Add(issuing);
+        movements.Add(receiving);
     }
 
     private StockMovement CreateBaseMovement(StockSelectionItem item, Guid batchid, JobRequisitionItem? fromJRI)

+ 16 - 2
prs.stores/Utilities/DimensionUtils.cs

@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
 using System.Linq.Expressions;
 using Comal.Classes;
 using InABox.Core;
@@ -10,7 +11,8 @@ public static class DimensionUtils
     private static Dictionary<Guid, ScriptDocument>? _dimensionscriptcache;
     
     public static void ResetDimensionScriptCache() => _dimensionscriptcache = null;
-         
+
+    [MemberNotNull(nameof(_dimensionscriptcache))]
     public static void ReloadDimensionScriptCache(Guid[]? ids, Func<Filter<ProductDimensionUnit>?,Columns<ProductDimensionUnit>,CoreTable> reload)
     {
 
@@ -58,7 +60,7 @@ public static class DimensionUtils
         if (_dimensionscriptcache == null)
             ReloadDimensionScriptCache(null, reload);
         
-        if (_dimensionscriptcache?.TryGetValue(dimensions.Unit.ID, out ScriptDocument? _script) == true)
+        if (_dimensionscriptcache.TryGetValue(dimensions.Unit.ID, out ScriptDocument? _script))
         {
             _script.SetValue("Quantity", quantity);
             _script.SetValue("Cost", cost);
@@ -94,6 +96,18 @@ public static class DimensionUtils
         return (quantity, cost);
     }
 
+    public static bool HasConversion(IDimensions dimensions, Func<Filter<ProductDimensionUnit>?, Columns<ProductDimensionUnit>, CoreTable> reload)
+    {
+        if (_dimensionscriptcache == null)
+            ReloadDimensionScriptCache(null, reload);
+        return _dimensionscriptcache.ContainsKey(dimensions.Unit.ID);
+    }
+
+    public static bool HasConversion(IDimensions dimensions, IQueryProvider<ProductDimensionUnit> query)
+    {
+        return HasConversion(dimensions, (f, c) => query.Query(f, c));
+    }
+
     /// <summary>
     /// Convert the dimensions on a <see cref="PurchaseOrderItem"/>, and adjust
     /// its <see cref="PurchaseOrderItem.Cost"/> and <see