Parcourir la source

Added BillApproval system; improved bill aggregates; added IncTax to ConsignmentLink

Kenric Nugteren il y a 9 mois
Parent
commit
1f660a10aa

+ 57 - 7
prs.classes/Entities/Bill/Bill.cs

@@ -51,32 +51,82 @@ namespace Comal.Classes
         [TimestampEditor]
         [Security(typeof(CanApproveBills), Editable = Editable.Disabled)]
         public DateTime Approved { get; set; }
+
+        [EditorSequence("Additional", 4)]
+        [Editable(Editable.Disabled)]
+        public BillTypeLink BillType { get; set; }
         
-        
+        private class ExTaxFormula : ComplexFormulaGenerator<Bill, double>
+        {
+            public override IComplexFormulaNode<Bill, double> GetFormula() =>
+                Aggregate<BillLine>(AggregateCalculation.Sum, x => x.Property(x => x.ExTax))
+                    .WithLink(x => x.BillLink.ID, x => x.ID);
+        }
         [DoubleEditor(Editable = Editable.Hidden, Summary = Summary.Sum)]
-        [Aggregate(typeof(BillExTax))]
+        [ComplexFormula(typeof(ExTaxFormula))]
         public double ExTax { get; set; }
 
+        private class TaxFormula : ComplexFormulaGenerator<Bill, double>
+        {
+            public override IComplexFormulaNode<Bill, double> GetFormula() =>
+                Aggregate<BillLine>(AggregateCalculation.Sum, x => x.Property(x => x.Tax))
+                    .WithLink(x => x.BillLink.ID, x => x.ID);
+        }
         [DoubleEditor(Editable = Editable.Hidden, Summary = Summary.Sum)]
-        [Aggregate(typeof(BillTax))]
+        [ComplexFormula(typeof(TaxFormula))]
         public double Tax { get; set; }
 
+        private class IncTaxFormula : ComplexFormulaGenerator<Bill, double>
+        {
+            public override IComplexFormulaNode<Bill, double> GetFormula() =>
+                Aggregate<BillLine>(AggregateCalculation.Sum, x => x.Property(x => x.IncTax))
+                    .WithLink(x => x.BillLink.ID, x => x.ID);
+        }
         [DoubleEditor(Editable = Editable.Hidden, Summary = Summary.Sum)]
-        [Aggregate(typeof(BillIncTax))]
+        [ComplexFormula(typeof(IncTaxFormula))]
         public double IncTax { get; set; }
 
+        private class AmountPaidFormula : ComplexFormulaGenerator<Bill, double>
+        {
+            public override IComplexFormulaNode<Bill, double> GetFormula() =>
+                Aggregate<BillPayment>(AggregateCalculation.Sum, x => x.Property(x => x.Amount))
+                    .WithLink(x => x.BillLink.ID, x => x.ID);
+        }
         [CurrencyEditor(Editable = Editable.Hidden, Summary = Summary.Sum)]
-        [Aggregate(typeof(BillAmountPaid))]
+        [ComplexFormula(typeof(AmountPaidFormula))]
         public double AmountPaid { get; set; }
 
+        private class BalanceFormula : ComplexFormulaGenerator<Bill, double>
+        {
+            public override IComplexFormulaNode<Bill, double> GetFormula() =>
+                Formula(FormulaOperator.Subtract, Property(x => x.IncTax), Property(x => x.AmountPaid));
+        }
         [CurrencyEditor(Editable = Editable.Hidden, Summary = Summary.Sum)]
-        [Formula(typeof(BillBalance))]
+        [ComplexFormula(typeof(BalanceFormula))]
         public double Balance { get; set; }
 
-        [Aggregate(typeof(BillDocumentCount))]
+        private class DocumentsFormula : ComplexFormulaGenerator<Bill, int>
+        {
+            public override IComplexFormulaNode<Bill, int> GetFormula() =>
+                Count<BillDocument, Guid>(x => x.Property(x => x.ID))
+                    .WithLink(x => x.EntityLink.ID, x => x.ID);
+        }
+        [ComplexFormula(typeof(DocumentsFormula))]
         [IntegerEditor(Editable = Editable.Hidden)]
         public int Documents { get; set; }
 
+        private class OutstandingApprovalsFormula : ComplexFormulaGenerator<Bill, string>
+        {
+            public override IComplexFormulaNode<Bill, string> GetFormula() =>
+                Aggregate(AggregateCalculation.Concat,
+                    x => x.Property(x => x.Employee.Code),
+                    filter: new Filter<BillApproval>(x => x.Approved).IsEqualTo(DateTime.MinValue))
+                .WithLink(x => x.Bill.ID, x => x.ID);
+        }
+        [ComplexFormula(typeof(OutstandingApprovalsFormula))]
+        [Editable(Editable.Hidden)]
+        public string OutstandingApprovals { get; set; }
+
         [NullEditor]
         [LoggableProperty]
         public DateTime Posted { get; set; }

