|
|
@@ -1,16 +1,214 @@
|
|
|
using Comal.Classes;
|
|
|
+using InABox.Clients;
|
|
|
using InABox.Core;
|
|
|
using InABox.Poster.Xero;
|
|
|
+using InABox.Scripting;
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Linq;
|
|
|
using System.Text;
|
|
|
using System.Threading.Tasks;
|
|
|
+using XeroSupplier = Xero.NetStandard.OAuth2.Model.Accounting.Contact;
|
|
|
+using XeroAccounting = Xero.NetStandard.OAuth2.Model.Accounting;
|
|
|
+using PRS.Shared.Posters.Xero.Services;
|
|
|
+using InABox.Core.Postable;
|
|
|
+using InABox.Database;
|
|
|
|
|
|
namespace PRS.Shared.Posters.Xero;
|
|
|
|
|
|
-public class SupplierXeroPoster
|
|
|
+public class SupplierXeroPosterSettings : XeroPosterSettings
|
|
|
{
|
|
|
+ protected override string DefaultScript()
|
|
|
+ {
|
|
|
+ return @"using XeroSupplier = Xero.NetStandard.OAuth2.Model.Accounting.Contact;
|
|
|
+
|
|
|
+// 'Module' *must* implement " + nameof(ISupplierXeroPosterScript) + @"
|
|
|
+public class Module : " + nameof(ISupplierXeroPosterScript) + @"
|
|
|
+{
|
|
|
+ public void BeforePost(IDataModel<Supplier> model)
|
|
|
+ {
|
|
|
+ // Perform pre-processing
|
|
|
+ }
|
|
|
+
|
|
|
+ public void ProcessSupplier(IDataModel<Supplier> model, Supplier supplier, XeroSupplier xeroSupplier)
|
|
|
+ {
|
|
|
+ // Do extra processing for a supplier; throw an exception to fail this supplier.
|
|
|
+ }
|
|
|
+}";
|
|
|
+ }
|
|
|
+}
|
|
|
+public interface ISupplierXeroPosterScript
|
|
|
+{
|
|
|
+ void BeforePost(IDataModel<Supplier> model);
|
|
|
+
|
|
|
+ void ProcessSupplier(IDataModel<Supplier> model, Supplier supplier, XeroSupplier xeroSupplier);
|
|
|
+}
|
|
|
+
|
|
|
+public class SupplierXeroAutoRefresher : IAutoRefresher<Supplier>
|
|
|
+{
|
|
|
+ public bool ShouldRepost(Supplier supplier)
|
|
|
+ {
|
|
|
+ var shouldRepost = supplier.HasOriginalValue(x => x.Name)
|
|
|
+ || supplier.HasOriginalValue(x => x.Code)
|
|
|
+ || supplier.HasOriginalValue(x => x.ABN)
|
|
|
+ || supplier.HasOriginalValue(x => x.Email)
|
|
|
+ || supplier.HasOriginalValue(x => x.Telephone)
|
|
|
+ // || supplier.Delivery.HasOriginalValue(x => x.Street)
|
|
|
+ // || supplier.Delivery.HasOriginalValue(x => x.City)
|
|
|
+ // || supplier.Delivery.HasOriginalValue(x => x.State)
|
|
|
+ // || supplier.Delivery.HasOriginalValue(x => x.PostCode)
|
|
|
+ || supplier.Postal.HasOriginalValue(x => x.Street)
|
|
|
+ || supplier.Postal.HasOriginalValue(x => x.City)
|
|
|
+ || supplier.Postal.HasOriginalValue(x => x.State)
|
|
|
+ || supplier.Postal.HasOriginalValue(x => x.PostCode);
|
|
|
+ if (shouldRepost)
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(supplier.SupplierStatus.HasOriginalValue(x => x.ID))
|
|
|
+ {
|
|
|
+ var originalID = supplier.SupplierStatus.GetOriginalValue(x => x.ID);
|
|
|
+ var currentID = supplier.SupplierStatus.ID;
|
|
|
+
|
|
|
+ var statuses = DbFactory.NewProvider(Logger.Main).Query(
|
|
|
+ Filter<SupplierStatus>.Where(x => x.ID).IsEqualTo(originalID).Or(x => x.ID).IsEqualTo(currentID),
|
|
|
+ Columns.None<SupplierStatus>().Add(x => x.ID).Add(x => x.Active))
|
|
|
+ .ToArray<SupplierStatus>();
|
|
|
+ if (statuses.Length == 2 && statuses[0].Active != statuses[1].Active)
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+public class SupplierXeroPoster : IXeroPoster<Supplier, SupplierXeroPosterSettings>, IAutoRefreshPoster<Supplier, SupplierXeroAutoRefresher>
|
|
|
+{
|
|
|
+ public ScriptDocument? Script { get; set; }
|
|
|
+ public XeroConnectionData ConnectionData { get; set; }
|
|
|
+ public SupplierXeroPosterSettings Settings { get; set; }
|
|
|
+ public IPosterDispatcher Dispatcher { get; set; }
|
|
|
+
|
|
|
+ public ISupplierXeroPosterScript? ScriptObject => Script?.GetObject<ISupplierXeroPosterScript>();
|
|
|
+
|
|
|
+ public bool BeforePost(IDataModel<Supplier> model)
|
|
|
+ {
|
|
|
+ foreach (var (_, table) in model.ModelTables)
|
|
|
+ {
|
|
|
+ table.IsDefault = false;
|
|
|
+ }
|
|
|
+ model.SetIsDefault<Supplier>(true);
|
|
|
+ model.SetColumns<Supplier>(RequiredColumns());
|
|
|
+
|
|
|
+ model.SetIsDefault<SupplierContact>(true, alias: "Supplier_SupplierContact");
|
|
|
+ model.SetColumns<SupplierContact>(RequiredSupplierContactColumns(), alias: "Supplier_SupplierContact");
|
|
|
+
|
|
|
+ ScriptObject?.BeforePost(model);
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ #region Script Functions
|
|
|
+
|
|
|
+ private Result<Exception> ProcessSupplier(IDataModel<Supplier> model, Supplier supplier, XeroSupplier xeroSupplier)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ ScriptObject?.ProcessSupplier(model, supplier, xeroSupplier);
|
|
|
+ return Result.Ok();
|
|
|
+ }
|
|
|
+ catch(Exception e)
|
|
|
+ {
|
|
|
+ return Result.Error(e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ public static Columns<Supplier> RequiredColumns()
|
|
|
+ {
|
|
|
+ return Columns.None<Supplier>()
|
|
|
+ .Add(x => x.ID)
|
|
|
+ .Add(x => x.PostedReference)
|
|
|
+ .Add(x => x.PostedStatus)
|
|
|
+ .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 Columns<SupplierContact> RequiredSupplierContactColumns()
|
|
|
+ {
|
|
|
+ return Columns.None<SupplierContact>()
|
|
|
+ .Add(x => x.Supplier.ID)
|
|
|
+ .Add(x => x.Contact.Name)
|
|
|
+ .Add(x => x.Contact.Email);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static Result<Exception> UpdateSupplier(XeroConnectionData data, Supplier supplier, List<SupplierContact> contacts, XeroSupplier xeroSupplier)
|
|
|
+ {
|
|
|
+ // Documentation: https://developer.xero.com/documentation/api/accounting/contacts
|
|
|
+
|
|
|
+ // Since this might be called from some other poster, we need to ensure we have the right columns.
|
|
|
+ Client.EnsureColumns(supplier, RequiredColumns());
|
|
|
+
|
|
|
+ // FirstName
|
|
|
+ // LastName
|
|
|
+ // CompanyNumber
|
|
|
+ // BankAccountDetails
|
|
|
+ // AccountsReceivableTaxType
|
|
|
+ // AccountsPayableTaxType
|
|
|
+ // DefaultCurrency
|
|
|
+ // XeroNetworkKey
|
|
|
+ // SalesDefaultAccountCode
|
|
|
+ // PurchasesDefaultAccountCode
|
|
|
+ // SalesTrackingCategories
|
|
|
+ // PurchasesTrackingCategories
|
|
|
+ // TrackingCategoryName
|
|
|
+ // TrackingOptionName
|
|
|
+ // PaymentTerms
|
|
|
+
|
|
|
+ xeroSupplier.Name = supplier.Name.Replace("<", "").Replace(">", "").Trim().Truncate(255).Trim();
|
|
|
+ xeroSupplier.ContactNumber = supplier.Code.Truncate(50);
|
|
|
+ xeroSupplier.AccountNumber = xeroSupplier.AccountNumber.NotWhiteSpaceOr(xeroSupplier.ContactNumber);
|
|
|
+ xeroSupplier.ContactStatus = supplier.SupplierStatus.ID == Guid.Empty || supplier.SupplierStatus.Active
|
|
|
+ ? XeroSupplier.ContactStatusEnum.ACTIVE
|
|
|
+ : XeroSupplier.ContactStatusEnum.ARCHIVED;
|
|
|
+ xeroSupplier.EmailAddress = supplier.Email.Truncate(255);
|
|
|
+ xeroSupplier.ContactPersons = contacts.Select(x =>
|
|
|
+ {
|
|
|
+ var person = new XeroAccounting.ContactPerson();
|
|
|
+ ContactXeroUtils.SplitName(x.Contact.Name, out var firstName, out var lastName);
|
|
|
+ person.FirstName = firstName;
|
|
|
+ person.LastName = lastName;
|
|
|
+ person.EmailAddress = x.Contact.Email;
|
|
|
+ return person;
|
|
|
+ }).Take(5).ToList();
|
|
|
+ xeroSupplier.TaxNumber = supplier.ABN;
|
|
|
+
|
|
|
+ xeroSupplier.Addresses =
|
|
|
+ [
|
|
|
+ ContactXeroUtils.ConvertAddress(supplier.Postal, XeroAccounting.Address.AddressTypeEnum.POBOX),
|
|
|
+ // Xero says that DELIVERY addresses are not valid for contacts
|
|
|
+ // ContactXeroUtils.ConvertAddress(supplier.Delivery, XeroAccounting.Address.AddressTypeEnum.DELIVERY)
|
|
|
+ ];
|
|
|
+ xeroSupplier.Phones =
|
|
|
+ [
|
|
|
+ ContactXeroUtils.ConvertPhone(supplier.Telephone, XeroAccounting.Phone.PhoneTypeEnum.OFFICE)
|
|
|
+ ];
|
|
|
+
|
|
|
+ return Result.Ok();
|
|
|
+ }
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Try to find a supplier in Xero which matches <paramref name="supplier"/>, and if this fails, create a new one.
|
|
|
/// </summary>
|
|
|
@@ -21,43 +219,280 @@ public class SupplierXeroPoster
|
|
|
/// </remarks>
|
|
|
/// <param name="data"></param>
|
|
|
/// <param name="supplier">The supplier to map to.</param>
|
|
|
- /// <returns>The ID of the Xero supplier.</returns>
|
|
|
- public static Result<Guid, Exception> MapSupplier(XeroConnectionData data, Supplier supplier)
|
|
|
- {
|
|
|
- return null;
|
|
|
- // 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, Filter<MYOBSupplier>.Where(x => x.DisplayID).IsEqualTo(supplier.Code), top: 1);
|
|
|
- // return result.MapOk(suppliers =>
|
|
|
- // {
|
|
|
- // if(suppliers.Items.Length == 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, settings, supplier, myobSupplier, true)
|
|
|
- // .MapOk(() => service.Save(data, myobSupplier)
|
|
|
- // .MapOk(x =>
|
|
|
- // {
|
|
|
- // supplier.PostedReference = x.UID.ToString();
|
|
|
- // // Marking as repost because a script may not have run.
|
|
|
- // supplier.PostedStatus = PostedStatus.RequiresRepost;
|
|
|
- // return x.UID;
|
|
|
- // })).Flatten();
|
|
|
- // }
|
|
|
- // else
|
|
|
- // {
|
|
|
- // supplier.PostedReference = suppliers.Items[0].UID.ToString();
|
|
|
- // supplier.PostedStatus = PostedStatus.RequiresRepost;
|
|
|
- // return Result.Ok(suppliers.Items[0].UID);
|
|
|
- // }
|
|
|
- // }).Flatten();
|
|
|
+ /// <returns>The UID of the Xero supplier.</returns>
|
|
|
+ public static Result<Guid, Exception> MapSupplier(XeroConnectionData data, Supplier supplier, List<SupplierContact> contacts)
|
|
|
+ {
|
|
|
+ if(Guid.TryParse(supplier.PostedReference, out var myobID))
|
|
|
+ {
|
|
|
+ return Result.Ok(myobID);
|
|
|
+ }
|
|
|
+
|
|
|
+ var result = data.GetItem<ContactService, XeroSupplier>(Filter<XeroSupplier>.Where(x => x.ContactNumber).IsEqualTo(supplier.Code));
|
|
|
+ return result.MapOk(xeroSupplier =>
|
|
|
+ {
|
|
|
+ if(xeroSupplier is null || !xeroSupplier.ContactID.HasValue)
|
|
|
+ {
|
|
|
+ if(supplier.Code.Length > 50)
|
|
|
+ {
|
|
|
+ return Result.Error(new Exception("Customer code is longer than 50 characters"));
|
|
|
+ }
|
|
|
+ xeroSupplier = new XeroSupplier();
|
|
|
+ return UpdateSupplier(data, supplier, contacts, xeroSupplier)
|
|
|
+ .MapOk(() => new ContactService(data).Save(xeroSupplier)
|
|
|
+ .MapOk(x =>
|
|
|
+ {
|
|
|
+ supplier.PostedReference = x.ContactID.ToString() ?? "";
|
|
|
+ // Marking as repost because a script may not have run.
|
|
|
+ supplier.PostedStatus = PostedStatus.RequiresRepost;
|
|
|
+ return x.ContactID ?? Guid.Empty;
|
|
|
+ }))
|
|
|
+ .Flatten();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ supplier.PostedReference = xeroSupplier.ContactID.ToString() ?? "";
|
|
|
+ supplier.PostedStatus = PostedStatus.RequiresRepost;
|
|
|
+ return Result.Ok(xeroSupplier.ContactID.Value);
|
|
|
+ }
|
|
|
+ }).Flatten();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool IsBlankCode(string code)
|
|
|
+ {
|
|
|
+ return code.IsNullOrWhiteSpace() || code.Equals("*None");
|
|
|
+ }
|
|
|
+
|
|
|
+ public IPullResult<Supplier> Pull()
|
|
|
+ {
|
|
|
+ var result = new PullResult<Supplier>();
|
|
|
+
|
|
|
+ var supplierCodes = new HashSet<string>();
|
|
|
+
|
|
|
+ var page = 1;
|
|
|
+ var pageSize = 400;
|
|
|
+
|
|
|
+ var service = new ContactService(ConnectionData);
|
|
|
+ while (true)
|
|
|
+ {
|
|
|
+ if(!service.Query(null, page: page, pageSize: pageSize).Get(out var xeroSuppliers, out var error))
|
|
|
+ {
|
|
|
+ CoreUtils.LogException("", error);
|
|
|
+ throw new PullFailedMessageException(error.Message);
|
|
|
+ }
|
|
|
+ if(xeroSuppliers.Count == 0)
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ var xeroIDs = xeroSuppliers.ToArray(x => x.ContactID.ToString() ?? "");
|
|
|
+ var xeroCodes = xeroSuppliers.Select(x => x.ContactNumber).Where(x => !IsBlankCode(x)).ToArray();
|
|
|
+ var xeroNames = xeroSuppliers.Where(x => IsBlankCode(x.ContactNumber) && !x.Name.IsNullOrWhiteSpace())
|
|
|
+ .Select(x => x.Name).ToArray();
|
|
|
+
|
|
|
+ var suppliers = Client.Query(
|
|
|
+ Filter<Supplier>.Where(x => x.PostedReference).InList(xeroIDs)
|
|
|
+ .Or(x => x.Code).InList(xeroCodes)
|
|
|
+ .Or(x => x.Name).InList(xeroNames),
|
|
|
+ Columns.None<Supplier>().Add(x => x.ID).Add(x => x.PostedReference).Add(x => x.Code).Add(x => x.Name))
|
|
|
+ .ToArray<Supplier>();
|
|
|
+ var supplierDict = suppliers.Where(x => !x.PostedReference.IsNullOrWhiteSpace())
|
|
|
+ .ToDictionary(x => x.PostedReference);
|
|
|
+ var blankSuppliers = suppliers.Where(x => x.PostedReference.IsNullOrWhiteSpace()).ToArray();
|
|
|
+
|
|
|
+ var needCodes = new Dictionary<string, (string prefix, int i, Supplier supplier)>();
|
|
|
+
|
|
|
+ foreach(var xeroSupplier in xeroSuppliers)
|
|
|
+ {
|
|
|
+ if (supplierDict.TryGetValue(xeroSupplier.ContactID.ToString() ?? "", out var supplier))
|
|
|
+ {
|
|
|
+ // Skipping existing suppliers at this point.
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ supplier = !IsBlankCode(xeroSupplier.ContactNumber)
|
|
|
+ ? blankSuppliers.FirstOrDefault(x => string.Equals(x.Code, xeroSupplier.ContactNumber))
|
|
|
+ : blankSuppliers.FirstOrDefault(x => string.Equals(x.Name, xeroSupplier.Name));
|
|
|
+ if(supplier is not null)
|
|
|
+ {
|
|
|
+ supplier.PostedReference = xeroSupplier.ContactID.ToString() ?? "";
|
|
|
+ result.AddEntity(PullResultType.Linked, supplier);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ supplier = new Supplier();
|
|
|
+
|
|
|
+ string code;
|
|
|
+ if (!IsBlankCode(xeroSupplier.ContactNumber))
|
|
|
+ {
|
|
|
+ code = xeroSupplier.ContactID.ToString() ?? "";
|
|
|
+ }
|
|
|
+ else if (!xeroSupplier.Name.IsNullOrWhiteSpace())
|
|
|
+ {
|
|
|
+ code = xeroSupplier.Name[..Math.Min(3, xeroSupplier.Name.Length)].ToUpper();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ code = "SUP";
|
|
|
+ }
|
|
|
+ int i = 1;
|
|
|
+ supplier.Code = code;
|
|
|
+ while (supplierCodes.Contains(supplier.Code))
|
|
|
+ {
|
|
|
+ supplier.Code = $"{code}{i:d3}";
|
|
|
+ ++i;
|
|
|
+ }
|
|
|
+ supplierCodes.Add(supplier.Code);
|
|
|
+
|
|
|
+ supplier.Name = xeroSupplier.Name;
|
|
|
+ supplier.ABN = xeroSupplier.TaxNumber;
|
|
|
+ supplier.Email = xeroSupplier.EmailAddress;
|
|
|
+
|
|
|
+ if(xeroSupplier.Addresses is not null)
|
|
|
+ {
|
|
|
+ var postal = xeroSupplier.Addresses.FirstOrDefault(x => x.AddressType == XeroAccounting.Address.AddressTypeEnum.POBOX);
|
|
|
+ if(postal is not null)
|
|
|
+ {
|
|
|
+ supplier.Postal.CopyFrom(ContactXeroUtils.ConvertAddress(postal));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach(var contactPerson in xeroSupplier.ContactPersons)
|
|
|
+ {
|
|
|
+ var contact = new Contact();
|
|
|
+ contact.Name = $"{contactPerson.FirstName} {contactPerson.LastName}";
|
|
|
+ contact.Email = contactPerson.EmailAddress;
|
|
|
+ }
|
|
|
+
|
|
|
+ supplier.Telephone = xeroSupplier.Phones.FirstOrDefault()?.PhoneNumber ?? "";
|
|
|
+
|
|
|
+ supplier.PostedReference = xeroSupplier.ContactID.ToString() ?? "";
|
|
|
+ result.AddEntity(PullResultType.New, supplier);
|
|
|
+ needCodes.Add(supplier.Code, (code, i, supplier));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Do code clash checking
|
|
|
+ while(needCodes.Count > 0)
|
|
|
+ {
|
|
|
+ var codes = Client.Query(
|
|
|
+ Filter<Supplier>.Where(x => x.Code).InList(needCodes.Values.ToArray(x => x.supplier.Code)),
|
|
|
+ Columns.None<Supplier>().Add(x => x.Code));
|
|
|
+ var newNeedCodes = new Dictionary<string, (string prefix, int i, Supplier supplier)>();
|
|
|
+ foreach(var row in codes.Rows)
|
|
|
+ {
|
|
|
+ var code = row.Get<Supplier, string>(x => x.Code);
|
|
|
+ if(needCodes.Remove(code, out var needed))
|
|
|
+ {
|
|
|
+ int i = needed.i;
|
|
|
+ do
|
|
|
+ {
|
|
|
+ needed.supplier.Code = $"{needed.prefix}{i:d3}";
|
|
|
+ ++i;
|
|
|
+ } while (supplierCodes.Contains(needed.supplier.Code));
|
|
|
+ supplierCodes.Add(needed.supplier.Code);
|
|
|
+ newNeedCodes.Add(needed.supplier.Code, (needed.prefix, i, needed.supplier));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ needCodes = newNeedCodes;
|
|
|
+ }
|
|
|
+
|
|
|
+ ++page;
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ public IPostResult<Supplier> Process(IDataModel<Supplier> model)
|
|
|
+ {
|
|
|
+ var results = new PostResult<Supplier>();
|
|
|
+
|
|
|
+ var service = new ContactService(ConnectionData);
|
|
|
+
|
|
|
+ var suppliers = model.GetTable<Supplier>().ToArray<Supplier>();
|
|
|
+ var contacts = model.GetTable<SupplierContact>(alias: "Supplier_SupplierContact")
|
|
|
+ .ToObjects<SupplierContact>()
|
|
|
+ .GroupByDictionary(x => x.Supplier.ID);
|
|
|
+
|
|
|
+ foreach(var supplier in suppliers)
|
|
|
+ {
|
|
|
+ if(supplier.Code.Length > 50)
|
|
|
+ {
|
|
|
+ results.AddFailed(supplier, "Code is longer than 50 characters.");
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ var supplierContacts = contacts.GetValueOrDefault(supplier.ID) ?? [];
|
|
|
+
|
|
|
+ XeroSupplier xeroSupplier;
|
|
|
+ Exception? error;
|
|
|
+ if(Guid.TryParse(supplier.PostedReference, out var xeroID))
|
|
|
+ {
|
|
|
+ if(!service.Get(xeroID).Get(out var newSupplier, out error))
|
|
|
+ {
|
|
|
+ CoreUtils.LogException("", error, $"Failed to find Supplier in Xero with id {xeroID}");
|
|
|
+ results.AddFailed(supplier, $"Failed to find Supplier in Xero with id {xeroID}: {error.Message}");
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ xeroSupplier = newSupplier;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (service.GetItem(Filter<XeroSupplier>.Where(x => x.ContactNumber).IsEqualTo(supplier.Code))
|
|
|
+ .Get(out var externalXeroSupplier, out error)
|
|
|
+ && externalXeroSupplier is not null)
|
|
|
+ {
|
|
|
+ xeroSupplier = externalXeroSupplier;
|
|
|
+ }
|
|
|
+ else if (service.GetItem(
|
|
|
+ Filter<XeroSupplier>.Where(x => x.Name).IsEqualTo(supplier.Name)
|
|
|
+ .And(Filter<XeroSupplier>.Where(x => x.ContactNumber).IsEqualTo(null)
|
|
|
+ .Or(x => x.ContactNumber).IsEqualTo("")
|
|
|
+ .Or(x => x.ContactNumber).IsEqualTo("*None")))
|
|
|
+ .Get(out externalXeroSupplier, out error)
|
|
|
+ && externalXeroSupplier is not null)
|
|
|
+ {
|
|
|
+ xeroSupplier = externalXeroSupplier;
|
|
|
+ xeroSupplier.ContactNumber = supplier.Code;
|
|
|
+ }
|
|
|
+ else if(error is null)
|
|
|
+ {
|
|
|
+ xeroSupplier = new();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ CoreUtils.LogException("", error);
|
|
|
+ results.AddFailed(supplier, error.Message);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if(UpdateSupplier(ConnectionData, supplier, supplierContacts, xeroSupplier)
|
|
|
+ .MapOk(() => ProcessSupplier(model, supplier, xeroSupplier)).Flatten()
|
|
|
+ .MapOk(() => service.Save(xeroSupplier)).Flatten()
|
|
|
+ .Get(out var result, out error))
|
|
|
+ {
|
|
|
+ supplier.PostedReference = result.ContactID.ToString() ?? "";
|
|
|
+ results.AddSuccess(supplier);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ CoreUtils.LogException("", error, $"Error while posting supplier {supplier.ID}");
|
|
|
+ results.AddFailed(supplier, error.Message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return results;
|
|
|
+ }
|
|
|
+}
|
|
|
+public class SupplierMYOBPosterEngine<T> : XeroPosterEngine<Supplier, SupplierXeroPoster, SupplierXeroPosterSettings>, IPullerEngine<Supplier, SupplierXeroPoster>
|
|
|
+{
|
|
|
+ public IPullResult<Supplier> DoPull()
|
|
|
+ {
|
|
|
+ LoadConnectionData();
|
|
|
+ return Poster.Pull();
|
|
|
}
|
|
|
|
|
|
+ protected override IList<string> RequiredScopes()
|
|
|
+ {
|
|
|
+ return [
|
|
|
+ "accounting.contacts"
|
|
|
+ ];
|
|
|
+ }
|
|
|
}
|