瀏覽代碼

Improvements to Reservation management screen; better selection window, and allowing negatives and zeroes to be taken from.

Kenric Nugteren 1 年之前
父節點
當前提交
c1070aa518

+ 14 - 0
prs.classes/Entities/Job/Requisitions/JobRequisitionItem.cs

@@ -96,6 +96,20 @@ namespace Comal.Classes
         public override Filter<JobRequisitionItemPurchaseOrderItem>? Filter =>
             new Filter<JobRequisitionItemPurchaseOrderItem>(x => x.PurchaseOrderItem.ReceivedDate).IsEqualTo(null);
     }
+    public class JobRequisitionItemTreatmentRequiredAggregate : CoreAggregate<JobRequisitionItem, StockMovement, double>
+    {
+        public override Expression<Func<JobRequisitionItemPurchaseOrderItem, double>> Aggregate => x => x.PurchaseOrderItem.Qty;
+
+        public override Dictionary<Expression<Func<JobRequisitionItemPurchaseOrderItem, object>>, Expression<Func<JobRequisitionItem, object>>> Links => new Dictionary<Expression<Func<JobRequisitionItemPurchaseOrderItem, object>>, Expression<Func<JobRequisitionItem, object>>>
+        {
+            { x => x.JobRequisitionItem.ID, x => x.ID }
+        };
+
+        public override AggregateCalculation Calculation => AggregateCalculation.Sum;
+
+        public override Filter<JobRequisitionItemPurchaseOrderItem>? Filter =>
+            new Filter<JobRequisitionItemPurchaseOrderItem>(x => x.PurchaseOrderItem.ReceivedDate).IsEqualTo(null);
+    }
 
     public interface IJobRequisitionItem : IEntity
     {

+ 445 - 0
prs.desktop/Panels/Reservation Management/JobRequisitionItemSelectionGrid.cs

@@ -0,0 +1,445 @@
+using Comal.Classes;
+using InABox.Clients;
+using InABox.Core;
+using InABox.DynamicGrid;
+using InABox.WPF;
+using Syncfusion.Data;
+using Syncfusion.UI.Xaml.Grid;
+using Syncfusion.Windows.Shared;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace PRSDesktop.Panels.Reservation_Management;
+
+public class JobRequisitionItemSelectionItem : BaseObject
+{
+    public double Quantity { get; set; }
+
+    private double _issued;
+    public double Issued
+    {
+        get => _issued;
+        set
+        {
+            var oldValue = _issued;
+            var canMinus = CanMinus;
+            var canPlus = CanPlus;
+
+            _issued = value;
+            if(oldValue != value)
+            {
+                OnPropertyChanged(nameof(Issued), oldValue, value);
+            }
+            if(canMinus != CanMinus)
+            {
+                OnPropertyChanged(nameof(CanMinus), canMinus, CanMinus);
+            }
+            if(canPlus != CanPlus)
+            {
+                OnPropertyChanged(nameof(CanPlus), canPlus, CanPlus);
+            }
+        }
+    }
+
+    public double MaxValue { get; set; }
+
+    private bool _editable = true;
+    public bool Editable
+    {
+        get => _editable;
+        set
+        {
+            var oldValue = _editable;
+            var canMinus = CanMinus;
+            var canPlus = CanPlus;
+
+            _editable = value;
+            if(oldValue != value)
+            {
+                OnPropertyChanged(nameof(Editable), oldValue, value);
+            }
+            if(canMinus != CanMinus)
+            {
+                OnPropertyChanged(nameof(CanMinus), canMinus, CanMinus);
+            }
+            if(canPlus != CanPlus)
+            {
+                OnPropertyChanged(nameof(CanPlus), canPlus, CanPlus);
+            }
+        }
+    }
+
+    public bool CanMinus => Editable && Issued > 0;
+
+    public bool CanPlus => Editable && Issued < MaxValue;
+
+    public JobRequisitionItem JRI { get; set; }
+}
+
+public abstract class JobRequisitionItemSelectionGrid<T> : DynamicItemsListGrid<T>, INotifyPropertyChanged
+    where T : JobRequisitionItemSelectionItem, new()
+{
+    private double _totalQuantity;
+    public double TotalQuantity
+    {
+        get => _totalQuantity;
+        set
+        {
+            _totalQuantity = value;
+            OnPropertyChanged();
+        }
+    }
+
+
+    private double _totalIssued;
+    public double TotalIssued
+    {
+        get => _totalIssued;
+        set
+        {
+            _totalIssued = value;
+            OnPropertyChanged();
+        }
+    }
+
+    private bool _observing = true;
+    public void SetObserving(bool observing)
+    {
+        if(_observing != observing)
+        {
+            _observing = observing;
+            if (_observing)
+            {
+                Recalculate();
+            }
+        }
+    }
+
+    protected override void Init()
+    {
+        base.Init();
+
+        ActionColumns.Add(new DynamicTemplateColumn(TotalColumn_Template)
+        {
+            HeaderText = "Total",
+            Width = 60,
+            GetSummary = () =>
+            {
+                var summary = new GridSummaryColumn
+                {
+                    TemplateSelector = new FuncTemplateSelector((item, o) => TotalQuantity_Template())
+                };
+                return summary;
+            }
+        });
+        ActionColumns.Add(new DynamicTemplateColumn(QuantityColumn_Template)
+        {
+            HeaderText = "Quantity",
+            Width = 230,
+            GetSummary = () =>
+            {
+                var summary = new GridSummaryColumn
+                {
+                    TemplateSelector = new FuncTemplateSelector((item, o) => TotalIssued_Template())
+                };
+                return summary;
+            }
+        });
+    }
+
+    protected override void DoReconfigure(DynamicGridOptions options)
+    {
+        base.DoReconfigure(options);
+        options.Clear();
+    }
+
+    private FrameworkElement TotalQuantity_Template()
+    {
+        var quantity = new DoubleTextBox
+        {
+            HorizontalContentAlignment = HorizontalAlignment.Center,
+            VerticalContentAlignment = VerticalAlignment.Center,
+            VerticalAlignment = VerticalAlignment.Stretch,
+            HorizontalAlignment = HorizontalAlignment.Stretch,
+            Background = new SolidColorBrush(Colors.WhiteSmoke),
+            IsReadOnly = true,
+            Margin = new Thickness(0, 5, 0, 5)
+        };
+        quantity.Bind(DoubleTextBox.ValueProperty, this, x => x.TotalQuantity);
+        return quantity;
+    }
+
+    private FrameworkElement TotalIssued_Template()
+    {
+        var quantity = new DoubleTextBox
+        {
+            HorizontalContentAlignment = HorizontalAlignment.Center,
+            VerticalContentAlignment = VerticalAlignment.Center,
+            VerticalAlignment = VerticalAlignment.Stretch,
+            HorizontalAlignment = HorizontalAlignment.Stretch,
+            Background = new SolidColorBrush(Colors.LightGreen),
+            IsReadOnly = true,
+            Margin = new Thickness(50 + 30 + 10, 5, 50 + 30 + 5, 5)
+        };
+        quantity.Bind(DoubleTextBox.ValueProperty, this, x => x.TotalIssued);
+        return quantity;
+    }
+
+    private class TotalAggregate : ISummaryAggregate
+    {
+        public double Sum { get; private set; }
+
+        private JobRequisitionItemSelectionGrid<T> Grid;
+
+        public enum TotalColumn
+        {
+            Quantity,
+            Issued
+        }
+
+        public TotalColumn Type { get; set; }
+
+        public TotalAggregate(TotalColumn type, JobRequisitionItemSelectionGrid<T> grid)
+        {
+            Type = type;
+            Grid = grid;
+        }
+
+        public Action<IEnumerable, string, PropertyDescriptor> CalculateAggregateFunc()
+        {
+            return AggregateFunc;
+        }
+
+        private void AggregateFunc(IEnumerable items, string property, PropertyDescriptor args)
+        {
+            if (items is IEnumerable<DataRowView> rows)
+            {
+                Sum = Type switch
+                {
+                    TotalColumn.Quantity => Grid.TotalQuantity,
+                    TotalColumn.Issued or _ => Grid.TotalIssued
+                };
+            }
+            else
+            {
+                Logger.Send(LogType.Error, "", $"Attempting to calculate aggregate on invalid data type '{items.GetType()}'.");
+            }
+        }
+    }
+
+    private class UIComponent : DynamicGridGridUIComponent<T>
+    {
+        public UIComponent(JobRequisitionItemSelectionGrid<T> grid)
+        {
+            Parent = grid;
+
+            GridLines = DynamicGridLines.None;
+            RowHeight = 40;
+            HeaderRowHeight = 40;
+        }
+
+        protected override Brush? GetCellSelectionBackgroundBrush()
+        {
+            return null;
+        }
+        protected override Brush? GetCellSelectionForegroundBrush()
+        {
+            return null;
+        }
+
+        protected override Brush? GetCellBackground(CoreRow row, DynamicColumnBase column)
+        {
+            return base.GetCellBackground(row, column);
+        }
+
+        protected override Style GetHeaderCellStyle(DynamicColumnBase column)
+        {
+            var style = base.GetHeaderCellStyle(column);
+            style.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Silver)));
+            style.Setters.Add(new Setter(Control.FontWeightProperty, FontWeights.Bold));
+            style.Setters.Add(new Setter(Control.BorderThicknessProperty, new Thickness(0.0)));
+            style.Setters.Add(new Setter(Control.MarginProperty, new Thickness(0.0)));
+            return style;
+        }
+
+        protected override Style GetSummaryRowStyle()
+        {
+            var style = base.GetSummaryRowStyle();
+            style.Setters.Add(new Setter(Control.BorderBrushProperty, new SolidColorBrush(Colors.Silver)));
+            style.Setters.Add(new Setter(Control.BorderThicknessProperty, new Thickness(1, 1, 0, 1)));
+            style.Setters.Add(new Setter(Control.MarginProperty, new Thickness(0, 1, 0, 0)));
+            style.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.WhiteSmoke)));
+            return style;
+        }
+
+        protected override Style GetSummaryCellStyle(DynamicColumnBase column)
+        {
+            var style = base.GetSummaryCellStyle(column);
+
+            style.Setters.Add(new Setter(Control.BorderThicknessProperty, new Thickness(0)));
+            style.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Transparent)));
+            style.Setters.Add(new Setter(GridTableSummaryCell.HorizontalContentAlignmentProperty, HorizontalAlignment.Stretch));
+            style.Setters.Add(new Setter(Control.VerticalContentAlignmentProperty, VerticalAlignment.Stretch));
+            style.Setters.Add(new Setter(Control.PaddingProperty, new Thickness(0)));
+
+            return style;
+        }
+    }
+
+    protected override IDynamicGridUIComponent<T> CreateUIComponent()
+    {
+        return new UIComponent(this);
+    }
+
+    private FrameworkElement? TotalColumn_Template(CoreRow row)
+    {
+        var item = LoadItem(row);
+
+        var quantity = new DoubleTextBox
+        {
+            HorizontalContentAlignment = HorizontalAlignment.Center,
+            VerticalContentAlignment = VerticalAlignment.Center,
+            VerticalAlignment = VerticalAlignment.Stretch,
+            Background = new SolidColorBrush(Colors.WhiteSmoke),
+            IsReadOnly = true,
+            Margin = new Thickness(0, 5, 0, 5)
+        };
+        quantity.Bind(DoubleTextBox.ValueProperty, item, x => x.Quantity);
+        quantity.Bind(Button.IsEnabledProperty, item, x => x.Editable);
+
+        return quantity;
+    }
+
+    private FrameworkElement? QuantityColumn_Template(CoreRow row)
+    {
+        var item = LoadItem(row);
+
+        var grid = new Grid();
+        grid.Margin = new Thickness(5);
+        grid.AddColumn(50);
+        grid.AddColumn(30);
+        grid.AddColumn(60);
+        grid.AddColumn(30);
+        grid.AddColumn(50);
+
+        var none = new Button
+        {
+            Content = "None",
+        };
+        none.Bind(Button.IsEnabledProperty, item, x => x.Editable);
+        none.Margin = new Thickness(0);
+        none.Click += (o, e) => None_Click(item);
+        grid.AddChild(none, 0, 0);
+
+        var minus = new Button
+        {
+            Content = "-",
+        };
+        minus.Bind(Button.IsEnabledProperty, item, x => x.CanMinus);
+        minus.Margin = new Thickness(5, 0, 0, 0);
+        minus.Click += (o, e) => Minus_Click(item);
+        grid.AddChild(minus, 0, 1);
+
+        var quantity = new DoubleTextBox
+        {
+            HorizontalContentAlignment = HorizontalAlignment.Center,
+            VerticalContentAlignment = VerticalAlignment.Center,
+            VerticalAlignment = VerticalAlignment.Stretch,
+            Background = new SolidColorBrush(Colors.LightYellow),
+            Margin = new Thickness(5, 0, 0, 0)
+        };
+        quantity.Bind(DoubleTextBox.ValueProperty, item, x => x.Issued);
+        quantity.MinValue = 0;
+        quantity.Bind(DoubleTextBox.MaxValueProperty, item, x => x.MaxValue);
+        quantity.Bind(Button.IsEnabledProperty, item, x => x.Editable);
+        grid.AddChild(quantity, 0, 2);
+
+        var plus = new Button
+        {
+            Content = "+",
+        };
+        plus.Bind(Button.IsEnabledProperty, item, x => x.CanPlus);
+        plus.Margin = new Thickness(5, 0, 0, 0);
+        plus.Click += (o, e) => Plus_Click(item);
+        grid.AddChild(plus, 0, 3);
+
+        var all = new Button
+        {
+            Content = "All",
+        };
+        all.Bind(Button.IsEnabledProperty, item, x => x.Editable);
+        all.Margin = new Thickness(5, 0, 0, 0);
+        all.Click += (o, e) => All_Click(item);
+        grid.AddChild(all, 0, 4);
+
+        return grid;
+    }
+
+    private void None_Click(T item)
+    {
+        item.Issued = 0;
+    }
+
+    private void Minus_Click(T item)
+    {
+        item.Issued = Math.Max(0, item.Issued - 1);
+    }
+
+    private void Plus_Click(T item)
+    {
+        if(item.JRI.ID == Guid.Empty)
+        {
+            item.Issued += 1;
+        }
+        else
+        {
+            item.Issued = Math.Min(item.Issued + 1, item.Quantity);
+        }
+    }
+
+    private void All_Click(T item)
+    {
+        item.Issued = Math.Max(0, item.Quantity);
+    }
+
+    protected override void Reload(Filters<T> criteria, Columns<T> columns, ref SortOrder<T>? sort, Action<CoreTable?, Exception?> action)
+    {
+        foreach(var item in Items)
+        {
+            item.PropertyChanged += (o, e) => Recalculate();
+        }
+        Recalculate();
+
+        base.Reload(criteria, columns, ref sort, action);
+    }
+
+    public Dictionary<Guid, double> GetQuantities()
+    {
+        return Items.ToDictionary(x => x.JRI.ID, x => x.Issued);
+    }
+    private void Recalculate()
+    {
+        if (!_observing) return;
+
+        TotalQuantity = Items.Sum(x => x.Quantity);
+        TotalIssued = Items.Sum(x => x.Issued);
+    }
+
+    public event PropertyChangedEventHandler? PropertyChanged;
+
+    protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
+    {
+        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+    }
+}