+ 0 - 80
prs.classes/Entities/Bill/BillAggregates.cs

@@ -1,80 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq.Expressions;
-using InABox.Core;
-
-namespace Comal.Classes
-{
-    public class BillExTax : CoreAggregate<Bill, BillLine, double>
-    {
-        public override Expression<Func<BillLine, double>> Aggregate => x => x.ExTax;
-
-        public override AggregateCalculation Calculation => AggregateCalculation.Sum;
-
-        public override Dictionary<Expression<Func<BillLine, object>>, Expression<Func<Bill, object>>> Links =>
-            new Dictionary<Expression<Func<BillLine, object>>, Expression<Func<Bill, object>>>()
-            {
-                { BillLine => BillLine.BillLink.ID, Bill => Bill.ID }
-            };
-    }
-
-    public class BillTax : CoreAggregate<Bill, BillLine, double>
-    {
-        public override Expression<Func<BillLine, double>> Aggregate => x => x.Tax;
-
-        public override AggregateCalculation Calculation => AggregateCalculation.Sum;
-
-        public override Dictionary<Expression<Func<BillLine, object>>, Expression<Func<Bill, object>>> Links =>
-            new Dictionary<Expression<Func<BillLine, object>>, Expression<Func<Bill, object>>>()
-            {
-                { BillLine => BillLine.BillLink.ID, Bill => Bill.ID }
-            };
-    }
-
-    public class BillIncTax : CoreAggregate<Bill, BillLine, double>
-    {
-        public override Expression<Func<BillLine, double>> Aggregate => x => x.IncTax;
-
-        public override AggregateCalculation Calculation => AggregateCalculation.Sum;
-
-        public override Dictionary<Expression<Func<BillLine, object>>, Expression<Func<Bill, object>>> Links =>
-            new Dictionary<Expression<Func<BillLine, object>>, Expression<Func<Bill, object>>>()
-            {
-                { BillLine => BillLine.BillLink.ID, Bill => Bill.ID }
-            };
-    }
-
-    public class BillAmountPaid : CoreAggregate<Bill, BillPayment, double>
-    {
-        public override Expression<Func<BillPayment, double>> Aggregate => x => x.Amount;
-
-        public override AggregateCalculation Calculation => AggregateCalculation.Sum;
-
-        public override Dictionary<Expression<Func<BillPayment, object>>, Expression<Func<Bill, object>>> Links =>
-            new Dictionary<Expression<Func<BillPayment, object>>, Expression<Func<Bill, object>>>()
-            {
-                { BillPayment => BillPayment.BillLink.ID, Bill => Bill.ID }
-            };
-    }
-
-    public class BillBalance : IFormula<Bill, double>
-    {
-        public Expression<Func<Bill, double>> Value => x => x.IncTax;
-        public Expression<Func<Bill, double>>[] Modifiers => new Expression<Func<Bill, double>>[] { x => x.AmountPaid };
-        public FormulaOperator Operator => FormulaOperator.Subtract;
-        public FormulaType Type => FormulaType.Virtual;
-    }
-
-    public class BillDocumentCount : CoreAggregate<Bill, BillDocument, Guid>
-    {
-        public override Expression<Func<BillDocument, Guid>> Aggregate => x => x.ID;
-
-        public override AggregateCalculation Calculation => AggregateCalculation.Count;
-
-        public override Dictionary<Expression<Func<BillDocument, object>>, Expression<Func<Bill, object>>> Links =>
-            new Dictionary<Expression<Func<BillDocument, object>>, Expression<Func<Bill, object>>>()
-            {
-                { BillDocument => BillDocument.EntityLink.ID, Bill => Bill.ID }
-            };
-    }
-}

+ 30 - 0
prs.classes/Entities/Bill/BillApproval.cs

@@ -0,0 +1,30 @@
+using InABox.Core;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Comal.Classes
+{
+    public class BillApproval : Entity, ISequenceable, IRemotable, IPersistent, IOneToMany<Bill>, ILicense<AccountsPayableLicense>
+    {
+        [EditorSequence(1)]
+        public EmployeeLink Employee { get; set; }
+
+        [NullEditor]
+        public BillLink Bill { get; set; }
+
+        [EditorSequence(2)]
+        public DateTime Approved { get; set; }
+
+        [NullEditor]
+        public long Sequence { get; set; }
+
+        static BillApproval()
+        {
+            DefaultColumns.Add<BillApproval>(x => x.Bill.Number);
+            DefaultColumns.Add<BillApproval>(x => x.Employee.Code);
+            DefaultColumns.Add<BillApproval>(x => x.Employee.Name);
+            DefaultColumns.Add<BillApproval>(x => x.Approved);
+        }
+    }
+}

