فهرست منبع

Implement ForeignCurrency class for Suppliers and SupplierProducts

frogsoftware 11 ماه پیش
والد
کامیت
808684eda8

+ 27 - 0
prs.classes/Entities/ForeignCurrency/ForeignCurrency.cs

@@ -0,0 +1,27 @@
+using InABox.Core;
+
+namespace Comal.Classes
+{
+    public class ForeignCurrency : Entity, IRemotable, IPersistent, IForeignCurrency
+    {
+        [UniqueCodeEditor(Visible = Visible.Default, Editable = Editable.Enabled)]
+        [EditorSequence(1)]
+        public string Code { get; set; }
+        
+        [TextBoxEditor]
+        [EditorSequence(2)]
+        public string Description { get; set; }
+        
+        [TextBoxEditor]
+        [EditorSequence(3)]
+        public string Identifier { get; set; }
+        
+        [DoubleEditor]
+        [EditorSequence(4)]
+        public double ExchangeRate { get; set; }
+        
+        [CheckBoxEditor(Visible=Visible.Hidden, Editable = Editable.Enabled)]
+        [EditorSequence(5)]
+        public bool Active { get; set; }
+    }
+}

+ 28 - 0
prs.classes/Entities/ForeignCurrency/ForeignCurrencyLink.cs

@@ -0,0 +1,28 @@
+using System;
+using InABox.Core;
+
+namespace Comal.Classes
+{
+    public class ForeignCurrencyLink : EntityLink<ForeignCurrency>, IForeignCurrency, IRemotable, IPersistent
+    {
+        [RequiredColumn]
+        [CodePopupEditor(typeof(ForeignCurrency))]
+        public override Guid ID { get; set; }
+        
+        [CodeEditor(Visible = Visible.Default, Editable = Editable.Hidden)]
+        public string Code { get; set; }    
+        
+        [TextBoxEditor(Visible=Visible.Optional,Editable=Editable.Hidden)]
+        public string Description { get; set; }
+        
+        [TextBoxEditor(Visible=Visible.Optional,Editable=Editable.Hidden)]
+        public string Identifier { get; set; }
+        
+        [RequiredColumn]
+        [DoubleEditor(Visible=Visible.Optional, Editable=Editable.Hidden)]
+        public double ExchangeRate { get; set; }
+        
+        [CheckBoxEditor]
+        public bool Active { get; set; }
+    }
+}

+ 28 - 0
prs.classes/Entities/ForeignCurrency/ForeignCurrencyLookups.cs

@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using InABox.Core;
+
+namespace Comal.Classes
+{
+    public class ForeignCurrencyLookups : EntityLookup<ForeignCurrency>
+    {
+        public override Filter<ForeignCurrency>? DefineFilter()
+        {
+            return new Filter<ForeignCurrency>(x => x.Active).IsEqualTo(true);
+        }
+
+        public override SortOrder<ForeignCurrency> DefineSortOrder()
+        {
+            return new SortOrder<ForeignCurrency>(x => x.Code);
+        }
+
+        public override Columns<ForeignCurrency> DefineColumns()
+        {
+            return base.DefineColumns().Add(x=>x.ID).Add(x=>x.Description).Add(x=>x.ExchangeRate);
+        }
+
+        public override string FormatLookup(Dictionary<string, object?> values, IEnumerable<string> exclude)
+        {
+            return $"{values[nameof(ForeignCurrency.Description)]} ({values[nameof(ForeignCurrency.ExchangeRate)]:F2})";
+        }
+    }
+}

+ 12 - 0
prs.classes/Entities/ForeignCurrency/IForeignCurrency.cs

@@ -0,0 +1,12 @@
+namespace Comal.Classes
+{
+    public interface IForeignCurrency
+    {
+        string Code { get; set; }
+        string Description { get; set; }
+        string Identifier { get; set; }
+        double ExchangeRate { get; set; }
+        
+        bool Active { get; set; }
+    }
+}

+ 6 - 1
prs.classes/Entities/Supplier/Supplier.cs