+ 94 - 15
prs.desktop/Panels/Reservation Management/ReservationManagementHoldingsGrid.xaml

@@ -68,17 +68,43 @@
                     Padding="2">
                     <Button 
                         x:Name="currentStyle" 
-                        Content="{Binding UnitsOfCurrentStyle, StringFormat=N0}"
                         Tag="{Binding StockOfCurrentStyle}"
-                        Background="WhiteSmoke"
-                        Click="Take_Click">
+                        Click="Take_Click"
+                        MouseRightButtonUp="Take_RightClick"
+                        Visibility="{Binding Visibility}">
                         <Button.Style>
                             <Style TargetType="Button">
-                                <Setter Property="Visibility" Value="{Binding Visibility}" />
+                                <Setter Property="Content" Value="{Binding UnitsOfCurrentStyle, StringFormat=N0}"/>
+                                <Setter Property="Template">
+                                    <Setter.Value>
+                                        <ControlTemplate TargetType="Button">
+                                            <Border Background="{TemplateBinding Background}" 
+                                                    BorderBrush="{TemplateBinding BorderBrush}"
+                                                    BorderThickness="1">
+                                                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
+                                            </Border>
+                                        </ControlTemplate>
+                                    </Setter.Value>
+                                </Setter>
+                                <Setter Property="Background" Value="Transparent"/>
+                                <Setter Property="BorderBrush" Value="Transparent"/>
                                 <Style.Triggers>
                                     <DataTrigger Binding="{Binding UnitsOfCurrentStyle}" Value="0">