+ 33 - 0
prs.classes/Entities/Bill/BillType.cs

@@ -0,0 +1,33 @@
+using InABox.Core;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Comal.Classes
+{
+    public class BillType : Entity, IRemotable, IPersistent, ILicense<AccountsPayableLicense>
+    {
+        [UniqueCodeEditor]
+        [EditorSequence(1)]
+        public string Code { get; set; }
+
+        [EditorSequence(2)]
+        public string Description { get; set; }
+
+        static BillType()
+        {
+            DefaultColumns.Add<BillType>(x => x.Code);
+            DefaultColumns.Add<BillType>(x => x.Description);
+        }
+    }
+
+    public class BillTypeLink : EntityLink<BillType>
+    {
+        [LookupEditor(typeof(BillType))]
+        public override Guid ID { get; set; }
+
+        public string Code { get; set; }
+
+        public string Description { get; set; }
+    }
+}

+ 25 - 0
prs.classes/Entities/Bill/BillTypeEmployee.cs

@@ -0,0 +1,25 @@
+using InABox.Core;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Comal.Classes
+{
+    public class BillTypeEmployee : Entity, IRemotable, IPersistent, IManyToMany<BillType, Employee>, ISequenceable, ILicense<AccountsPayableLicense>
+    {
+        public BillTypeLink BillType { get; set; }
+
+        public EmployeeLink Employee { get; set; }
+
+        [NullEditor]
+        public long Sequence { get; set; }
+
+        static BillTypeEmployee()
+        {
+            DefaultColumns.Add<BillTypeEmployee>(x => x.BillType.Code);
+            DefaultColumns.Add<BillTypeEmployee>(x => x.BillType.Description);
+            DefaultColumns.Add<BillTypeEmployee>(x => x.Employee.Code);
+            DefaultColumns.Add<BillTypeEmployee>(x => x.Employee.Name);
+        }
+    }
+}

+ 2 - 2
prs.classes/Entities/Consignment/ConsignmentLink.cs

@@ -20,8 +20,8 @@ namespace Comal.Classes
         [TextBoxEditor(Editable = Editable.Hidden)]
         public string Status { get; set; }
         
-        [NullEditor]
         public double ExTax { get; set; }
-        
+
+        public double IncTax { get; set; }
     }
 }

+ 1 - 1
prs.desktop/Panels/DataEntry/DataEntryPanel.xaml.cs

@@ -235,7 +235,7 @@ public partial class DataEntryPanel : UserControl, IBasePanel, IDynamicEditorHos
         Editor = new EmbeddedDynamicEditorForm();
         
         if (_selectedType == typeof(Bill))
-            Editor.SetLayoutType<SupplierBillEditLayout>();
+            Editor.SetLayoutType<SupplierBillEditDocumentLayout>();
         else
             Editor.SetLayoutType<VerticalDynamicEditorGridLayout>();
         

+ 52 - 0
prs.desktop/Panels/Suppliers/Bills/SupplierBillEditDocumentLayout.xaml

@@ -0,0 +1,52 @@
+<dynamicGrid:DynamicEditorGridLayout
+    x:Class="PRSDesktop.SupplierBillEditDocumentLayout"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+    xmlns:local="clr-namespace:PRSDesktop"
+    xmlns:dynamicGrid="clr-namespace:InABox.DynamicGrid;assembly=InABox.Wpf"
+    xmlns:sf="http://schemas.syncfusion.com/wpf"
+    mc:Ignorable="d" 
+    d:DesignHeight="450" d:DesignWidth="800">
+
+    <Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type local:SupplierBillEditLayout}}}">
+        
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+        </Grid.RowDefinitions>
+        
+        <dynamicGrid:DynamicTabControl 
+            x:Name="Editors"
+            Grid.Row="0"
+            SelectionChanged="Editors_SelectionChanged"
+            SeparatorMargin="4"/>
+        
+        <sf:SfGridSplitter 
+            Grid.Row="1" 
+            Height="4"
+            HorizontalAlignment="Stretch"
+            Background="Transparent"
+            ResizeBehavior="PreviousAndNext"
+            Template="{StaticResource HorizontalSplitter}"
+            PreviewStyle="{StaticResource HorizontalSplitterPreview}"/>
+        
+        <dynamicGrid:DynamicTabControl 
+            x:Name="OtherPages"
+            Grid.Row="2" 
+            Margin="0,0,0,0"
+            SelectionChanged="Editors_SelectionChanged"
+            TabStripPlacement="Bottom">
+            <dynamicGrid:DynamicTabControl.RightPanel>
+                <Border BorderThickness="0" HorizontalAlignment="Stretch" SizeChanged="DocumentWidthChanged">
+                    <ContentControl 
+                        x:Name="DocumentControl"/>
+                </Border>
+            </dynamicGrid:DynamicTabControl.RightPanel>
+        </dynamicGrid:DynamicTabControl>
+        
+        
+    </Grid>
+</dynamicGrid:DynamicEditorGridLayout>