@@ -90,7 +90,10 @@ namespace Comal.Classes
         
         [EditorSequence("Accounts",2)]
         public Address Postal { get; set; }
-
+        
+        [EditorSequence("Accounts",3)]
+        public ForeignCurrencyLink Currency { get; set; }
+        
         [CurrencyEditor(Editable = Editable.Hidden, Summary = Summary.Sum)]
         [Aggregate(typeof(SupplierOrderBalance))]
         public double OrderBalance { get; set; }
@@ -124,5 +127,7 @@ namespace Comal.Classes
         {
             return string.Format("{0}: {1}", Code, Name);
         }
+
+        
     }
 }

+ 3 - 1
prs.classes/Entities/Supplier/SupplierLink.cs

@@ -14,7 +14,9 @@ namespace Comal.Classes
         [TextBoxEditor(Visible = Visible.Optional, Editable = Editable.Hidden)]
         public string Name { get; set; }
         
-        [EditorSequence(6)]
         public SupplierCategoryLink Category { get; set; }
+        
+        [RequiredColumn]
+        public ForeignCurrencyLink Currency { get; set; }
     }
 }

+ 26 - 12
prs.classes/Entities/Supplier/SupplierProduct.cs

@@ -9,6 +9,7 @@ namespace Comal.Classes
     {
         private bool bChanging;
 
+        [RequiredColumn]
         [EditorSequence(0)]
         public SupplierLink SupplierLink { get; set; }
 
@@ -48,28 +49,32 @@ namespace Comal.Classes
         
         [EditorSequence(7)]
         public JobLink Job { get; set; }
-
-        [CurrencyEditor(Visible = Visible.Optional)]
+        
+        [DoubleEditor(Visible = Visible.Optional)]
         [EditorSequence(8)]
+        public double ForeignCurrencyPrice { get; set; }
+        
+        [CurrencyEditor(Visible = Visible.Optional)]
+        [EditorSequence(9)]
         public double TradePrice { get; set; }
 
         [DoubleEditor(Visible = Visible.Optional)]
-        [EditorSequence(9)]
+        [EditorSequence(10)]
         public double Discount { get; set; }
 
-        [CurrencyEditor(Visible = Visible.Optional)]
-        [EditorSequence(10)]
+        [CurrencyEditor(Visible = Visible.Optional, Editable = Editable.Disabled)]
+        [EditorSequence(11)]
         public double CostPrice { get; set; }
 
-        [EditorSequence(11)]
+        [EditorSequence(12)]
         public TaxCodeLink TaxCode { get; set; }
 
         [URLEditor]
-        [EditorSequence(12)]
+        [EditorSequence(13)]
         public string URL { get; set; }
 
         [TimestampEditor]
-        [EditorSequence(13)]
+        [EditorSequence(14)]
         public DateTime Expired { get; set; }
         
         static SupplierProduct()
@@ -88,12 +93,21 @@ namespace Comal.Classes
 
             bChanging = true;
 
-            if (name.Equals("TradePrice"))
+            if (name.Equals(nameof(ForeignCurrencyPrice)) && SupplierLink.Currency.ID != Guid.Empty)
+            {
+                TradePrice = (double)after / (SupplierLink.Currency.ExchangeRate.IsEffectivelyEqual(0.0) ? 1.0 : SupplierLink.Currency.ExchangeRate);
+                CostPrice = TradePrice * (100.0F - Discount) / 100.0F;
+            }
+            else if (name.Equals(nameof(TradePrice)))
+            {
+                if (SupplierLink.Currency.ID != Guid.Empty) 
+                    ForeignCurrencyPrice = (double)after * SupplierLink.Currency.ExchangeRate;
                 CostPrice = (double)after * (100.0F - Discount) / 100.0F;
-            else if (name.Equals("Discount"))
+            }
+            else if (name.Equals(nameof(Discount)))
                 CostPrice = TradePrice * (100.0F - (double)after) / 100.0F;
-            else if (name.Equals("CostPrice"))
-                TradePrice = Discount == 100.0F ? double.MaxValue : (double)after / ((100.0F - Discount) / 100.0F);
+            //else if (name.Equals(name,nameof(CostPrice)))
+            //    TradePrice = Discount == 100.0F ? double.MaxValue : (double)after / ((100.0F - Discount) / 100.0F);
 
             bChanging = false;
         }