-                                        <Setter Property="Visibility" Value="Collapsed" />
+                                        <Setter Property="Content" Value="-"/>
                                     </DataTrigger>
+                                    <DataTrigger Binding="{Binding CurrentStylePositive}" Value="True">
+                                        <Setter Property="Background" Value="WhiteSmoke"/>
+                                        <Setter Property="BorderBrush" Value="#707070"/>
+                                    </DataTrigger>
+                                    <MultiDataTrigger>
+                                        <MultiDataTrigger.Conditions>
+                                            <Condition Binding="{Binding CurrentStylePositive}" Value="True"/>
+                                            <Condition Binding="{Binding IsMouseOver,RelativeSource={RelativeSource Self}}" Value="True"/>
+                                        </MultiDataTrigger.Conditions>
+                                        <MultiDataTrigger.Setters>
+                                            <Setter Property="Background" Value="LightBlue"/>
+                                        </MultiDataTrigger.Setters>
+                                    </MultiDataTrigger>
                                 </Style.Triggers>
                             </Style>
                         </Button.Style>
@@ -93,17 +119,44 @@
                     Padding="2">
                     <Button 
                         x:Name="noStyle" 
-                        Content="{Binding UnitsOfNoStyle, StringFormat=N0}" 
                         Tag="{Binding StockOfNoStyle}" 