+ 111 - 0
prs.desktop/Panels/Suppliers/Bills/SupplierBillEditDocumentLayout.xaml.cs

@@ -0,0 +1,111 @@
+using Comal.Classes;
+using InABox.DynamicGrid;
+using System.Collections.Generic;
+using System.ComponentModel;
+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.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace PRSDesktop;
+
+/// <summary>
+/// Interaction logic for SupplierBillEditLayout.xaml
+/// </summary>
+public partial class SupplierBillEditDocumentLayout : DynamicEditorGridLayout, INotifyPropertyChanged
+{
+    public override bool TabStripVisible
+    {
+        get { return Editors.TabStripVisible; }
+        set { Editors.TabStripVisible = value; }
+    }
+
+    public override double TotalWidth => 0.0;
+    public override double TotalHeight => 0.0;
+
+    public SupplierBillEditDocumentLayout()
+    {
+        InitializeComponent();
+    }
+
+    public override void LoadPages(IEnumerable<IDynamicEditorPage> pages)
+    {
+        Editors.Items.Clear();
+        OtherPages.Items.Clear();
+        DocumentControl.Content = null;
+
+        foreach (var page in pages.OrderBy(x => x.PageType).ThenBy(x => x.Order).ThenBy(x => x.Caption()))
+        {
+            if(page is DynamicDocumentGrid<BillDocument, Bill, BillLink> docs)
+            {
+
+                docs.SimpleTemplate = true;
+                DocumentControl.Content = docs;
+            }
+            else
+            {
+                var tab = new DynamicTabItem();
+                tab.Header = page.Caption();
+                //if (page is FrameworkElement element)
+                //    element.Margin = new Thickness(0, 2, 0, 0);
+                tab.Content = page;
+
+                if (page is DynamicEditorGrid.DynamicEditPage)
+                {
+                    Editors.Items.Add(tab);
+                }
+                else
+                {
+                    OtherPages.Items.Add(tab);
+                }
+            }
+        }
+
+        Editors.SelectedIndex = 0;
+        OtherPages.SelectedIndex = 0;
+    }
+
+    private bool bChanging;
+
+    public event PropertyChangedEventHandler? PropertyChanged;
+
+    protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
+    {
+        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+    }
+
+    private void Editors_SelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
+        if (bChanging) return;
+        if ((e.OriginalSource != Editors && e.OriginalSource != OtherPages) || e.OriginalSource is not DynamicTabControl tabControl) return;
+        if (tabControl.SelectedItem is not DynamicTabItem tab) return;
+
+        bChanging = true;
+        try
+        {
+            if (tab is not null && tab.Content is IDynamicEditorPage page)
+            {
+                SelectPage(page);
+            }
+        }
+        finally
+        {
+            bChanging = false;
+        }
+    }
+
+    private void DocumentWidthChanged(object sender, SizeChangedEventArgs e)
+    {
+        if (DocumentControl.Content is DynamicDocumentGrid<BillDocument, Bill, BillLink> docs)
+            docs.RowHeight = e.NewSize.Width;
+    }
+}

+ 66 - 17
prs.desktop/Panels/Suppliers/Bills/SupplierBillEditLayout.xaml

@@ -6,26 +6,84 @@
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     xmlns:local="clr-namespace:PRSDesktop"
     xmlns:dynamicGrid="clr-namespace:InABox.DynamicGrid;assembly=InABox.Wpf"
+    xmlns:wpf="clr-namespace:InABox.Wpf;assembly=InABox.Wpf"
     xmlns:sf="http://schemas.syncfusion.com/wpf"
     mc:Ignorable="d" 
     d:DesignHeight="450" d:DesignWidth="800">
+    <dynamicGrid:DynamicEditorGridLayout.Resources>
+        <BoolToVisibilityConverter x:Key="boolVisibilityConverter"/>
+    </dynamicGrid:DynamicEditorGridLayout.Resources>
 
-    <Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type local:SupplierBillEditLayout}}}">
+    <Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type local:SupplierBillEditLayout}}}"
+          SizeChanged="Grid_SizeChanged">
         
         <Grid.RowDefinitions>
-            <RowDefinition Height="Auto"/>
+            <RowDefinition x:Name="EditorRow" Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="*"/>
         </Grid.RowDefinitions>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="*"/>
+            <ColumnDefinition Width="Auto"/>
+            <ColumnDefinition Width="Auto"/>
+        </Grid.ColumnDefinitions>
         
         <dynamicGrid:DynamicTabControl 
             x:Name="Editors"
             Grid.Row="0"
