|  | @@ -0,0 +1,310 @@
 | 
	
		
			
				|  |  | +using Comal.Classes;
 | 
	
		
			
				|  |  | +using InABox.Core;
 | 
	
		
			
				|  |  | +using InABox.Core.Postable;
 | 
	
		
			
				|  |  | +using InABox.Poster.MYOB;
 | 
	
		
			
				|  |  | +using InABox.Scripting;
 | 
	
		
			
				|  |  | +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 MYOBPurchaseOrder = MYOB.AccountRight.SDK.Contracts.Version2.Purchase.ServicePurchaseOrder;
 | 
	
		
			
				|  |  | +using MYOBPurchaseOrderItem = MYOB.AccountRight.SDK.Contracts.Version2.Purchase.ServicePurchaseOrderLine;
 | 
	
		
			
				|  |  | +using MYOBTaxCode = MYOB.AccountRight.SDK.Contracts.Version2.GeneralLedger.TaxCode;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +namespace PRS.Shared.Posters.MYOB;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +public class PurchaseOrderMYOBPosterSettings : MYOBPosterSettings
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +    public override string DefaultScript(Type TPostable)
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        return @"using MYOBPurchaseOrder = MYOB.AccountRight.SDK.Contracts.Version2.Purchase.ServicePurchaseOrder;
 | 
	
		
			
				|  |  | +using MYOBPurchaseOrderItem = MYOB.AccountRight.SDK.Contracts.Version2.Purchase.ServicePurchaseOrderLine;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +public class Module
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +    public void BeforePost(IDataModel<PurchaseOrder> model)
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        // Perform pre-processing
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    public void ProcessPurchaseOrder(IDataModel<PurchaseOrder> model, PurchaseOrder purchaseOrder, MYOBPurchaseOrder myobPurchaseOrder)
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        // Do extra processing for a purchase order; throw an exception to fail this purchase order.
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    public void ProcessPurchaseOrderItem(IDataModel<PurchaseOrder> model, PurchaseOrderItem purchaseOrderItem, MYOBPurchaseOrderItem myobPurchaseOrderItem)
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        // Do extra processing for a purchase order item; throw an exception to fail this purchase order item (and thus fail the entire purchase order)
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}";
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +public class PurchaseOrderMYOBPoster : IMYOBPoster<PurchaseOrder, PurchaseOrderMYOBPosterSettings>
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +    public ScriptDocument? Script { get; set; }
 | 
	
		
			
				|  |  | +    public MYOBConnectionData ConnectionData { get; set; }
 | 
	
		
			
				|  |  | +    public PurchaseOrderMYOBPosterSettings Settings { get; set; }
 | 
	
		
			
				|  |  | +    public MYOBGlobalPosterSettings GlobalSettings { get; set; }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    public bool BeforePost(IDataModel<PurchaseOrder> model)
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        foreach (var (_, table) in model.ModelTables)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            table.IsDefault = false;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        model.SetIsDefault<PurchaseOrder>(true);
 | 
	
		
			
				|  |  | +        model.SetColumns<PurchaseOrder>(RequiredPurchaseOrderColumns());
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        model.SetIsDefault<PurchaseOrderItem>(true, alias: "PurchaseOrder_PurchaseOrderItem");
 | 
	
		
			
				|  |  | +        model.SetColumns<PurchaseOrderItem>(RequiredPurchaseOrderItemColumns(), alias: "PurchaseOrder_PurchaseOrderItem");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        model.SetIsDefault<Supplier>(true, alias: "PurchaseOrder_Supplier");
 | 
	
		
			
				|  |  | +        model.SetColumns<Supplier>(RequiredSupplierColumns(), alias: "PurchaseOrder_Supplier");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        Script?.Execute(methodname: "BeforePost", parameters: new object[] { model });
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        return true;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    #region Script Functions
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private Result<Exception> ProcessPurchaseOrder(IDataModel<PurchaseOrder> model, PurchaseOrder po, MYOBPurchaseOrder myobPO)
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        return this.WrapScript("ProcessPurchaseOrder", model, po, myobPO);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    private Result<Exception> ProcessPurchaseOrderItem(IDataModel<PurchaseOrder> model, PurchaseOrderItem poItem, MYOBPurchaseOrderItem myobPOItem)
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        return this.WrapScript("ProcessPurchaseOrderItem", model, poItem, myobPOItem);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    #endregion
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private static Columns<PurchaseOrder> RequiredPurchaseOrderColumns()
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        return Columns.None<PurchaseOrder>()
 | 
	
		
			
				|  |  | +            .Add(x => x.ID)
 | 
	
		
			
				|  |  | +            .Add(x => x.PostedReference)
 | 
	
		
			
				|  |  | +            .Add(x => x.IssuedDate)
 | 
	
		
			
				|  |  | +            .Add(x => x.PONumber)
 | 
	
		
			
				|  |  | +            .Add(x => x.SupplierLink.ID)
 | 
	
		
			
				|  |  | +            .Add(x => x.Description);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    private static Columns<PurchaseOrderItem> RequiredPurchaseOrderItemColumns()
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        return Columns.None<PurchaseOrderItem>()
 | 
	
		
			
				|  |  | +            .Add(x => x.ID)
 | 
	
		
			
				|  |  | +            .Add(x => x.PurchaseOrderLink.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<PurchaseOrder> Process(IDataModel<PurchaseOrder> model)
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        var results = new PostResult<PurchaseOrder>();
 | 
	
		
			
				|  |  | +        var service = new ServicePurchaseOrderService(ConnectionData.Configuration, null, ConnectionData.AuthKey);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        var pos = model.GetTable<PurchaseOrder>().ToArray<PurchaseOrder>();
 | 
	
		
			
				|  |  | +        if(pos.Any(x => x.IssuedDate.IsEmpty()))
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            throw new PostFailedMessageException($"We can't process unissued purchase orders; please set [{nameof(PurchaseOrder.IssuedDate)}] for" +
 | 
	
		
			
				|  |  | +                $" all purchase orders before processing.");
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        var poItems = model.GetTable<PurchaseOrderItem>("PurchaseOrder_PurchaseOrderItem")
 | 
	
		
			
				|  |  | +            .ToObjects<PurchaseOrderItem>().GroupBy(x => x.PurchaseOrderLink.ID).ToDictionary(x => x.Key, x => x.ToArray());
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        var suppliers = model.GetTable<Supplier>("PurchaseOrder_Supplier")
 | 
	
		
			
				|  |  | +            .ToObjects<Supplier>().ToDictionary(x => x.ID);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        foreach(var po in pos)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            MYOBPurchaseOrder myobPO;
 | 
	
		
			
				|  |  | +            Exception? error;
 | 
	
		
			
				|  |  | +            bool isNew;
 | 
	
		
			
				|  |  | +            if(Guid.TryParse(po.PostedReference, out var myobID))
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                if(!service.Get(ConnectionData, myobID).Get(out var newPO, out error))
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    CoreUtils.LogException("", error, $"Failed to find Purchase Order in MYOB with id {myobID}");
 | 
	
		
			
				|  |  | +                    results.AddFailed(po, $"Failed to find Purchase Order in MYOB with id {myobID}: {error.Message}");
 | 
	
		
			
				|  |  | +                    continue;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                myobPO = newPO;
 | 
	
		
			
				|  |  | +                isNew = false;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            else
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                myobPO = new MYOBPurchaseOrder();
 | 
	
		
			
				|  |  | +                isNew = true;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            myobPO.Number = po.PONumber.Truncate(13);
 | 
	
		
			
				|  |  | +            myobPO.Date = po.IssuedDate;
 | 
	
		
			
				|  |  | +            // myobPO.SupplierInvoiceNumber
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if(suppliers.TryGetValue(po.SupplierLink.ID, out var supplier))
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                if(!SupplierMYOBPoster.MapSupplier(ConnectionData, supplier, GlobalSettings).Get(out var supplierID, out error))
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    CoreUtils.LogException("", error, $"Error while posting purchase order {po.ID}");
 | 
	
		
			
				|  |  | +                    results.AddFailed(po, error.Message);
 | 
	
		
			
				|  |  | +                    continue;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                myobPO.Supplier ??= new();
 | 
	
		
			
				|  |  | +                myobPO.Supplier.UID = supplierID;
 | 
	
		
			
				|  |  | +                results.AddFragment(supplier, supplier.PostedStatus);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            // myobPO.ShipToAddress = 
 | 
	
		
			
				|  |  | +            // myobPO.Terms = 
 | 
	
		
			
				|  |  | +            myobPO.IsTaxInclusive = true;
 | 
	
		
			
				|  |  | +            myobPO.IsReportable = true;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            // myobPO.Freight = 
 | 
	
		
			
				|  |  | +            // myobPO.FreightForeign = 
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if (isNew)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                if(!this.GetDefaultTaxCode().Get(out var taxCodeID, out error))
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    results.AddFailed(po, error.Message);
 | 
	
		
			
				|  |  | +                    continue;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                myobPO.FreightTaxCode ??= new();
 | 
	
		
			
				|  |  | +                myobPO.FreightTaxCode.UID = taxCodeID;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            // myobPO.Category = 
 | 
	
		
			
				|  |  | +            myobPO.Comment = po.Description.Truncate(2000);
 | 
	
		
			
				|  |  | +            // myobPO.ShippingMethod = 
 | 
	
		
			
				|  |  | +            // myobPO.PromisedDate = 
 | 
	
		
			
				|  |  | +            // myobPO.JournalMemo = 
 | 
	
		
			
				|  |  | +            // myobPO.OrderDeliveryStatus = 
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if(poItems.TryGetValue(po.ID, out var lines))
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                var newLines = new MYOBPurchaseOrderItem[lines.Length];
 | 
	
		
			
				|  |  | +                string? failed = null;
 | 
	
		
			
				|  |  | +                for(int i = 0; i < lines.Length; ++i)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    var poItem = lines[i];
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                    var line = new MYOBPurchaseOrderItem();
 | 
	
		
			
				|  |  | +                    line.Type = OrderLineType.Transaction;
 | 
	
		
			
				|  |  | +                    line.Description = poItem.Description.Truncate(1000);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                    if(poItem.PurchaseGL.ID == Guid.Empty)
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        failed = "Not all lines have a PurchaseGL code set.";
 | 
	
		
			
				|  |  | +                        break;
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                    if(!Guid.TryParse(poItem.PurchaseGL.PostedReference, out var accountID))
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        if(AccountMYOBUtils.GetAccount(ConnectionData, poItem.PurchaseGL.Code).Get(out accountID, out error))
 | 
	
		
			
				|  |  | +                        {
 | 
	
		
			
				|  |  | +                            results.AddFragment(new GLCode { ID = poItem.PurchaseGL.ID, PostedReference = accountID.ToString() });
 | 
	
		
			
				|  |  | +                        }
 | 
	
		
			
				|  |  | +                        else
 | 
	
		
			
				|  |  | +                        {
 | 
	
		
			
				|  |  | +                            CoreUtils.LogException("", error);
 | 
	
		
			
				|  |  | +                            failed = error.Message;
 | 
	
		
			
				|  |  | +                            break;
 | 
	
		
			
				|  |  | +                        }
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                    line.Account ??= new();
 | 
	
		
			
				|  |  | +                    line.Account.UID = accountID;
 | 
	
		
			
				|  |  | +                    line.Total = (decimal)poItem.IncTax;
 | 
	
		
			
				|  |  | +                    line.TotalForeign = 0;
 | 
	
		
			
				|  |  | +                    // line.UnitsOfMeasure =
 | 
	
		
			
				|  |  | +                    // line.UnitCount =
 | 
	
		
			
				|  |  | +                    // line.UnitPrice =
 | 
	
		
			
				|  |  | +                    // line.UnitPriceForeign =
 | 
	
		
			
				|  |  | +                    // line.DiscountPercent =
 | 
	
		
			
				|  |  | +                    // line.Job =
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                    if(poItem.TaxCode.ID == Guid.Empty)
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        failed = "Not all lines have a TaxCode set.";
 | 
	
		
			
				|  |  | +                        break;
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                    if(!Guid.TryParse(poItem.TaxCode.PostedReference, out var taxCodeID))
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        if (!ConnectionData.GetUID<TaxCodeService, MYOBTaxCode>(
 | 
	
		
			
				|  |  | +                                new Filter<MYOBTaxCode>(x => x.Code).IsEqualTo(poItem.TaxCode.Code))
 | 
	
		
			
				|  |  | +                            .Get(out taxCodeID, out error))
 | 
	
		
			
				|  |  | +                        {
 | 
	
		
			
				|  |  | +                            CoreUtils.LogException("", error, $"Failed to find TaxCode in MYOB with code {poItem.TaxCode.Code}");
 | 
	
		
			
				|  |  | +                            failed = $"Failed to find TaxCode in MYOB with code {poItem.TaxCode.Code}: {error.Message}";
 | 
	
		
			
				|  |  | +                            break;
 | 
	
		
			
				|  |  | +                        }
 | 
	
		
			
				|  |  | +                        else if (taxCodeID == Guid.Empty)
 | 
	
		
			
				|  |  | +                        {
 | 
	
		
			
				|  |  | +                            failed = $"Failed to find TaxCode in MYOB with code {poItem.TaxCode.Code}";
 | 
	
		
			
				|  |  | +                            break;
 | 
	
		
			
				|  |  | +                        }
 | 
	
		
			
				|  |  | +                        results.AddFragment(new TaxCode { ID = taxCodeID, PostedReference = taxCodeID.ToString() });
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                    line.TaxCode ??= new();
 | 
	
		
			
				|  |  | +                    line.TaxCode.UID = taxCodeID;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                    if(!ProcessPurchaseOrderItem(model, poItem, line).Get(out error))
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        failed = error.Message;
 | 
	
		
			
				|  |  | +                        break;
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                    newLines[i] = line;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                if(failed is not null)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    results.AddFailed(po, failed);
 | 
	
		
			
				|  |  | +                    continue;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                myobPO.Lines = newLines;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            else
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                myobPO.Lines = [];
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if(!ProcessPurchaseOrder(model, po, myobPO).Get(out error))
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                results.AddFailed(po, error.Message);
 | 
	
		
			
				|  |  | +                continue;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if(service.Save(ConnectionData, myobPO).Get(out var result, out error))
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                po.PostedReference = result.UID.ToString();
 | 
	
		
			
				|  |  | +                results.AddSuccess(po);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            else
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                CoreUtils.LogException("", error, $"Error while posting purchase order {po.ID}");
 | 
	
		
			
				|  |  | +                results.AddFailed(po, error.Message);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        return results;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +public class PurchaseOrderMYOBPosterEngine : MYOBPosterEngine<PurchaseOrder, PurchaseOrderMYOBPoster, PurchaseOrderMYOBPosterSettings>
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +}
 |