-                        Background="WhiteSmoke"
-                        Click="Take_Click">
+                        Click="Take_Click"
+                        MouseRightButtonUp="Take_RightClick"
+                        Visibility="{Binding Visibility}">
                         <Button.Style>
                             <Style TargetType="Button">
-                                <Setter Property="Visibility" Value="{Binding Visibility}" />
+                                <Setter Property="Content" Value="{Binding UnitsOfNoStyle, StringFormat=N0}"/>
+                                <Setter Property="Template">
+                                    <Setter.Value>
+                                        <ControlTemplate TargetType="Button">
+                                            <Border Background="{TemplateBinding Background}" 
+                                                    BorderBrush="{TemplateBinding BorderBrush}"
+                                                    BorderThickness="1">
+                                                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"
+                                                                  Content="{TemplateBinding Content}"/>
+                                            </Border>
+                                        </ControlTemplate>
+                                    </Setter.Value>
+                                </Setter>
+                                <Setter Property="Background" Value="Transparent"/>
+                                <Setter Property="BorderBrush" Value="Transparent"/>
                                 <Style.Triggers>
                                     <DataTrigger Binding="{Binding UnitsOfNoStyle}" Value="0">
-                                        <Setter Property="Visibility" Value="Collapsed" />
+                                        <Setter Property="Content" Value="-"/>
+                                    </DataTrigger>
+                                    <DataTrigger Binding="{Binding NoStylePositive}" Value="True">
+                                        <Setter Property="Background" Value="WhiteSmoke"/>
+                                        <Setter Property="BorderBrush" Value="#707070"/>
                                     </DataTrigger>
+                                    <MultiDataTrigger>
+                                        <MultiDataTrigger.Conditions>
+                                            <Condition Binding="{Binding NoStylePositive}" Value="True"/>
+                                            <Condition Binding="{Binding IsMouseOver,RelativeSource={RelativeSource Self}}" Value="True"/>
+                                        </MultiDataTrigger.Conditions>
+                                        <MultiDataTrigger.Setters>
+                                            <Setter Property="Background" Value="LightBlue"/>
+                                        </MultiDataTrigger.Setters>
+                                    </MultiDataTrigger>
                                 </Style.Triggers>
                             </Style>
                         </Button.Style>
@@ -119,17 +172,43 @@
                     <Button 
                         Grid.Column="4" 
                         x:Name="otherStyle" 
-                        Content="{Binding UnitsOfOtherStyles, StringFormat=N0}"  
                         Tag="{Binding StockOfOtherStyles}"
-                        Background="WhiteSmoke"
-                        Click="Take_Click">
+                        Click="Take_Click"
+                        MouseRightButtonUp="Take_RightClick"
+                        Visibility="{Binding Visibility}">
                         <Button.Style>
                             <Style TargetType="Button">
-                                <Setter Property="Visibility" Value="{Binding Visibility}" />
+                                <Setter Property="Content" Value="{Binding UnitsOfOtherStyles, StringFormat=N0}"/>
+                                <Setter Property="Template">
+                                    <Setter.Value>
+                                        <ControlTemplate TargetType="Button">
+                                            <Border Background="{TemplateBinding Background}" 
+                                                    BorderBrush="{TemplateBinding BorderBrush}"
+                                                    BorderThickness="1">
+                                                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
+                                            </Border>
+                                        </ControlTemplate>
+                                    </Setter.Value>
+                                </Setter>
+                                <Setter Property="Background" Value="Transparent"/>
+                                <Setter Property="BorderBrush" Value="Transparent"/>
                                 <Style.Triggers>
                                     <DataTrigger Binding="{Binding UnitsOfOtherStyles}" Value="0">
-                                        <Setter Property="Visibility" Value="Collapsed" />
+                                        <Setter Property="Content" Value="-"/>
+                                    </DataTrigger>
+                                    <DataTrigger Binding="{Binding OtherStylesPositive}" Value="True">
+                                        <Setter Property="Background" Value="WhiteSmoke"/>
+                                        <Setter Property="BorderBrush" Value="#707070"/>
                                     </DataTrigger>
+                                    <MultiDataTrigger>
+                                        <MultiDataTrigger.Conditions>
+                                            <Condition Binding="{Binding OtherStylesPositive}" Value="True"/>
+                                            <Condition Binding="{Binding IsMouseOver,RelativeSource={RelativeSource Self}}" Value="True"/>
+                                        </MultiDataTrigger.Conditions>
+                                        <MultiDataTrigger.Setters>
+                                            <Setter Property="Background" Value="LightBlue"/>
+                                        </MultiDataTrigger.Setters>
+                                    </MultiDataTrigger>
                                 </Style.Triggers>
                             </Style>
                         </Button.Style>

+ 34 - 4
prs.desktop/Panels/Reservation Management/ReservationManagementHoldingsGrid.xaml.cs

@@ -2,6 +2,7 @@
 using InABox.Clients;
 using InABox.Core;
 using InABox.Wpf;
+using InABox.WPF;
 using java.sql;
 using org.omg.PortableInterceptor;
 using System;
@@ -109,9 +110,9 @@ public partial class ReservationManagementHoldingsGrid
                 model.StockOfOtherStyles.AddRange(mvts);
             }
         }