+ 137 - 0
prs.desktop/Grids/ForeignCurrencyGrid.cs

@@ -0,0 +1,137 @@
+using System;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media.Imaging;
+using Comal.Classes;
+using InABox.Clients;
+using InABox.Configuration;
+using InABox.Core;
+using InABox.DynamicGrid;
+using InABox.Wpf;
+using InABox.WPF;
+using OpenExchangeRates;
+using Exception = System.Exception;
+
+namespace PRSDesktop;
+
+public class ForeignCurrencyGridSettings : BaseObject, IGlobalConfigurationSettings
+{
+    [TextBoxEditor]
+    [EditorSequence(1)]
+    public string ApiKey { get; set; }
+
+    [TextBoxEditor] 
+    [EditorSequence(1)] 
+    public string BaseCurrency { get; set; } = "AUD";
+}
+
+public class ForeignCurrencyGrid : DynamicDataGrid<ForeignCurrency>
+{
+    private BitmapImage tick = PRSDesktop.Resources.tick.AsBitmapImage();
+
+    private Button update;
+
+    private ForeignCurrencyGridSettings _settings;
+    
+    public ForeignCurrencyGrid()
+    {
+        _settings = new GlobalConfiguration<ForeignCurrencyGridSettings>().Load();
+        HiddenColumns.Add(x=>x.Code);
+        HiddenColumns.Add(x=>x.Description);
+        HiddenColumns.Add(x=>x.Identifier);
+        HiddenColumns.Add(x=>x.Active);
+        ActionColumns.Add(new DynamicTickColumn<ForeignCurrency,bool>(x=>x.Active, tick, tick, null, ToggleActive));
+        AddButton(null, PRSDesktop.Resources.autoupdate.AsBitmapImage(), UpdateSettings);
+        update = AddButton("Update", PRSDesktop.Resources.payment.AsBitmapImage(), UpdateExchangeRates);
+        update.Visibility = !string.IsNullOrEmpty(_settings.ApiKey) ? Visibility.Visible : Visibility.Collapsed;
+    }
+
+    private bool UpdateSettings(Button button, CoreRow[] rows)
+    {
+        if (DynamicGridUtils.EditObject(_settings))
+        {
+            new GlobalConfiguration<ForeignCurrencyGridSettings>().Save(_settings);
+            update.Visibility = !string.IsNullOrEmpty(_settings.ApiKey) ? Visibility.Visible : Visibility.Collapsed;
+        }
+        return false;
+    }
+
+    private bool ToggleActive(CoreRow? row)
+    {
+        if (row == null)
+            return false;
+        var fc = row.ToObject<ForeignCurrency>();
+        fc.Active = !fc.Active;
+        new Client<ForeignCurrency>().Save(fc, string.Format("Set to {0} ", fc.Active ? "Active" : "InActive"));
+        return true;
+    }
+
+
+    private bool UpdateExchangeRates(Button button, CoreRow[] rows)
+    {
+        try
+        {
+            Progress.ShowModal("Retrieving Rates", progress =>
+            {
+                var data = Data.ToObjects<ForeignCurrency>().ToList();
+                using (var client = new OpenExchangeRatesClient("ff16587e173b43788a34c035a2c53edd"))
+                {
+                    var currencies = client.GetCurrenciesAsync().Result;
+                    if (currencies == null)
+                        throw new Exception("No Currencies Returned");
+
+                    var rates = client.GetLatestRatesAsync().Result;
+                    if (rates == null)
+                        throw new Exception("No Rates Returned");
+
+                    if (!rates.Rates.TryGetValue("AUD", out decimal aud_decimal))
+                        throw new Exception("Cannot Find AUD rate");
+
+                    var usd_aud = Convert.ToDouble(aud_decimal);
+                    if (usd_aud.IsEffectivelyEqual(0.0))
+                        throw new Exception("USD -> AUD conversion return 0.00");
+
+                    foreach (var rate in rates.Rates)
+                    {
+                        var foreign_usd = Convert.ToDouble(rate.Value);
+                        var items = data.Where(x => String.Equals(x.Identifier, rate.Key)).ToList();
+                        if (!items.Any())
+                        {
+                            if (currencies.TryGetValue(rate.Key, out string? description))
+                            {
+                                var newitem = new ForeignCurrency()
+                                {
+                                    Code = rate.Key,
+                                    Description = description,
+                                    Identifier = rate.Key,
+                                    Active = false
+                                };
+                                items.Add(newitem);
+                                data.Add(newitem);
+                            }
+                        }
+
+                        foreach (var item in items.Where(x => x.Active))
+                            item.ExchangeRate =  foreign_usd / usd_aud;
+                    }
+                }
+                
+                var updates = data.Where(x => x.IsChanged()).ToArray();
+                if (updates.Any())
+                {
+                    progress.Report($"Updating {updates.Length} records...");
+                    new Client<ForeignCurrency>().Save(updates, "Updated from openexchangerates.org");
+                }
+                    
+                
+            });
+        }
+        catch (Exception e)
+        {
+            MessageWindow.ShowError("Error retrieving data!", e);
+        }
+        return true;
+    }
+        
+}