-            SelectionChanged="Editors_SelectionChanged"
-            SeparatorMargin="4"/>
+            SelectionChanged="Editors_SelectionChanged"/>
+        
+        <sf:SfGridSplitter 
+            Grid.Row="0" 
+            Grid.Column="1"
+            Width="4"
+            VerticalAlignment="Stretch"
+            Background="Transparent"
+            ResizeBehavior="PreviousAndNext"
+            Template="{StaticResource VerticalSplitter}"
+            Visibility="{Binding CanApprove,Converter={StaticResource boolVisibilityConverter}}"/>
+
+
+        <Border Grid.Row="0" Grid.Column="2" Margin="5,0,0,0"
+                BorderBrush="Gray" BorderThickness="0.75"
+                Background="White" Padding="7"
+                Visibility="{Binding CanApprove,Converter={StaticResource boolVisibilityConverter}}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                </Grid.RowDefinitions>
+                <Label Content="PO Amount"
+                       Grid.Row="0" HorizontalAlignment="Center"/>
+                <sf:CurrencyTextBox x:Name="POMoneyBox"
+                                    Grid.Row="1" CurrencyDecimalDigits="2"
+                                    Height="25" Width="150"
+                                    Margin="0,0,0,5"
+                                    Background="WhiteSmoke"
+                                    VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
+                                    Value="{Binding POAmount}"/>
+                <Label Content="Bill Amount"
+                       Grid.Row="2" HorizontalAlignment="Center"/>
+                <sf:CurrencyTextBox x:Name="BillMoneyBox"
+                                    Grid.Row="3" CurrencyDecimalDigits="2"
+                                    Height="25" Width="150"
+                                    Margin="0,0,0,10"
+                                    Background="WhiteSmoke"
+                                    VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
+                                    Value="{Binding BillAmount}"/>
+                <Button x:Name="ApproveButton" Grid.Row="4"
+                        Content="Approve"
+                        Width="150"
+                        Padding="5" Click="ApproveButton_Click"/>
+            </Grid>
+        </Border>
         
         <sf:SfGridSplitter 
             Grid.Row="1" 
+            Grid.ColumnSpan="3"
             Height="4"
             HorizontalAlignment="Stretch"
             Background="Transparent"
@@ -33,19 +91,10 @@
             Template="{StaticResource HorizontalSplitter}"
             PreviewStyle="{StaticResource HorizontalSplitterPreview}"/>
         
-        <dynamicGrid:DynamicTabControl 
-            x:Name="OtherPages"
-            Grid.Row="2" 
-            Margin="0,0,0,0"
-            SelectionChanged="Editors_SelectionChanged"
-            TabStripPlacement="Bottom">
-            <dynamicGrid:DynamicTabControl.RightPanel>
-                <Border BorderThickness="0" HorizontalAlignment="Stretch" SizeChanged="DocumentWidthChanged">
-                    <ContentControl 
-                        x:Name="DocumentControl"/>
-                </Border>
-            </dynamicGrid:DynamicTabControl.RightPanel>
-        </dynamicGrid:DynamicTabControl>
+        <dynamicGrid:DynamicTabControl x:Name="OtherPages"
+                                       Grid.Row="2" Grid.ColumnSpan="3"
+                                       SelectionChanged="Editors_SelectionChanged"
+                                       TabStripPlacement="Bottom"/>
         
         
     </Grid>

+ 78 - 27
prs.desktop/Panels/Suppliers/Bills/SupplierBillEditLayout.xaml.cs

@@ -1,5 +1,6 @@
 using Comal.Classes;
 using InABox.DynamicGrid;