-        model.UnitsOfCurrentStyle = Math.Round(Math.Max(model.StockOfCurrentStyle.Sum(x => x.Units), 0));
-        model.UnitsOfNoStyle = Math.Round(Math.Max(model.StockOfNoStyle.Sum(x => x.Units), 0));
-        model.UnitsOfOtherStyles = Math.Round(Math.Max(model.StockOfOtherStyles.Sum(x => x.Units), 0));
+        model.UnitsOfCurrentStyle = Math.Round(model.StockOfCurrentStyle.Sum(x => x.Units));
+        model.UnitsOfNoStyle = Math.Round(model.StockOfNoStyle.Sum(x => x.Units));
+        model.UnitsOfOtherStyles = Math.Round(model.StockOfOtherStyles.Sum(x => x.Units));
     }
 
     private void CalculateHoldings()
@@ -232,11 +233,38 @@ public partial class ReservationManagementHoldingsGrid
         listViewRed.ItemsSource = redList;
     }
 
+    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
             || element.DataContext is not ReservationManagementHoldingsModel model
             || element.Tag is not List<StockMovement> mvts) return;
+
+        if (mvts == model.StockOfCurrentStyle && model.UnitsOfCurrentStyle <= 0
+            || mvts == model.StockOfNoStyle && model.UnitsOfNoStyle <= 0
+            || mvts == model.StockOfOtherStyles && model.UnitsOfOtherStyles <= 0)
+        {
+            return;
+        }
+
         LaunchStockSelectionPage(model, mvts);
     }
 
@@ -275,7 +303,9 @@ public partial class ReservationManagementHoldingsGrid
                 // Shouldn't be possible.
             }
         }
