فهرست منبع

Added Bill MYOB poster

Kenric Nugteren 1 سال پیش
والد
کامیت
539877c28a

+ 2 - 1
prs.classes/Entities/GLCode/GLCode.cs

@@ -3,7 +3,8 @@
 namespace Comal.Classes
 {
     [UserTracking(typeof(Product))]
-    public class GLCode : Entity, IRemotable, IPersistent, ILicense<CoreLicense>, IExportable, IImportable, IMergeable, IPostableFragment<Invoice>
+    public class GLCode : Entity, IRemotable, IPersistent, ILicense<CoreLicense>, IExportable, IImportable, IMergeable,
+        IPostableFragment<Invoice>, IPostableFragment<Bill>
     {
         [UniqueCodeEditor(Visible = Visible.Default, Editable = Editable.Enabled)]
         public string Code { get; set; }

+ 16 - 4
prs.classes/Entities/Supplier/Supplier.cs

@@ -54,7 +54,7 @@ namespace Comal.Classes
     }
 
     [UserTracking(typeof(Bill))]
-    public class Supplier : Entity, IPersistent, IRemotable, ILicense<CoreLicense>, IExportable, IImportable, IMergeable
+    public class Supplier : Entity, IPersistent, IRemotable, ILicense<CoreLicense>, IExportable, IImportable, IMergeable, IPostable
     {
         [UniqueCodeEditor(Visible = Visible.Default, Editable = Editable.Enabled)]
         [EditorSequence(1)]
@@ -70,13 +70,20 @@ namespace Comal.Classes
         [TextBoxEditor]
         [EditorSequence(4)]
         public string Email { get; set; } = "";
-        
+
+        [TextBoxEditor]
         [EditorSequence(5)]
-        public SupplierStockLocationLink DefaultLocation { get; set; }
+        public string Telephone { get; set; } = "";
         
         [EditorSequence(6)]
+        public SupplierStockLocationLink DefaultLocation { get; set; }
+        
+        [EditorSequence(7)]
         public SupplierCategoryLink Category { get; set; }
 
+        [EditorSequence(8)]
+        public SupplierStatusLink SupplierStatus { get; set; }
+
         [EditorSequence("Delivery",1)]
         public Address Delivery { get; set; }
         
@@ -98,7 +105,12 @@ namespace Comal.Classes
         [CurrencyEditor(Editable = Editable.Hidden, Summary = Summary.Sum)]
         [Formula(typeof(SupplierBalance))]
         public double Balance { get; set; }
-        
+
+        public DateTime Posted { get; set; }
+        public PostedStatus PostedStatus { get; set; }
+        public string PostedNote { get; set; }
+        public string PostedReference { get; set; }
+
         public override string ToString()
         {
             return string.Format("{0}: {1}", Code, Name);

+ 11 - 0
prs.classes/Entities/Supplier/SupplierStatus/ISupplierStatus.cs

@@ -0,0 +1,11 @@
+namespace Comal.Classes
+{
+    public interface ISupplierStatus
+    {
+        string Code { get; set; }
+
+        string Description { get; set; }
+
+        bool Active { get; set; }
+    }
+}

+ 25 - 0
prs.classes/Entities/Supplier/SupplierStatus/SupplierStatus.cs

@@ -0,0 +1,25 @@
+using InABox.Core;
+
+namespace Comal.Classes
+{
+    [UserTracking(typeof(Supplier))]
+    public class SupplierStatus : Entity, IRemotable, IPersistent, ISupplierStatus, ILicense<CoreLicense>, IExportable, IImportable, IMergeable
+    {
+        [UniqueCodeEditor(Visible = Visible.Default, Editable = Editable.Enabled)]
+        [EditorSequence(1)]
+        public string Code { get; set; }
+
+        [TextBoxEditor(Visible = Visible.Default)]
+        [EditorSequence(2)]
+        public string Description { get; set; }
+
+        [CheckBoxEditor(Visible = Visible.Optional)]
+        [EditorSequence(3)]
+        public bool Active { get; set; }
+
+        [CheckBoxEditor(Visible = Visible.Optional)]
+        [EditorSequence(4)]
+        public bool Default { get; set; }
+
+    }
+}

+ 20 - 0
prs.classes/Entities/Supplier/SupplierStatus/SupplierStatusLink.cs

@@ -0,0 +1,20 @@
+using System;
+using InABox.Core;
+
+namespace Comal.Classes
+{
+    public class SupplierStatusLink : EntityLink<SupplierStatus>, ISupplierStatus
+    {
+        [LookupEditor(typeof(SupplierStatus))]
+        public override Guid ID { get; set; }
+
+        [CodeEditor(Visible = Visible.Default)]
+        public string Code { get; set; }
+
+        [TextBoxEditor(Visible = Visible.Optional, Editable = Editable.Hidden)]
+        public string Description { get; set; }
+
+        [CheckBoxEditor(Visible = Visible.Optional, Editable = Editable.Hidden)]
+        public bool Active { get; set; }
+    }
+}

+ 22 - 0
prs.classes/Entities/Supplier/SupplierStatus/SupplierStatusLookups.cs

@@ -0,0 +1,22 @@
+using InABox.Core;
+
+namespace Comal.Classes
+{
+    public class SupplierStatusLookups : EntityLookup<SupplierStatus>
+    {
+        public override Columns<SupplierStatus> DefineColumns()
+        {
+            return new Columns<SupplierStatus>(ColumnTypeFlags.DefaultVisible);
+        }
+
+        public override Filter<SupplierStatus> DefineFilter()
+        {
+            return null;
+        }
+
+        public override SortOrder<SupplierStatus> DefineSortOrder()
+        {
+            return new SortOrder<SupplierStatus>(x => x.Description);
+        }
+    }
+}

+ 1 - 1
prs.classes/Entities/TaxCode/TaxCode.cs

@@ -14,7 +14,7 @@ namespace Comal.Classes
 
     [UserTracking(typeof(Invoice))]
     public class TaxCode : Entity, IPersistent, IRemotable, ILicense<CoreLicense>, IExportable, IImportable, IMergeable,
-        IPostableFragment<Customer>, IPostableFragment<Invoice>, ITaxCode
+        IPostableFragment<Customer>, IPostableFragment<Invoice>, IPostableFragment<Bill>, ITaxCode
     {
         [EditorSequence(1)]
         [UniqueCodeEditor(Visible = Visible.Default, Editable = Editable.Enabled)]

+ 32 - 0
prs.shared/Posters/MYOB/AccountMYOBUtils.cs

@@ -0,0 +1,32 @@
+using InABox.Core;
+using InABox.Poster.MYOB;
+using MYOB.AccountRight.SDK.Services.GeneralLedger;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MYOBAccount = MYOB.AccountRight.SDK.Contracts.Version2.GeneralLedger.Account;
+
+namespace PRS.Shared.Posters.MYOB;
+
+public static class AccountMYOBUtils
+{
+    public static Result<Guid, Exception> GetAccount(MYOBConnectionData data, string glCode)
+    {
+        if (!data.GetUID<AccountService, MYOBAccount>(
+                new Filter<MYOBAccount>(x => x.DisplayID).IsEqualTo(glCode))
+            .Get(out var accountID, out var error))
+        {
+            return Result.Error(new Exception($"Failed to find Account in MYOB with code {glCode}", error));
+        }
+        else if (accountID == Guid.Empty)
+        {
+            return Result.Error(new Exception($"Failed to find Account in MYOB with code {glCode}"));
+        }
+        else
+        {
+            return Result.Ok(accountID);
+        }
+    }
+}

+ 239 - 0
prs.shared/Posters/MYOB/BillMYOBPoster.cs

@@ -0,0 +1,239 @@
+using Comal.Classes;
+using InABox.Core;
+using InABox.Poster.MYOB;
+using MYOB.AccountRight.SDK.Contracts.Version2.Sale;
+using MYOB.AccountRight.SDK.Services.GeneralLedger;
+using MYOB.AccountRight.SDK.Services.Purchase;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MYOBAccount = MYOB.AccountRight.SDK.Contracts.Version2.GeneralLedger.Account;
+using MYOBBill = MYOB.AccountRight.SDK.Contracts.Version2.Purchase.ServiceBill;
+using MYOBBillLine = MYOB.AccountRight.SDK.Contracts.Version2.Purchase.ServiceBillLine;
+using MYOBTaxCode = MYOB.AccountRight.SDK.Contracts.Version2.GeneralLedger.TaxCode;
+
+namespace PRS.Shared.Posters.MYOB;
+
+public class BillMYOBPosterSettings : MYOBPosterSettings
+{
+}
+
+public class BillMYOBPoster : IMYOBPoster<Bill, BillMYOBPosterSettings>
+{
+    public MYOBConnectionData ConnectionData { get; set; }
+
+    public BillMYOBPosterSettings Settings { get; set; }
+
+    public MYOBGlobalPosterSettings GlobalSettings { get; set; }
+
+    public bool BeforePost(IDataModel<Bill> model)
+    {
+        foreach(var (_, table) in model.ModelTables)
+        {
+            table.ShouldLoad = false;
+        }
+        model.SetShouldLoad<Bill>(true);
+        model.SetColumns<Bill>(RequiredBillColumns());
+
+        model.SetShouldLoad<BillLine>(true, alias: "Bill_BillLine");
+        model.SetColumns<BillLine>(RequiredBillLineColumns());
+
+        model.SetIsDefault<Supplier>(true, alias: "Bill_Supplier");
+        model.SetColumns<Supplier>(RequiredSupplierColumns());
+
+        return true;
+    }
+
+    private static Columns<Bill> RequiredBillColumns()
+    {
+        return Columns.None<Bill>()
+            .Add(x => x.ID)
+            .Add(x => x.PostedReference)
+            .Add(x => x.Number)
+            .Add(x => x.AccountingDate)
+            .Add(x => x.SupplierLink.ID)
+            .Add(x => x.Description);
+    }
+    private static Columns<BillLine> RequiredBillLineColumns()
+    {
+        return Columns.None<BillLine>()
+            .Add(x => x.ID)
+            .Add(x => x.BillLink.ID)
+            .Add(x => x.Description)
+            .Add(x => x.IncTax)
+            .Add(x => x.PurchaseGL.ID)
+            .Add(x => x.PurchaseGL.Code)
+            .Add(x => x.PurchaseGL.PostedReference)
+            .Add(x => x.TaxCode.ID)
+            .Add(x => x.TaxCode.Code)
+            .Add(x => x.TaxCode.PostedReference);
+    }
+    private static Columns<Supplier> RequiredSupplierColumns()
+    {
+        return SupplierMYOBPoster.RequiredColumns();
+    }
+
+    public IPostResult<Bill> Process(IDataModel<Bill> model)
+    {
+        // https://developer.myob.com/api/myob-business-api/v2/purchase/bill/bill_service/
+
+        var results = new PostResult<Bill>();
+
+        var service = new ServiceBillService(ConnectionData.Configuration, null, ConnectionData.AuthKey);
+
+        var bills = model.GetTable<Bill>().ToArray<Bill>();
+
+        var suppliers = model.GetTable<Supplier>("Bill_Supplier")
+            .ToObjects<Supplier>().ToDictionary(x => x.ID);
+
+        var billLines = model.GetTable<BillLine>("Bill_BillLine")
+            .ToObjects<BillLine>().GroupBy(x => x.BillLink.ID).ToDictionary(x => x.Key, x => x.ToArray());
+        
+        foreach(var bill in bills)
+        {
+            MYOBBill myobBill;
+            if(Guid.TryParse(bill.PostedReference, out var myobID))
+            {
+                if(!service.Get(ConnectionData, myobID).Get(out var newBill, out var error))
+                {
+                    CoreUtils.LogException("", error, $"Failed to find Bill in MYOB with id {myobID}");
+                    results.AddFailed(bill, $"Failed to find Bill in MYOB with id {myobID}: {error.Message}");
+                    continue;
+                }
+                myobBill = newBill;
+            }
+            else
+            {
+                myobBill = new MYOBBill();
+            }
+
+            myobBill.Number = bill.Number.Truncate(13);
+
+            // Probably configure which date.
+            myobBill.Date = bill.AccountingDate;
+            myobBill.SupplierInvoiceNumber = bill.Number.Truncate(255);
+
+            if(suppliers.TryGetValue(bill.SupplierLink.ID, out var supplier))
+            {
+                if(!SupplierMYOBPoster.MapSupplier(ConnectionData, supplier).Get(out var supplierID, out var error))
+                {
+                    CoreUtils.LogException("", error, $"Error while posting bill {bill.ID}");
+                    results.AddFailed(bill, error.Message);
+                    continue;
+                }
+                myobBill.Supplier.UID = supplierID;
+            }
+
+            // myobBill.ShipToAddress = 
+            // myobBill.Terms = 
+            myobBill.IsTaxInclusive = true;
+            myobBill.IsReportable = true;
+            // myobBill.Freight = 
+            // myobBill.FreightForeign = 
+            // myobBill.FreightTaxCode = 
+            // myobBill.Category = 
+            myobBill.Comment = bill.Description.Truncate(2000);
+            // myobBill.ShippingMethod = 
+            // myobBill.PromisedDate = 
+            // myobBill.JournalMemo = 
+            // myobBill.BillDeliveryStatus = 
+            // myobBill.Order = 
+
+            if(billLines.TryGetValue(bill.ID, out var lines))
+            {
+                var newLines = new MYOBBillLine[lines.Length];
+                string? failed = null;
+                for(int i = 0; i < lines.Length; ++i)
+                {
+                    var billLine = lines[i];
+
+                    var line = new MYOBBillLine();
+                    line.RowID = i + 1;
+                    line.Type = InvoiceLineType.Transaction;
+                    line.Description = billLine.Description;
+
+                    if(billLine.PurchaseGL.ID == Guid.Empty)
+                    {
+                        failed = "Not all lines have a PurchaseGL code set.";
+                        break;
+                    }
+                    if(!Guid.TryParse(billLine.PurchaseGL.PostedReference, out var accountID))
+                    {
+                        if(AccountMYOBUtils.GetAccount(ConnectionData, billLine.PurchaseGL.Code).Get(out accountID, out var error))
+                        {
+                            results.AddFragment(new GLCode { ID = billLine.PurchaseGL.ID, PostedReference = accountID.ToString() });
+                        }
+                        else
+                        {
+                            CoreUtils.LogException("", error);
+                            failed = error.Message;
+                            break;
+                        }
+                    }
+                    line.Account.UID = accountID;
+                    line.Total = (decimal)billLine.IncTax;
+                    line.TotalForeign = 0;
+                    // line.UnitsOfMeasure =
+                    // line.UnitCount =
+                    // line.UnitPrice =
+                    // line.UnitPriceForeign =
+                    // line.DiscountPercent =
+                    // line.Job =
+
+                    if(billLine.TaxCode.ID == Guid.Empty)
+                    {
+                        failed = "Not all lines have a TaxCode set.";
+                        break;
+                    }
+                    if(!Guid.TryParse(billLine.TaxCode.PostedReference, out var taxCodeID))
+                    {
+                        if (!ConnectionData.GetUID<TaxCodeService, MYOBTaxCode>(
+                                new Filter<MYOBTaxCode>(x => x.Code).IsEqualTo(billLine.TaxCode.Code))
+                            .Get(out taxCodeID, out var error))
+                        {
+                            CoreUtils.LogException("", error, $"Failed to find TaxCode in MYOB with code {billLine.TaxCode.Code}");
+                            failed = $"Failed to find TaxCode in MYOB with code {billLine.TaxCode.Code}: {error.Message}";
+                            break;
+                        }
+                        else if (taxCodeID == Guid.Empty)
+                        {
+                            failed = $"Failed to find TaxCode in MYOB with code {billLine.TaxCode.Code}";
+                            break;
+                        }
+                        results.AddFragment(new TaxCode { ID = taxCodeID, PostedReference = taxCodeID.ToString() });
+                    }
+                    line.TaxCode.UID = taxCodeID;
+
+                    newLines[i] = line;
+                }
+
+                if(failed is not null)
+                {
+                    results.AddFailed(bill, failed);
+                    continue;
+                }
+                myobBill.Lines = newLines;
+            }
+            else
+            {
+                myobBill.Lines = [];
+            }
+
+            try
+            {
+                var result = service.UpdateEx(ConnectionData.CompanyFile, myobBill, ConnectionData.CompanyFileCredentials);
+                bill.PostedReference = result.UID.ToString();
+                results.AddSuccess(bill);
+            }
+            catch(Exception e)
+            {
+                CoreUtils.LogException("", e, $"Error while posting receipt {bill.ID}");
+                results.AddFailed(bill, e.Message);
+            }
+        }
+
+        return results;
+    }
+}

+ 14 - 11
prs.shared/Posters/MYOB/CustomerMYOBPoster.cs

@@ -25,21 +25,16 @@ public class CustomerMYOBPosterSettings : MYOBPosterSettings
     public string DefaultTaxCode { get; set; }
 }
 
-public class CustomerMYOBPoster : IMYOBPoster<Customer, CustomerMYOBPosterSettings>
+public static class ContactMYOBUtils
 {
-    public CustomerMYOBPosterSettings Settings { get; set; }
-    public MYOBGlobalPosterSettings GlobalSettings { get; set; }
-
-    public MYOBConnectionData ConnectionData { get; set; }
-
-    private static void SplitName(string name, out string firstName, out string lastName)
+    public static void SplitName(string name, out string firstName, out string lastName)
     {
         var names = name.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
         firstName = names.Length > 0 ? names[0] : "";
         lastName = names.Length > 1 ? names[1] : "";
     }
 
-    private static MYOBAddress ConvertAddress(Address address, int location, IContact contact)
+    public static MYOBAddress ConvertAddress(Address address, int location, IContact contact)
     {
         return new MYOBAddress
         {
@@ -54,6 +49,14 @@ public class CustomerMYOBPoster : IMYOBPoster<Customer, CustomerMYOBPosterSettin
             ContactName = contact.Name.Truncate(25)
         };
     }
+}
+
+public class CustomerMYOBPoster : IMYOBPoster<Customer, CustomerMYOBPosterSettings>
+{
+    public CustomerMYOBPosterSettings Settings { get; set; }
+    public MYOBGlobalPosterSettings GlobalSettings { get; set; }
+
+    public MYOBConnectionData ConnectionData { get; set; }
 
     public bool BeforePost(IDataModel<Customer> model)
     {
@@ -100,7 +103,7 @@ public class CustomerMYOBPoster : IMYOBPoster<Customer, CustomerMYOBPosterSettin
         // Since this might be called from some other poster, we need to ensure we have the right columns.
         Client.EnsureColumns(customer, RequiredColumns());
 
-        SplitName(customer.DefaultContact.Name, out var firstName, out var lastName);
+        ContactMYOBUtils.SplitName(customer.DefaultContact.Name, out var firstName, out var lastName);
 
         myobCustomer.CompanyName = customer.Name.Truncate(50);
         myobCustomer.FirstName = firstName.Truncate(30);
@@ -111,8 +114,8 @@ public class CustomerMYOBPoster : IMYOBPoster<Customer, CustomerMYOBPosterSettin
         myobCustomer.IsActive = customer.CustomerStatus.ID == Guid.Empty || customer.CustomerStatus.Active;
         myobCustomer.Addresses =
         [
-            ConvertAddress(customer.Delivery, 1, customer.DefaultContact),
-            ConvertAddress(customer.Postal, 2, customer.DefaultContact)
+            ContactMYOBUtils.ConvertAddress(customer.Delivery, 1, customer.DefaultContact),
+            ContactMYOBUtils.ConvertAddress(customer.Postal, 2, customer.DefaultContact)
         ];
         // Notes = 
         // PhotoURI =

+ 5 - 12
prs.shared/Posters/MYOB/InvoiceMYOBPoster.cs

@@ -151,22 +151,15 @@ public class InvoiceMYOBPoster : IMYOBPoster<Invoice, InvoiceMYOBPosterSettings>
                     }
                     if(!Guid.TryParse(item.SellGL.PostedReference, out var accountID))
                     {
-                        if (!ConnectionData.GetUID<AccountService, MYOBAccount>(
-                                new Filter<MYOBAccount>(x => x.DisplayID).IsEqualTo(item.SellGL.Code))
-                            .Get(out accountID, out var error))
+                        if(AccountMYOBUtils.GetAccount(ConnectionData, item.SellGL.Code).Get(out accountID, out var error))
                         {
-                            CoreUtils.LogException("", error, $"Failed to find Account in MYOB with code {item.SellGL.Code}");
-                            failed = $"Failed to find Account in MYOB with code {item.SellGL.Code}: {error.Message}";
-                            break;
-                        }
-                        else if (accountID == Guid.Empty)
-                        {
-                            failed = $"Failed to find Account in MYOB with code {item.SellGL.Code}";
-                            break;
+                            results.AddFragment(new GLCode { ID = item.SellGL.ID, PostedReference = accountID.ToString() });
                         }
                         else
                         {
-                            results.AddFragment(new GLCode { ID = item.SellGL.ID, PostedReference = accountID.ToString() });
+                            CoreUtils.LogException("", error);
+                            failed = error.Message;
+                            break;
                         }
                     }
                     line.Account.UID = accountID;

+ 10 - 19
prs.shared/Posters/MYOB/ReceiptMYOBPoster.cs

@@ -47,6 +47,7 @@ public class ReceiptMYOBPoster : IMYOBPoster<Receipt, ReceiptMYOBPosterSettings>
         return Columns.None<Receipt>()
             .Add(x => x.ID)
             .Add(x => x.PostedReference)
+            .Add(x => x.PostedStatus)
             .Add(x => x.CustomerLink.ID)
             .Add(x => x.Date)
             .Add(x => x.Total)
@@ -70,6 +71,8 @@ public class ReceiptMYOBPoster : IMYOBPoster<Receipt, ReceiptMYOBPosterSettings>
 
     public IPostResult<Receipt> Process(IDataModel<Receipt> model)
     {
+        // https://developer.myob.com/api/myob-business-api/v2/sale/customerpayment/
+
         var results = new PostResult<Receipt>();
 
         var service = new CustomerPaymentService(ConnectionData.Configuration, null, ConnectionData.AuthKey);
@@ -84,26 +87,14 @@ public class ReceiptMYOBPoster : IMYOBPoster<Receipt, ReceiptMYOBPosterSettings>
         
         foreach(var receipt in receipts)
         {
-            bool isNew;
-            MYOBReceipt myobReceipt;
-            Exception? error;
-            if(Guid.TryParse(receipt.PostedReference, out var myobID))
+            // For receipts, always create a new one, so we cannot posted already posted stuff.
+            if(receipt.PostedStatus == PostedStatus.Posted)
             {
-                if(!service.Get(ConnectionData, myobID).Get(out var newReceipt, out error))
-                {
-                    CoreUtils.LogException("", error, $"Failed to find Receipt in MYOB with id {myobID}");
-                    results.AddFailed(receipt, $"Failed to find Receipt in MYOB with id {myobID}: {error.Message}");
-                    continue;
-                }
-                myobReceipt = newReceipt;
-                isNew = false;
-            }
-            else
-            {
-                myobReceipt = new MYOBReceipt();
-                isNew = true;
+                continue;
             }
 
+            var myobReceipt = new MYOBReceipt();
+
             myobReceipt.DepositTo = DepositTo.UndepositedFunds;
 
             // Setting this to null right now.
@@ -112,7 +103,7 @@ public class ReceiptMYOBPoster : IMYOBPoster<Receipt, ReceiptMYOBPosterSettings>
 
             if(customers.TryGetValue(receipt.CustomerLink.ID, out var customer))
             {
-                if(!CustomerMYOBPoster.MapCustomer(ConnectionData, customer).Get(out var customerID, out error))
+                if(!CustomerMYOBPoster.MapCustomer(ConnectionData, customer).Get(out var customerID, out var error))
                 {
                     CoreUtils.LogException("", error, $"Error while posting receipt {receipt.ID}");
                     results.AddFailed(receipt, error.Message);
@@ -160,7 +151,7 @@ public class ReceiptMYOBPoster : IMYOBPoster<Receipt, ReceiptMYOBPosterSettings>
                     results.AddFailed(receipt, failed);
                     continue;
                 }
-                myobReceipt.Invoices = [];
+                myobReceipt.Invoices = myobInvoices;
             }
             else
             {

+ 240 - 0
prs.shared/Posters/MYOB/SupplierMYOBPoster.cs

@@ -0,0 +1,240 @@
+using Comal.Classes;
+using InABox.Clients;
+using InABox.Core;
+using InABox.Core.Postable;
+using InABox.Poster.MYOB;
+using MYOB.AccountRight.SDK.Services.Contact;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MYOBSupplier = MYOB.AccountRight.SDK.Contracts.Version2.Contact.Supplier;
+
+namespace PRS.Shared.Posters.MYOB;
+
+public class SupplierMYOBPosterSettings : MYOBPosterSettings
+{
+    [TextBoxEditor(ToolTip = "The MYOB tax code which should be used when posting suppliers")]
+    public string DefaultTaxCode { get; set; }
+}
+
+public class SupplierMYOBPoster : IMYOBPoster<Supplier, SupplierMYOBPosterSettings>
+{
+    public MYOBConnectionData ConnectionData { get; set; }
+    public SupplierMYOBPosterSettings Settings { get; set; }
+    public MYOBGlobalPosterSettings GlobalSettings { get; set; }
+
+    public bool BeforePost(IDataModel<Supplier> model)
+    {
+        foreach (var (_, table) in model.ModelTables)
+        {
+            table.IsDefault = false;
+        }
+        model.SetIsDefault<Supplier>(true);
+        model.SetColumns<Supplier>(RequiredColumns());
+
+        return true;
+    }
+    public static Columns<Supplier> RequiredColumns()
+    {
+        return Columns.None<Supplier>()
+            .Add(x => x.ID)
+            .Add(x => x.PostedReference)
+            .Add(x => x.Name)
+            .Add(x => x.Code)
+            .Add(x => x.SupplierStatus.ID)
+            .Add(x => x.SupplierStatus.Active)
+            .Add(x => x.Postal.Street)
+            .Add(x => x.Postal.City)
+            .Add(x => x.Postal.State)
+            .Add(x => x.Postal.PostCode)
+            .Add(x => x.Email)
+            .Add(x => x.Telephone)
+            .Add(x => x.ABN);
+    }
+
+    public static Result<Exception> UpdateSupplier(MYOBConnectionData data, SupplierMYOBPosterSettings settings, Supplier supplier, MYOBSupplier myobSupplier, bool isNew)
+    {
+        // Documentation: https://developer.myob.com/api/myob-business-api/v2/contact/supplier/
+
+        // Since this might be called from some other poster, we need to ensure we have the right columns.
+        Client.EnsureColumns(supplier, RequiredColumns());
+
+        // ContactMYOBUtils.SplitName(supplier.DefaultContact.Name, out var firstName, out var lastName);
+
+        myobSupplier.CompanyName = supplier.Name.Truncate(50);
+        // myobSupplier.FirstName = 
+        // myobSupplier.LastName = 
+        myobSupplier.IsIndividual = false;
+        myobSupplier.DisplayID = supplier.Code.Truncate(15);
+        // If there is not customer status, we will use default to Active = true.
+        myobSupplier.IsActive = supplier.SupplierStatus.ID == Guid.Empty || supplier.SupplierStatus.Active;
+        myobSupplier.Addresses =
+        [
+            ContactMYOBUtils.ConvertAddress(supplier.Postal, 1, new Contact
+            {
+                Email = supplier.Email,
+                Telephone = supplier.Telephone
+            })
+        ];
+        // Notes = 
+        // PhotoURI =
+        // RowVersion = 
+        // myobSupplier.BuyingDetails.PurchaseLayout = 
+        // myobCustomer.BuyingDetails.PrintedForm = 
+        // myobSupplier.BuyingDetails.PurchaseOrderDelivery = 
+        // myobCustomer.BuyingDetails.ExpenseAccount.UID = 
+        // myobCustomer.BuyingDetails.PaymentMemo = 
+        // myobCustomer.BuyingDetails.PurchaseComment = 
+        // myobCustomer.BuyingDetails.SupplierBillingRate = 
+        // myobCustomer.BuyingDetails.ShippingMethod = 
+        // myobCustomer.BuyingDetails.IsReportable = 
+        // myobCustomer.BuyingDetails.CostPerHour = 
+        // myobCustomer.BuyingDetails.Credit.Limit = 
+        myobSupplier.BuyingDetails.ABN = supplier.ABN.Truncate(14);
+        // myobCustomer.BuyingDetails.ABNBranch
+        // myobCustomer.BuyingDetails.TaxIdNumber
+
+        if (isNew)
+        {
+            if (settings.DefaultTaxCode.IsNullOrWhiteSpace())
+            {
+                throw new PostFailedMessageException("Default tax code has not been set up.");
+            }
+            else if(data.GetMYOBTaxCodeUID(settings.DefaultTaxCode).Get(out var taxID, out var error))
+            {
+                if (taxID == Guid.Empty)
+                {
+                    return Result.Error(new Exception($"Failed to find TaxCode in MYOB with code {settings.DefaultTaxCode}"));
+                }
+                myobSupplier.BuyingDetails.TaxCode.UID = taxID;
+                myobSupplier.BuyingDetails.FreightTaxCode.UID = taxID;
+            }
+            else
+            {
+                CoreUtils.LogException("", error, $"Failed to find TaxCode in MYOB with code {settings.DefaultTaxCode}");
+                return Result.Error(new Exception($"Failed to find TaxCode in MYOB with code {settings.DefaultTaxCode}: {error.Message}", error));
+            }
+        }
+        // myobCustomer.BuyingDetails.UseSupplierTaxCode
+        // myobCustomer.BuyingDetails.Terms
+
+        // myobCustomer.PaymentDetails
+        // myobCustomer.PhotoURI
+
+        return Result.Ok();
+    }
+
+    /// <summary>
+    /// Try to find a supplier in MYOB which matches <paramref name="supplier"/>, and if this fails, create a new one.
+    /// </summary>
+    /// <remarks>
+    /// After this has finished, <paramref name="supplier"/> will be updated with <see cref="Supplier.PostedReference"/> set to the correct ID.
+    /// <br/>
+    /// <paramref name="supplier"/> needs to have at least <see cref="Supplier.Code"/> and <see cref="Supplier.PostedReference"/> as loaded columns.
+    /// </remarks>
+    /// <param name="data"></param>
+    /// <param name="supplier">The supplier to map to.</param>
+    /// <returns>The UID of the MYOB supplier.</returns>
+    public static Result<Guid, Exception> MapSupplier(MYOBConnectionData data, Supplier supplier)
+    {
+        if(Guid.TryParse(supplier.PostedReference, out var myobID))
+        {
+            return Result.Ok(myobID);
+        }
+
+        var service = new SupplierService(data.Configuration, null, data.AuthKey);
+        var result = service.Query(data, new Filter<MYOBSupplier>(x => x.DisplayID).IsEqualTo(supplier.Code), top: 1);
+        return result.MapOk(suppliers =>
+        {
+            if(suppliers.Count == 0)
+            {
+                if(supplier.Code.Length > 15)
+                {
+                    return Result.Error(new Exception("Customer code is longer than 15 characters"));
+                }
+                var myobSupplier = new MYOBSupplier();
+                return UpdateSupplier(data, PosterUtils.LoadPosterSettings<Supplier, SupplierMYOBPosterSettings>(), supplier, myobSupplier, true)
+                    .MapOk<Result<Guid, Exception>>(() =>
+                    {
+                        try
+                        {
+                            var result = service.UpdateEx(data.CompanyFile, myobSupplier, data.CompanyFileCredentials);
+                            supplier.PostedReference = result.UID.ToString();
+                            return Result.Ok(result.UID);
+                        }
+                        catch (Exception e)
+                        {
+                            CoreUtils.LogException("", e, $"Error while posting supplier {supplier.ID}");
+                            return Result.Error(e);
+                        }
+                    }).Flatten();
+            }
+            else
+            {
+                supplier.PostedReference = suppliers.Items[0].UID.ToString();
+                return Result.Ok(suppliers.Items[0].UID);
+            }
+        }).Flatten();
+    }
+
+    public IPostResult<Supplier> Process(IDataModel<Supplier> model)
+    {
+        var results = new PostResult<Supplier>();
+
+        var service = new SupplierService(ConnectionData.Configuration, null, ConnectionData.AuthKey);
+
+        var suppliers = model.GetTable<Supplier>().ToArray<Supplier>();
+        
+        foreach(var supplier in suppliers)
+        {
+            if(supplier.Code.Length > 15)
+            {
+                results.AddFailed(supplier, "Code is longer than 15 characters.");
+                continue;
+            }
+
+            bool isNew;
+            MYOBSupplier myobSupplier;
+            Exception? error;
+            if(Guid.TryParse(supplier.PostedReference, out var myobID))
+            {
+                if(!service.Get(ConnectionData, myobID).Get(out var newSupplier, out error))
+                {
+                    CoreUtils.LogException("", error, $"Failed to find Supplier in MYOB with id {myobID}");
+                    results.AddFailed(supplier, $"Failed to find Supplier in MYOB with id {myobID}: {error.Message}");
+                    continue;
+                }
+                myobSupplier = newSupplier;
+                isNew = false;
+            }
+            else
+            {
+                myobSupplier = new MYOBSupplier();
+                isNew = true;
+            }
+
+            if(UpdateSupplier(ConnectionData, Settings, supplier, myobSupplier, isNew).Get(out error))
+            {
+                try
+                {
+                    var result = service.UpdateEx(ConnectionData.CompanyFile, myobSupplier, ConnectionData.CompanyFileCredentials);
+                    supplier.PostedReference = result.UID.ToString();
+                    results.AddSuccess(supplier);
+                }
+                catch(Exception e)
+                {
+                    CoreUtils.LogException("", e, $"Error while posting supplier {supplier.ID}");
+                    results.AddFailed(supplier, e.Message);
+                }
+            }
+            else
+            {
+                results.AddFailed(supplier, error.Message);
+            }
+        }
+
+        return results;
+    }
+}