+using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Linq;
@@ -23,14 +24,66 @@ namespace PRSDesktop;
 /// </summary>
 public partial class SupplierBillEditLayout : DynamicEditorGridLayout, INotifyPropertyChanged
 {
+    public event Action? Approve;
+
     public override bool TabStripVisible
     {
         get { return Editors.TabStripVisible; }
         set { Editors.TabStripVisible = value; }
     }
 
-    public override double TotalWidth => 0.0;
-    public override double TotalHeight => 0.0;
+    private double _totalWidth;
+    public override double TotalWidth => _totalWidth;
+
+    private double _editorHeight;
+    private double _pageHeight;
+    public override double TotalHeight => _editorHeight + _pageHeight;
+
+    private double _poAmount;
+    public double POAmount
+    {
+        get => _poAmount;
+        set
+        {
+            _poAmount = value;
+            OnPropertyChanged();
+        }
+    }
+
+    private double _billAmount;
+    public double BillAmount
+    {
+        get => _billAmount;
+        set
+        {
+            _billAmount = value;
+            OnPropertyChanged();
+        }
+    }
+
+    private bool _canApprove = false;
+    public bool CanApprove
+    {
+        get => _canApprove;
+        set
+        {
+            _canApprove = value;
+            OnPropertyChanged();
+        }
+    }
+
+    private bool _isApproved = false;
+    public bool IsApproved
+    {
+        get => _isApproved;
+        set
+        {
+            _isApproved = value;
+            ApproveButton.Content = value ? "Unapprove" : "Approve";
+            OnPropertyChanged();
+        }
+    }
+
 
     public SupplierBillEditLayout()
     {
@@ -41,37 +94,31 @@ public partial class SupplierBillEditLayout : DynamicEditorGridLayout, INotifyPr
     {
         Editors.Items.Clear();
         OtherPages.Items.Clear();
-        DocumentControl.Content = null;
-
-        foreach (var page in pages.OrderBy(x => x.PageType).ThenBy(x => x.Order()).ThenBy(x => x.Caption()))
+        foreach (var page in pages.OrderBy(x => x.PageType).ThenBy(x => x.Order).ThenBy(x => x.Caption()))
         {
-            if(page is DynamicDocumentGrid<BillDocument, Bill, BillLink> docs)
-            {
+            var tab = new DynamicTabItem();
+            tab.Header = page.Caption();
+            if (page is FrameworkElement element)
+                element.Margin = new Thickness(0, 2, 0, 0);
+            tab.Content = page;
+
+            var minSize = page.MinimumSize();
 
-                docs.SimpleTemplate = true;
-                DocumentControl.Content = docs;
+            if(page is DynamicEditorGrid.DynamicEditPage)
+            {
+                Editors.Items.Add(tab);
+                _editorHeight = Math.Max(_editorHeight, minSize.Height);
+                _totalWidth = Math.Max(_totalWidth, minSize.Width);
             }
             else
             {
-                var tab = new DynamicTabItem();
-                tab.Header = page.Caption();
-                //if (page is FrameworkElement element)
-                //    element.Margin = new Thickness(0, 2, 0, 0);
-                tab.Content = page;
-
-                if (page is DynamicEditorGrid.DynamicEditPage)
-                {
-                    Editors.Items.Add(tab);
-                }
-                else
-                {
-                    OtherPages.Items.Add(tab);
-                }
+                OtherPages.Items.Add(tab);
+                _pageHeight = Math.Max(_pageHeight, minSize.Height);
             }
+
         }
 
         Editors.SelectedIndex = 0;
-        OtherPages.SelectedIndex = 0;
     }
 
     private bool bChanging;
@@ -103,9 +150,13 @@ public partial class SupplierBillEditLayout : DynamicEditorGridLayout, INotifyPr
         }
     }
 
-    private void DocumentWidthChanged(object sender, SizeChangedEventArgs e)
+    private void Grid_SizeChanged(object sender, SizeChangedEventArgs e)
+    {
+        EditorRow.MaxHeight = e.NewSize.Height - 50;
+    }
+
+    private void ApproveButton_Click(object sender, RoutedEventArgs e)
     {
-        if (DocumentControl.Content is DynamicDocumentGrid<BillDocument, Bill, BillLink> docs)
-            docs.RowHeight = e.NewSize.Width;
+        Approve?.Invoke();
     }
 }

+ 1 - 1
prs.desktop/Panels/Suppliers/Bills/SupplierBillLineGrid.cs

@@ -28,7 +28,6 @@ public class SupplierBillLineGrid : DynamicOneToManyGrid<Bill, BillLine>
 
     public SupplierBillLineGrid()
     {
-
         // AddButton("Import", PRSDesktop.Resources.purchase.AsBitmapImage(), ImportLines);
 
         HiddenColumns.Add(x => x.TaxCode.ID);
@@ -51,6 +50,7 @@ public class SupplierBillLineGrid : DynamicOneToManyGrid<Bill, BillLine>
         
         HiddenColumns.Add(x=>x.Consignment.ID);
         HiddenColumns.Add(x=>x.Consignment.Number);
+        HiddenColumns.Add(x=>x.Consignment.IncTax);
 
         HiddenColumns.Add(x=>x.OrderItem.ID);
         HiddenColumns.Add(x=>x.OrderItem.PurchaseOrderLink.ID);

+ 82 - 8
prs.desktop/Panels/Suppliers/Bills/SupplierBillPanel.xaml.cs

@@ -16,6 +16,7 @@ using System.IO;
 using System.Collections.ObjectModel;
 using System.Windows.Media;
 using System.Runtime.CompilerServices;
+using SuperSocket.ClientEngine;
 
 namespace PRSDesktop;
 
@@ -46,6 +47,8 @@ public partial class SupplierBillPanel : UserControl, IPanel<Bill>, IPropertiesP
 {
     private SupplierBillPanelSettings settings;
 
+    private SupplierBillEditLayout EditLayout;
+
     public SupplierBillPanel()
     {
         InitializeComponent();
@@ -69,6 +72,14 @@ public partial class SupplierBillPanel : UserControl, IPanel<Bill>, IPropertiesP
             () => (DataModel(Selection.Selected) as IDataModel<Bill>)!,
             () => Bills.Refresh(false, true),
             true);
+
+        host.CreateSetupSeparator();
+
+        host.CreateSetupActionIfCanView<BillType>("Bill Types", PRSDesktop.Resources.bill, (action) =>
+        {
+            var list = new MasterList(typeof(BillType));
+            list.ShowDialog();
+        });
     }
 
     public string SectionName => "Supplier Bills";
@@ -98,12 +109,69 @@ public partial class SupplierBillPanel : UserControl, IPanel<Bill>, IPropertiesP
             settings.ViewType == ScreenViewType.Details ? DynamicSplitPanelView.Detail : DynamicSplitPanelView.Combined;
         SplitPanel.AnchorWidth = settings.AnchorWidth;
 
-        Bill.SetLayoutType<VerticalDynamicEditorGridLayout>();
+        EditLayout = new();
+        EditLayout.Approve += EditLayout_Approve;
+
+        Bill.SetLayout(EditLayout);
         Bills.Refresh(true, false);
 
         BillPanelDocumentCache.Cache.ClearOld();
     }