-        var filteredHoldings = holdings.Where(x => x.Value.Units.IsEffectivelyGreaterThan(0)).Select(x => new StockSelectionPage.Holding(x.Value, x.Key.requiItemID));
+
+        var filteredHoldings = holdings.Where(x => !model.AlreadyAllocated || x.Value.Units.IsEffectivelyGreaterThan(0))
+            .Select(x => new StockSelectionPage.Holding(x.Value, x.Key.requiItemID));
 
         var page = new StockSelectionPage(
             filteredHoldings,

+ 4 - 0
prs.desktop/Panels/Reservation Management/ReservationManagementHoldingsModel.cs

@@ -20,6 +20,10 @@ public class ReservationManagementHoldingsModel
     public double UnitsOfNoStyle { get; set; }
     public double UnitsOfOtherStyles { get; set; }
 
+    public bool CurrentStylePositive => UnitsOfCurrentStyle > 0;
+    public bool NoStylePositive => UnitsOfNoStyle > 0;
+    public bool OtherStylesPositive => UnitsOfOtherStyles > 0;
+
     public bool Empty => UnitsOfCurrentStyle <= 0 && UnitsOfNoStyle <= 0 && UnitsOfOtherStyles <= 0;
 
     public bool AlreadyAllocated { get; set; }

+ 17 - 120
prs.desktop/Panels/Reservation Management/StockSelectionPage.xaml

@@ -7,7 +7,9 @@
         xmlns:wpf="clr-namespace:InABox.Wpf;assembly=InABox.Wpf"
         xmlns:WPF="clr-namespace:InABox.WPF;assembly=InABox.Wpf"
         mc:Ignorable="d" Title="Requisition Stock for Line"
-        Height="500" Width="1400"
+        Width="1000" WindowStartupLocation="CenterScreen"
+                    SizeToContent="Height"
+                    MaxHeight="600"
                     x:Name="Window">
     <wpf:ThemableWindow.Resources>
         <WPF:BooleanToVisibilityConverter x:Key="boolToVisibilityConverter" TrueValue="Visible" FalseValue="Collapsed"/>
@@ -15,7 +17,6 @@
 
     <Grid DataContext="{Binding ElementName=Window}">
         <Grid.RowDefinitions>
-            <RowDefinition Height="auto"/>
             <RowDefinition Height="auto"/>
             <RowDefinition Height="*"/>
             <RowDefinition Height="auto"/>
@@ -25,123 +26,19 @@
             <TextBlock x:Name="jobLbl"  Text="Job" HorizontalAlignment="Center" FontSize="14" VerticalAlignment="Center" FontWeight="DemiBold"/>
         </Border>
 
-        <Grid Grid.Row="1">
-            <Grid.ColumnDefinitions>
-                <ColumnDefinition Width="0.4*"/>
-                <ColumnDefinition>
-                    <ColumnDefinition.Style>
-                        <Style TargetType="ColumnDefinition">
-                            <Setter Property="Width" Value="0"/>
-                            <Style.Triggers>
-                                <DataTrigger Binding="{Binding ElementName=Window,Path=ShowRequisition}" Value="True">
-                                    <Setter Property="Width" Value="0.4*"/>
-                                </DataTrigger>
-                            </Style.Triggers>
-                        </Style>
-                    </ColumnDefinition.Style>
-                </ColumnDefinition>
-                <ColumnDefinition Width="0.4*"/>
-                <ColumnDefinition Width="0.6*"/>
-                <ColumnDefinition Width="0.1*" MinWidth="100"/>
-                <ColumnDefinition Width="30"/>
-                <ColumnDefinition Width="60"/>
-                <ColumnDefinition Width="30"/>
-                <ColumnDefinition Width="40"/>
-                <ColumnDefinition Width="8"/>
-            </Grid.ColumnDefinitions>
-            <TextBlock Grid.Column="0" Text="Area" HorizontalAlignment="Center" VerticalAlignment="Center" FontWeight="DemiBold" FontSize="14"/>
-            <TextBlock Grid.Column="1" Text="Requisition Item" HorizontalAlignment="Center" VerticalAlignment="Center" FontWeight="DemiBold" FontSize="14"/>
-            <TextBlock Grid.Column="2" Text="Location" HorizontalAlignment="Center" VerticalAlignment="Center" FontWeight="DemiBold" FontSize="14"/>
-            <TextBlock Grid.Column="3" Text="Style" HorizontalAlignment="Center" VerticalAlignment="Center" FontWeight="DemiBold" FontSize="14"/>
-            <TextBlock Grid.Column="4" x:Name="availableUnitsLbl" Text="Available Units" HorizontalAlignment="Center" VerticalAlignment="Center" TextWrapping="Wrap" TextAlignment="Center"
-                       FontWeight="DemiBold" FontSize="14" Margin="0,0,0,0"/>
-            <TextBlock x:Name="allocateLabel" Text="Allocate"
-                       Grid.Column="5" Grid.ColumnSpan="3"
-                       HorizontalAlignment="Center" TextAlignment="Center"
-                       VerticalAlignment="Center"
-                       FontWeight="DemiBold" FontSize="14" TextWrapping="Wrap"/>
-        </Grid>
-
-        <ListView x:Name="listView" ItemsSource="{Binding ViewModels}"
-                  Grid.Row="2" Margin="0"
-                  HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Center">
-            <ListView.ItemTemplate>
-                <DataTemplate DataType="local:StockSelectionViewModel">
-                    <Grid IsEnabled="{Binding ElementName=Window,Path=CanEdit}">
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="0.4*"/>
-                            <ColumnDefinition>
-                                <ColumnDefinition.Style>
-                                    <Style TargetType="ColumnDefinition">
-                                        <Setter Property="Width" Value="0"/>
-                                        <Style.Triggers>
-                                            <DataTrigger Binding="{Binding ElementName=Window,Path=ShowRequisition}" Value="True">
-                                                <Setter Property="Width" Value="0.4*"/>
-                                            </DataTrigger>
-                                        </Style.Triggers>
-                                    </Style>
-                                </ColumnDefinition.Style>
-                            </ColumnDefinition>
-                            <ColumnDefinition Width="0.4*"/>
-                            <ColumnDefinition Width="0.6*"/>
-                            <ColumnDefinition Width="0.1*" MinWidth="100"/>
-                            <ColumnDefinition Width="30"/>
-                            <ColumnDefinition Width="60"/>
-                            <ColumnDefinition Width="30"/>
-                            <ColumnDefinition Width="40"/>
-                        </Grid.ColumnDefinitions>
-                        <Border Grid.Column="0" BorderThickness="0.75" BorderBrush="Gray" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
-                            <TextBlock Text="{Binding Area}" HorizontalAlignment="Center" TextAlignment="Center"
-                                           VerticalAlignment="Center" TextWrapping="Wrap"/>
-                        </Border>
-                        
-                        <Border Grid.Column="1" BorderThickness="0.75" BorderBrush="Gray" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
-                            <TextBlock Text="{Binding Requisition}" HorizontalAlignment="Center" TextAlignment="Center"
-                                       Visibility="{Binding ElementName=Window,Path=ShowRequisition,Converter={StaticResource boolToVisibilityConverter}}"
-                                           VerticalAlignment="Center" TextWrapping="Wrap"/>
-                        </Border>
-                        <Border Grid.Column="2" BorderThickness="0.75" BorderBrush="Gray" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
-                            <TextBlock Text="{Binding Location}" HorizontalAlignment="Center" TextAlignment="Center"
-                                           VerticalAlignment="Center" TextWrapping="Wrap"/>
-                        </Border>
-                        <Border Grid.Column="3" BorderThickness="0.75" BorderBrush="Gray" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
-                            <TextBlock Text="{Binding Style}" HorizontalAlignment="Center" TextAlignment="Center"
-                                           VerticalAlignment="Center" TextWrapping="Wrap"/>
-                        </Border>
-                        <Border Grid.Column="4" BorderThickness="0.75" BorderBrush="Gray" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
-                            <TextBlock Text="{Binding Units}" HorizontalAlignment="Center" TextAlignment="Center"
-                                           VerticalAlignment="Center" TextWrapping="Wrap"/>
-                        </Border>
-
-
-                        <Button Grid.Column="5" Content="-" Width="25"  Height="25" Click="Minus_Click" FontWeight="Bold"
-                                Tag="{Binding}"/>
-                        <TextBox Grid.Column="6" Width="60" Height="25"
-                                 TextChanged="TextBox_TextChanged"
-                                 HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
-                                 HorizontalAlignment="Center" VerticalAlignment="Center"
-                                 Text="{Binding ChosenUnits}"
-                                 Tag="{Binding}"/>
-                        
-                        <Button Grid.Column="7" Content="+"  Width="25"  Height="25" Click="Plus_Click" FontWeight="Bold"
-                                Tag="{Binding}"/>
-                        <Button Grid.Column="8" Content="All" Width="40"  Height="25" Click="All_Clicked"
-                                Tag="{Binding}"/>
-
-                    </Grid>
-                </DataTemplate>
-            </ListView.ItemTemplate>
-        </ListView>
-
-        <!-- Accept or Cancel -->
-        <StackPanel Grid.Row="3"  Orientation="Horizontal" HorizontalAlignment="Right">
-
-            <Button Name="okButton" Click="SaveButton_Click" Margin="5" FontSize="13" Width="90" Height="30"
-                    IsDefault="True" Content="Save" IsEnabled="False"/>
-
-            <Button Name="cancelButton" IsCancel="True" Margin="5" FontSize="13" Width="90" Height="30"
-                    Click="Cancel_Click" Content="Cancel"/>
-
-        </StackPanel>
+        <local:StockSelectionGrid x:Name="Grid" Grid.Row="1" Margin="5"
+                                  PropertyChanged="Grid_PropertyChanged"/>
+
+        <DockPanel Grid.Row="2" LastChildFill="False">
+            <Button x:Name="CancelButton" Click="CancelButton_Click"
+                    Content="Cancel"
+                    Margin="5" Padding="5" MinWidth="60"
+                    DockPanel.Dock="Right"/>
+            <Button x:Name="OKButton" Click="OKButton_Click"
+                    Content="OK"
+                    Margin="5,5,0,5" Padding="5" MinWidth="60"
+                    DockPanel.Dock="Right"
+                    IsEnabled="{Binding CanSave}"/>
+        </DockPanel>
     </Grid>
 </wpf:ThemableWindow>

+ 89 - 120
prs.desktop/Panels/Reservation Management/StockSelectionPage.xaml.cs

@@ -11,11 +11,60 @@ using com.sun.tools.javac.util;
 using Comal.Classes;
 using InABox.Clients;
 using InABox.Core;
+using InABox.DynamicGrid;
 using InABox.Wpf;
 using javax.swing;
+using PRSDesktop.Panels.Reservation_Management;
 
 namespace PRSDesktop;
 
+public class StockSelectionItem : JobRequisitionItemSelectionItem
+{
+    public string Area => Holding.Location.Area.Description;
+
+    public string Requisition => JRI is null
+        ? "Unrequisitioned Items"
+        : $"{JRI.Job.JobNumber}: {JRI.Requisition.Number} {JRI.Requisition.Description}";
+
+    public string Location => Holding.Location.Description;
+
+    public string Style => Holding.Style.Description;
+
+    public StockHolding Holding { get; set; }
+}
+
+public class StockSelectionGrid : JobRequisitionItemSelectionGrid<StockSelectionItem>
+{
+    public bool ShowRequisition { get; set; } = false;
+
+    public override DynamicGridColumns GenerateColumns()
+    {
+        var columns = new DynamicGridColumns();
+        columns.Add<StockSelectionItem, string>(x => x.Area, 0, "Area", "", Alignment.MiddleCenter);
+        columns.Add<StockSelectionItem, string>(x => x.Location, 0, "Location", "", Alignment.MiddleCenter);
+        columns.Add<StockSelectionItem, string>(x => x.Style, 0, "Style", "", Alignment.MiddleCenter);
+
+        if (ShowRequisition)
+        {
+            columns.Add<StockSelectionItem, string>(x => x.Requisition, 0, "Requisition", "", Alignment.MiddleLeft);
+        }
+
+        return columns;
+    }
+
+    protected override void Reload(Filters<StockSelectionItem> criteria, Columns<StockSelectionItem> columns, ref SortOrder<StockSelectionItem>? sort, Action<CoreTable?, Exception?> action)
+    {
+        foreach(var item in Items)
+        {
+            item.Issued = 0;
+            item.Quantity = item.Holding.Units;
+            item.MaxValue = item.JRI.ID == Guid.Empty ? double.MaxValue : item.Quantity;
+        }
+
+        base.Reload(criteria, columns, ref sort, action);
+    }
+}
+
 /// <summary>
 /// Interaction logic for JobRequisitionStockSelectionPage.xaml
 /// </summary>
@@ -28,16 +77,14 @@ public partial class StockSelectionPage : ThemableWindow, INotifyPropertyChanged
         public Guid JobRequisitionItemID { get; set; } = jobRequisitionItemID;
     }
 