+ 1 - 0
prs.desktop/PRSDesktop.csproj

@@ -834,6 +834,7 @@
       <PackageReference Include="NAudio" Version="2.2.1" />
       <PackageReference Include="NDesk.Options.Core" Version="1.2.8" />
       <PackageReference Include="net.sf.mpxj" Version="12.10.1" />
+      <PackageReference Include="OpenExchangeRates.NET" Version="1.3.0" />
       <PackageReference Include="Scriban" Version="5.10.0" />
       <PackageReference Include="SharpAvi" Version="3.0.1" />
       <PackageReference Include="Syncfusion.DataGridExcelExport.Wpf" Version="25.2.6" />

+ 22 - 7
prs.desktop/Panels/Suppliers/Bills/SupplierBillLineGrid.cs

@@ -36,6 +36,14 @@ public class SupplierBillLineGrid : DynamicOneToManyGrid<Bill, BillLine>
         HiddenColumns.Add(x => x.IncTax);
         HiddenColumns.Add(x => x.Description);
         
+        HiddenColumns.Add(x=>x.Product.ID);
+        HiddenColumns.Add(x=>x.Product.Code);
+        HiddenColumns.Add(x=>x.Product.Name);
+        HiddenColumns.Add(x=>x.Product.TaxCode.ID);
+        HiddenColumns.Add(x=>x.Product.PurchaseGL.ID);
+        HiddenColumns.Add(x=>x.Product.SellGL.ID);
+        HiddenColumns.Add(x=>x.Product.CostCentre.ID);
+        
         HiddenColumns.Add(x=>x.Consignment.ID);
         HiddenColumns.Add(x=>x.Consignment.Number);
 
@@ -54,8 +62,8 @@ public class SupplierBillLineGrid : DynamicOneToManyGrid<Bill, BillLine>
             new DynamicTextColumn(DisplayLinkText,DisplayLinkClick)
             {
                 Position = DynamicActionColumnPosition.Start, 
-                Width = 50,
-                HeaderText = "Link",
+                Width = 30,
+                HeaderText = "?",
                 ToolTip = DisplayLinkToolTip,
                 Alignment = Alignment.MiddleCenter
             }