-    
+
+    #region Approval
+
+    private BillApproval? _approval = null;
+
+    private void EditLayout_Approve()
+    {
+        if (_approval is null) return;
+
+        if(_approval.Approved == DateTime.MinValue)
+        {
+            _approval.Approved = DateTime.Now;
+            EditLayout.IsApproved = true;
+            Client.Save(_approval, "Approved by user.");
+        }
+        else
+        {
+            _approval.Approved = DateTime.MinValue;
+            EditLayout.IsApproved = false;
+            Client.Save(_approval, "Unapproved by user.");
+        }
+    }
+
+    private void UpdateApproval()
+    {
+        if(_bills is not null && _bills.Length == 1)
+        {
+            _approval = Client.Query<BillApproval>(
+                new Filter<BillApproval>(x => x.Bill.ID).IsEqualTo(_bills[0].ID)
+                    .And(x => x.Employee.ID).IsEqualTo(App.EmployeeID),
+                Columns.Required<BillApproval>()
+                    .Add(x => x.ID)
+                    .Add(x => x.Approved))
+                .ToObjects<BillApproval>().FirstOrDefault();
+            EditLayout.CanApprove = _approval is not null;
+            EditLayout.IsApproved = _approval is not null ? _approval.Approved != DateTime.MinValue : false;
+
+            var billLinePage = Bill.Pages.OfType<SupplierBillLineGrid>().FirstOrDefault();
+            if(billLinePage is not null)
+            {
+                EditLayout.BillAmount = billLinePage.Items.Sum(x => x.IncTax);
+                EditLayout.POAmount = billLinePage.Items.Sum(x =>
+                {
+                    return x.OrderItem.IncTax + x.Consignment.IncTax;
+                });
+            }
+        }
+        else
+        {
+            _approval = null;
+        }
+    }
+
+    #endregion
+
     private void CheckSaved(CancelEventArgs cancel)
     {
         if (!bChanged)
@@ -158,13 +226,18 @@ public partial class SupplierBillPanel : UserControl, IPanel<Bill>, IPropertiesP
 
     private void ReloadBills()
     {
-        if (Bills.SelectedRows.Length != 0)
+        var newRows = Bills.SelectedRows;
+        if (newRows.Length != 0)
         {
-            _editRows = Bills.SelectedRows;
-            _bills = Bills.LoadBills(_editRows);
-            Bills.InitialiseEditorForm(Bill, _bills, null, true);
-            Bill.Visibility = Visibility.Visible;
-            LinkDocumentPage();
+            if(_editRows is null || !newRows.CompareTo(_editRows))
+            {
+                _editRows = Bills.SelectedRows;
+                _bills = Bills.LoadBills(_editRows);
+                Bills.InitialiseEditorForm(Bill, _bills, null, true);
+                Bill.Visibility = Visibility.Visible;
+                LinkDocumentPage();
+                UpdateApproval();
+            }
         }
         else
         {
@@ -172,6 +245,7 @@ public partial class SupplierBillPanel : UserControl, IPanel<Bill>, IPropertiesP
             _editRows = null;
             Bill.Visibility = Visibility.Hidden;
             ClearDocumentPage();
+            UpdateApproval();
         }
     }
 

+ 102 - 0
prs.desktop/Panels/Suppliers/Bills/SupplierBills.cs

@@ -4,10 +4,12 @@ using System.Linq;
 using System.Windows;
 using System.Windows.Media.Imaging;
 using Comal.Classes;
+using InABox.Clients;
 using InABox.Configuration;
 using InABox.Core;
 using InABox.DynamicGrid;
 using InABox.WPF;
+using NPOI.Util;
 
 namespace PRSDesktop
 {
@@ -27,12 +29,112 @@ namespace PRSDesktop
             HiddenColumns.Add(x => x.Approved);
             HiddenColumns.Add(x => x.DataEntered);
             HiddenColumns.Add(x => x.Checked);
+            HiddenColumns.Add(x => x.BillType.ID);
 
             ActionColumns.Add(new DynamicImageColumn(DataEntered_Image, null) { ToolTip = DataEntered_ToolTip });
             ActionColumns.Add(new DynamicImageColumn(Checked_Image, null) { ToolTip = Checked_ToolTip });
             ActionColumns.Add(new DynamicImageColumn(Approved_Image, null) { ToolTip = Approved_ToolTip });
 
             PostUtils.AddPostColumn(this);
+
+            ActionColumns.Add(new DynamicMenuColumn(BuildMenu));
+        }
+
+        private void BuildMenu(DynamicMenuColumn column, CoreRow? row)
+        {
+            if (row is null) return;
+
+            var menu = column.GetMenu();
+            menu.AddItem("Set Type", null, row, SetBillType);
+        }
+
+        public override DynamicEditorPages LoadEditorPages(Bill item)
+        {
+            // Need to re-order the pages, since we don't want approvals before lines or documents.
+
+            var pages = base.LoadEditorPages(item);
+
+            if(pages.TryGetPage<SupplierBillLineGrid>(out var billLineGrid))
+            {
+                billLineGrid.Order = 0;
+            }
+            if(pages.TryGetPage<DynamicDocumentGrid<BillDocument, Bill, BillLink>>(out var docGrid))
+            {
+                docGrid.Order = 1;
+            }
+            if(pages.TryGetPage<DynamicOneToManyGrid<Bill, BillApproval>>(out var approvalGrid))
+            {
+                approvalGrid.Order = 2;
+            }
+            
+            return pages;
+        }
+
+        private void SetBillType(CoreRow row)
+        {
+            var bill = row.ToObject<Bill>();
+
+            if(MultiSelectDialog<BillType>.SelectItem(
+                out var billType,
+                filter: new Filter<BillType>(x => x.ID).IsNotEqualTo(bill.BillType.ID),
+                columns: Columns.None<BillType>().Add(x => x.ID),
+                title: "Select Bill Type:"))
+            {
+                var oldEmployees = Client.Query(
+                    new Filter<BillTypeEmployee>(x => x.BillType.ID).IsEqualTo(bill.BillType.ID),
+                    Columns.None<BillTypeEmployee>().Add(x => x.Employee.ID),
+                    new SortOrder<BillTypeEmployee>(x => x.Sequence))
+                    .ToArray<BillTypeEmployee>();
+
+                var newEmployees = Client.Query(
+                    new Filter<BillTypeEmployee>(x => x.BillType.ID).IsEqualTo(billType.ID),
+                    Columns.None<BillTypeEmployee>().Add(x => x.Employee.ID),
+                    new SortOrder<BillTypeEmployee>(x => x.Sequence))
+                    .ToArray<BillTypeEmployee>();
+
+                var approvals = Client.Query(
+                    new Filter<BillApproval>(x => x.Bill.ID).IsEqualTo(bill.ID),
+                    Columns.None<BillApproval>()
+                        .Add(x => x.ID)
+                        .Add(x => x.Approved)
+                        .Add(x => x.Employee.ID)
+                        .Add(x => x.Sequence),
+                    new SortOrder<BillApproval>(x => x.Sequence))
+                    .ToArray<BillApproval>();
+
+                var toDelete = new List<BillApproval>();
+                var toSave = new List<BillApproval>();
+                foreach(var employee in newEmployees)
+                {
+                    var newApproval = new BillApproval();
+                    newApproval.Employee.CopyFrom(employee.Employee);
+                    newApproval.Bill.CopyFrom(bill);
+                    toSave.Add(newApproval);
+                }
+                foreach(var approval in approvals)
+                {
+                    if(approval.Approved == DateTime.MinValue
+                        && oldEmployees.Any(x => x.Employee.ID == approval.Employee.ID)
+                        && !newEmployees.Any(x => x.Employee.ID == approval.Employee.ID))
+                    {
+                        toDelete.Add(approval);
+                    }
+                    else
+                    {
+                        toSave.Add(approval);
+                    }
+                }
+                foreach(var (i, approval) in toSave.WithIndex())
+                {
+                    approval.Sequence = i;
+                }
+                Client.Delete(toDelete, "Deleted by changing bill type.");
+                Client.Save(toSave, "Updated when changing bill type.");
+
+                bill.BillType.CopyFrom(billType);
+                SaveItem(bill);
+                Refresh(false, true);
+            }
         }
 
         private FrameworkElement? Approved_ToolTip(DynamicActionColumn column, CoreRow? row)