-    public ObservableCollection<StockSelectionViewModel> ViewModels { get; } = new ObservableCollection<StockSelectionViewModel>();
+    public bool CanSave => Grid.TotalIssued > 0;
 
-    private readonly JobRequisitionItem Item;
+    public JobRequisitionItem Item { get; set; }
 
     public Guid IssuingJobID { get; set; }
 
     private bool Allocated = false;
 
-    public double TotalChosen => ViewModels.Sum(x => x.ChosenUnits);
-
     private bool _canEdit = true;
     public bool CanEdit
     {
@@ -45,23 +92,23 @@ public partial class StockSelectionPage : ThemableWindow, INotifyPropertyChanged
         private set
         {
             _canEdit = value;
-            OnPropertyChanged();
+            foreach(var item in Grid.Items)
+            {
+                item.Editable = value;
+            }
         }
     }
 
-    private bool _showRequisition = false;
     public bool ShowRequisition
     {
-        get => _showRequisition;
-        set
-        {
-            _showRequisition = value;
-            OnPropertyChanged();
-        }
+        get => Grid.ShowRequisition;
+        set => Grid.ShowRequisition = value;
     }
 
     public StockSelectionPage(IEnumerable<Holding> holdings, JobRequisitionItem item, Job issuingJob, bool allocated = false)
     {
+        InitializeComponent();
+
         Item = item;
 
         var jris = Client.Query(
@@ -73,12 +120,12 @@ public partial class StockSelectionPage : ThemableWindow, INotifyPropertyChanged
                 .Add(x => x.Requisition.Description))
             .ToObjects<JobRequisitionItem>().ToDictionary(x => x.ID);
 
-        foreach (var holding in holdings)
+        Grid.Items = holdings.Select(x => new StockSelectionItem
         {
-            ViewModels.Add(new StockSelectionViewModel(holding.StockHolding, jris.GetValueOrDefault(holding.JobRequisitionItemID)));
-        }
-
-        InitializeComponent();
+            JRI = jris.GetValueOrDefault(x.JobRequisitionItemID)
+                ?? new(),
+            Holding = x.StockHolding
+        }).ToList();
 
         IssuingJobID = issuingJob.ID;
         jobLbl.Text = $"Taking stock from Job: {issuingJob.Name} ({issuingJob.JobNumber})";
@@ -89,17 +136,27 @@ public partial class StockSelectionPage : ThemableWindow, INotifyPropertyChanged
         {
             CanEdit = Security.IsAllowed<CanEditAllocatedJobRequisitions>();
             ShowRequisition = true;
-            availableUnitsLbl.Text = "Allocated";
+            // availableUnitsLbl.Text = "Allocated";
             if (CanEdit)
             {
-                allocateLabel.Text = "Take";
+                // allocateLabel.Text = "Take";
             }
         }
+
+        Grid.Refresh(true, true);
+    }
+
+    private void Grid_PropertyChanged(object sender, PropertyChangedEventArgs e)
+    {
+        if(e.PropertyName == nameof(Grid.TotalIssued))
+        {
+            OnPropertyChanged(nameof(CanSave));
+        }
     }
 