@@ -68,13 +76,16 @@ public class SupplierBillLineGrid : DynamicOneToManyGrid<Bill, BillLine>
     {
         if (row == null)
             return "";
+        var prodid = row.Get<BillLine, Guid>(x => x.Product.ID);
         var poid = row.Get<BillLine, Guid>(x => x.OrderItem.PurchaseOrderLink.ID);
         var conid = row.Get<BillLine, Guid>(x => x.Consignment.ID);
         return !Guid.Equals(poid,Guid.Empty) 
-            ? "PO"
+            ? "O"
             : !Guid.Equals(conid,Guid.Empty)
                 ? "C"
-                : "--";
+                : !Guid.Equals(prodid,Guid.Empty)
+                    ? "P"
+                    : "--";
     }
 
     private bool DisplayLinkClick(CoreRow? row)
@@ -85,17 +96,21 @@ public class SupplierBillLineGrid : DynamicOneToManyGrid<Bill, BillLine>
     private FrameworkElement? DisplayLinkToolTip(DynamicActionColumn column, CoreRow? row)
     {
         var text = !Guid.Equals(Guid.Empty, row?.Get<BillLine, Guid>(x => x.OrderItem.PurchaseOrderLink.ID) ?? Guid.Empty)
-            ? String.Format("PO {0}: {1:F2} x {2}",
+            ? String.Format("Purchase Order: {0}: {1:F2} x {2}",
                 row?.Get<BillLine, String>(x => x.OrderItem.PurchaseOrderLink.PONumber),
                 row?.Get<BillLine, double>(x => x.OrderItem.Qty),
                 row?.Get<BillLine, String>(x => x.OrderItem.Description)
                 )
             : !Guid.Equals(Guid.Empty, row?.Get<BillLine, Guid>(x => x.Consignment.ID) ?? Guid.Empty)
-                ? String.Format("Cons. {0}: {1}",
+                ? String.Format("Consignment: {0}: {1}",
                     row?.Get<BillLine, String>(x => x.Consignment.Number),
                     row?.Get<BillLine, String>(x => x.Consignment.Description)
                 )
-                : "";
+                : !Guid.Equals(Guid.Empty, row?.Get<BillLine, Guid>(x => x.Product.ID) ?? Guid.Empty)
+                    ? String.Format("Product: {0}: {1}",
+                        row?.Get<BillLine, String>(x => x.Product.Code),
+                        row?.Get<BillLine, String>(x => x.Product.Name))
+                    : "";
     
         return String.IsNullOrWhiteSpace(text)
             ? column.TextToolTip(text)

+ 16 - 2
prs.desktop/Panels/Suppliers/SupplierProductGrid.cs

@@ -205,9 +205,23 @@ namespace PRSDesktop
 
         protected override BaseEditor? GetEditor(object item, DynamicGridColumn column)
         {
+            BaseEditor? editor = null;
             if (column.ColumnName.Equals("SupplierLink.ID"))
-                return new NullEditor();
-            return base.GetEditor(item, column);
+                editor = new NullEditor();
+            else if (column.ColumnName.Equals(nameof(SupplierProduct.ForeignCurrencyPrice)) && (item as SupplierProduct)?.SupplierLink.Currency.ID == Guid.Empty)
+            {
+                editor = base.GetEditor(item, column).CloneEditor();
+                editor.Editable = Editable.Hidden;
+            }
+            else if (column.ColumnName.Equals(nameof(SupplierProduct.TradePrice)) && (item as SupplierProduct)?.SupplierLink.Currency.ID != Guid.Empty)
+            {
+                editor = base.GetEditor(item, column).CloneEditor();
+                editor.Editable = Editable.Disabled;
+            }
+            else
+                editor = base.GetEditor(item, column);
+
+            return editor;
         }
 
         protected override void DoAdd(bool OpenEditorOnDirectEdit = false)

+ 6 - 0
prs.desktop/Setups/AccountsSetupActions.cs

@@ -43,6 +43,12 @@ public static class AccountsSetupActions
             var list = new MasterList(typeof(GLCode));
             list.ShowDialog();
         });
+        
+        host.CreateSetupActionIfCanView<ForeignCurrency>("Foreign Currencies", PRSDesktop.Resources.payment, (action) =>
+        {
+            var list = new MasterList(typeof(ForeignCurrency));
+            list.ShowDialog();
+        });
     }
     
     public static void PurchaseOrderCategories(IPanelHost host)