-    private void SaveButton_Click(object sender, RoutedEventArgs e)
+    private void OKButton_Click(object sender, RoutedEventArgs e)
     {
-        if(TotalChosen <= 0)
+        if(Grid.TotalIssued <= 0)
         {
             MessageWindow.ShowMessage("Please select from at least one holding to reserve.", "Error", image: MessageWindow.WarningImage);
             return;
@@ -115,17 +172,15 @@ public partial class StockSelectionPage : ThemableWindow, INotifyPropertyChanged
             }
         }
 
-        var models = ViewModels.Where(x => x.ChosenUnits > 0);
-
-        foreach (var model in models)
+        foreach (var item in Grid.Items.Where(x => x.Issued > 0))
         {
-            CreateStockMovements(model);
+            CreateStockMovements(item);
         }
 
         DialogResult = true;
     }
 
-    private void CreateStockMovements(StockSelectionViewModel model)
+    private void CreateStockMovements(StockSelectionItem item)
     {
         var batch = new StockMovementBatch
         {
@@ -135,15 +190,15 @@ public partial class StockSelectionPage : ThemableWindow, INotifyPropertyChanged
         };
         Client.Save(batch, "Created for requisitioning stock");
 
-        var issuing = CreateBaseMovement(model, batch.ID, model.JRI);
+        var issuing = CreateBaseMovement(item, batch.ID, item.JRI);
         issuing.Job.ID = IssuingJobID;
-        issuing.Issued = model.ChosenUnits;
-        issuing.JobRequisitionItem.ID = model.JRI?.ID ?? Guid.Empty;
+        issuing.Issued = item.Issued;
+        issuing.JobRequisitionItem.ID = item.JRI?.ID ?? Guid.Empty;
         issuing.Type = StockMovementType.TransferOut;
 
-        var receiving = CreateBaseMovement(model, batch.ID, model.JRI);
+        var receiving = CreateBaseMovement(item, batch.ID, item.JRI);
         receiving.Job.ID = Item.Job.ID;
-        receiving.Received = model.ChosenUnits;
+        receiving.Received = item.Issued;
         receiving.JobRequisitionItem.ID = Item.ID;
         receiving.Type = StockMovementType.TransferIn;
         receiving.Transaction = issuing.Transaction;
@@ -151,9 +206,9 @@ public partial class StockSelectionPage : ThemableWindow, INotifyPropertyChanged
         Client.Save(new StockMovement[] { issuing, receiving }, "Created from Reservation Management Screen");
     }
 
-    private StockMovement CreateBaseMovement(StockSelectionViewModel model, Guid batchid, JobRequisitionItem? fromJRI)
+    private StockMovement CreateBaseMovement(StockSelectionItem item, Guid batchid, JobRequisitionItem? fromJRI)
     {
-        var mvt = model.Holding.CreateMovement();
+        var mvt = item.Holding.CreateMovement();
 
         mvt.Batch.ID = batchid;
         mvt.Employee.ID = App.EmployeeID;
@@ -173,98 +228,12 @@ public partial class StockSelectionPage : ThemableWindow, INotifyPropertyChanged
         return mvt;
     }
 
-    private void Cancel_Click(object sender, RoutedEventArgs e)
+    private void CancelButton_Click(object sender, RoutedEventArgs e)
     {
         DialogResult = false;
     }
 
-    private void TextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
-    {
-        if (sender is not TextBox box || box.Tag is not StockSelectionViewModel model) return;
-
-        if (box.Text.IsNullOrWhiteSpace()) return;
-
-        if (double.TryParse(box.Text, out double value))
-        {
-            model.ChosenUnits = value;
-            OnPropertyChanged(nameof(TotalChosen));
-        }
-        else
-        {
-            // This'll call the event handler again.
-            box.Text = "0";
-        }
-    }
-
-    private void All_Clicked(object sender, RoutedEventArgs e)
-    {
-        if (sender is not FrameworkElement element || element.Tag is not StockSelectionViewModel model) return;
-
-        model.ChosenUnits = model.Holding.Units;
-        OnPropertyChanged(nameof(TotalChosen));
-    }
-
-    private void Minus_Click(object sender, RoutedEventArgs e)
-    {
-        if (sender is not FrameworkElement element || element.Tag is not StockSelectionViewModel model) return;
-        
-        model.ChosenUnits--;
-        OnPropertyChanged(nameof(TotalChosen));
-    }
-
-    private void Plus_Click(object sender, RoutedEventArgs e)
-    {
-        if (sender is not FrameworkElement element || element.Tag is not StockSelectionViewModel model) return;
-
-        model.ChosenUnits++;
-        OnPropertyChanged(nameof(TotalChosen));
-    }
-
-    public event PropertyChangedEventHandler? PropertyChanged;
-
-    protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
-    {
-        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
-
-        if(propertyName == nameof(TotalChosen))
-        {
-            okButton.IsEnabled = TotalChosen > 0;
-        }
-    }
-}
-
-public class StockSelectionViewModel : INotifyPropertyChanged
-{
     public event PropertyChangedEventHandler? PropertyChanged;
-    public string Requisition => JRI is null
-        ? "Unrequisitioned Items"
-        : $"{JRI.Job.JobNumber}: {JRI.Requisition.Number} {JRI.Requisition.Description}";
-    public string Location => Holding.Location.Description;
-    public string Area => Holding.Location.Area.Description;
-    public string Style => Holding.Style.Description;
-    public double Units => Holding.Units;
-
-    private double _chosenUnits;
-    public double ChosenUnits
-    {
-        get => _chosenUnits;
-        set
-        {
-            _chosenUnits = Math.Clamp(value, 0, Holding.Units);
-            OnPropertyChanged();
-        }
-    }
-
-    public StockHolding Holding { get; set; }
-
-    public JobRequisitionItem? JRI { get; set; }
-
-    public StockSelectionViewModel(StockHolding holding, JobRequisitionItem? jri)
-    {
-        Holding = holding;
-        JRI = jri;
-        ChosenUnits = 0;
-    }
 
     protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
     {