SupplierMYOBPoster.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. using Comal.Classes;
  2. using InABox.Clients;
  3. using InABox.Core;
  4. using InABox.Core.Postable;
  5. using InABox.Database;
  6. using InABox.Poster.MYOB;
  7. using InABox.Scripting;
  8. using MYOB.AccountRight.SDK.Services.Contact;
  9. using System;
  10. using System.Collections.Generic;
  11. using System.Linq;
  12. using System.Text;
  13. using System.Threading.Tasks;
  14. using MYOBSupplier = MYOB.AccountRight.SDK.Contracts.Version2.Contact.Supplier;
  15. namespace PRS.Shared.Posters.MYOB;
  16. public class SupplierMYOBPosterSettings : MYOBPosterSettings
  17. {
  18. public override string DefaultScript(Type TPostable)
  19. {
  20. return @"using MYOBSupplier = MYOB.AccountRight.SDK.Contracts.Version2.Contact.Supplier;
  21. public class Module
  22. {
  23. public void BeforePost(IDataModel<Supplier> model)
  24. {
  25. // Perform pre-processing
  26. }
  27. public void ProcessSupplier(IDataModel<Supplier> model, Supplier supplier, MYOBSupplier myobSupplier)
  28. {
  29. // Do extra processing for a supplier; throw an exception to fail this supplier.
  30. }
  31. }";
  32. }
  33. }
  34. public class SupplierMYOBAutoRefresher : IAutoRefresher<Supplier>
  35. {
  36. public bool ShouldRepost(Supplier supplier)
  37. {
  38. var shouldRepost = supplier.HasOriginalValue(x => x.Name)
  39. || supplier.HasOriginalValue(x => x.Code)
  40. || supplier.HasOriginalValue(x => x.ABN)
  41. || supplier.HasOriginalValue(x => x.Email)
  42. || supplier.HasOriginalValue(x => x.Telephone)
  43. || supplier.Delivery.HasOriginalValue(x => x.Street)
  44. || supplier.Delivery.HasOriginalValue(x => x.City)
  45. || supplier.Delivery.HasOriginalValue(x => x.State)
  46. || supplier.Delivery.HasOriginalValue(x => x.PostCode)
  47. || supplier.Postal.HasOriginalValue(x => x.Street)
  48. || supplier.Postal.HasOriginalValue(x => x.City)
  49. || supplier.Postal.HasOriginalValue(x => x.State)
  50. || supplier.Postal.HasOriginalValue(x => x.PostCode);
  51. if (shouldRepost)
  52. {
  53. return true;
  54. }
  55. if(supplier.SupplierStatus.HasOriginalValue(x => x.ID))
  56. {
  57. var originalID = supplier.SupplierStatus.GetOriginalValue(x => x.ID);
  58. var currentID = supplier.SupplierStatus.ID;
  59. var statuses = DbFactory.Provider.Query(
  60. new Filter<SupplierStatus>(x => x.ID).IsEqualTo(originalID).Or(x => x.ID).IsEqualTo(currentID),
  61. Columns.None<SupplierStatus>().Add(x => x.ID).Add(x => x.Active))
  62. .ToArray<SupplierStatus>();
  63. if (statuses.Length == 2 && statuses[0].Active != statuses[1].Active)
  64. {
  65. return true;
  66. }
  67. }
  68. return false;
  69. }
  70. }
  71. public class SupplierMYOBPoster : IMYOBPoster<Supplier, SupplierMYOBPosterSettings>, IAutoRefreshPoster<Supplier, SupplierMYOBAutoRefresher>
  72. {
  73. public ScriptDocument? Script { get; set; }
  74. public MYOBConnectionData ConnectionData { get; set; }
  75. public SupplierMYOBPosterSettings Settings { get; set; }
  76. public MYOBGlobalPosterSettings GlobalSettings { get; set; }
  77. public bool BeforePost(IDataModel<Supplier> model)
  78. {
  79. foreach (var (_, table) in model.ModelTables)
  80. {
  81. table.IsDefault = false;
  82. }
  83. model.SetIsDefault<Supplier>(true);
  84. model.SetColumns<Supplier>(RequiredColumns());
  85. Script?.Execute(methodname: "BeforePost", parameters: [model]);
  86. return true;
  87. }
  88. #region Script Functions
  89. private Result<Exception> ProcessSupplier(IDataModel<Supplier> model, Supplier supplier, MYOBSupplier myobSupplier)
  90. {
  91. return this.WrapScript("ProcessSupplier", model, supplier, myobSupplier);
  92. }
  93. #endregion
  94. public static Columns<Supplier> RequiredColumns()
  95. {
  96. return Columns.None<Supplier>()
  97. .Add(x => x.ID)
  98. .Add(x => x.PostedReference)
  99. .Add(x => x.PostedStatus)
  100. .Add(x => x.Name)
  101. .Add(x => x.Code)
  102. .Add(x => x.SupplierStatus.ID)
  103. .Add(x => x.SupplierStatus.Active)
  104. .Add(x => x.Postal.Street)
  105. .Add(x => x.Postal.City)
  106. .Add(x => x.Postal.State)
  107. .Add(x => x.Postal.PostCode)
  108. .Add(x => x.Delivery.Street)
  109. .Add(x => x.Delivery.City)
  110. .Add(x => x.Delivery.State)
  111. .Add(x => x.Delivery.PostCode)
  112. .Add(x => x.Email)
  113. .Add(x => x.Telephone)
  114. .Add(x => x.ABN);
  115. }
  116. public static Result<Exception> UpdateSupplier(MYOBConnectionData data, MYOBGlobalPosterSettings settings, Supplier supplier, MYOBSupplier myobSupplier, bool isNew)
  117. {
  118. // Documentation: https://developer.myob.com/api/myob-business-api/v2/contact/supplier/
  119. // Since this might be called from some other poster, we need to ensure we have the right columns.
  120. Client.EnsureColumns(supplier, RequiredColumns());
  121. // ContactMYOBUtils.SplitName(supplier.DefaultContact.Name, out var firstName, out var lastName);
  122. myobSupplier.CompanyName = supplier.Name.Truncate(50);
  123. // myobSupplier.FirstName =
  124. // myobSupplier.LastName =
  125. myobSupplier.IsIndividual = false;
  126. myobSupplier.DisplayID = supplier.Code.Truncate(15);
  127. // If there is not customer status, we will use default to Active = true.
  128. myobSupplier.IsActive = supplier.SupplierStatus.ID == Guid.Empty || supplier.SupplierStatus.Active;
  129. myobSupplier.Addresses =
  130. [
  131. ContactMYOBUtils.ConvertAddress(supplier.Postal, 1, new Contact
  132. {
  133. Email = supplier.Email,
  134. Telephone = supplier.Telephone
  135. }),
  136. ContactMYOBUtils.ConvertAddress(supplier.Delivery, 2, new Contact
  137. {
  138. Email = supplier.Email,
  139. Telephone = supplier.Telephone
  140. })
  141. ];
  142. // Notes =
  143. // PhotoURI =
  144. // RowVersion =
  145. myobSupplier.BuyingDetails ??= new();
  146. // myobSupplier.BuyingDetails.PurchaseLayout =
  147. // myobCustomer.BuyingDetails.PrintedForm =
  148. // myobSupplier.BuyingDetails.PurchaseOrderDelivery =
  149. // myobCustomer.BuyingDetails.ExpenseAccount.UID =
  150. // myobCustomer.BuyingDetails.PaymentMemo =
  151. // myobCustomer.BuyingDetails.PurchaseComment =
  152. // myobCustomer.BuyingDetails.SupplierBillingRate =
  153. // myobCustomer.BuyingDetails.ShippingMethod =
  154. // myobCustomer.BuyingDetails.IsReportable =
  155. // myobCustomer.BuyingDetails.CostPerHour =
  156. // myobCustomer.BuyingDetails.Credit.Limit =
  157. myobSupplier.BuyingDetails.ABN = supplier.ABN.Truncate(14);
  158. // myobCustomer.BuyingDetails.ABNBranch
  159. // myobCustomer.BuyingDetails.TaxIdNumber
  160. if (isNew)
  161. {
  162. if(!MYOBPosterUtils.GetDefaultTaxCode(data, settings).Get(out var taxID, out var error))
  163. {
  164. return Result.Error(error);
  165. }
  166. myobSupplier.BuyingDetails.TaxCode ??= new();
  167. myobSupplier.BuyingDetails.TaxCode.UID = taxID;
  168. myobSupplier.BuyingDetails.FreightTaxCode ??= new();
  169. myobSupplier.BuyingDetails.FreightTaxCode.UID = taxID;
  170. }
  171. // myobCustomer.BuyingDetails.UseSupplierTaxCode
  172. // myobCustomer.BuyingDetails.Terms
  173. // myobCustomer.PaymentDetails
  174. // myobCustomer.PhotoURI
  175. return Result.Ok();
  176. }
  177. /// <summary>
  178. /// Try to find a supplier in MYOB which matches <paramref name="supplier"/>, and if this fails, create a new one.
  179. /// </summary>
  180. /// <remarks>
  181. /// After this has finished, <paramref name="supplier"/> will be updated with <see cref="Supplier.PostedReference"/> set to the correct ID.
  182. /// <br/>
  183. /// <paramref name="supplier"/> needs to have at least <see cref="Supplier.Code"/> and <see cref="Supplier.PostedReference"/> as loaded columns.
  184. /// </remarks>
  185. /// <param name="data"></param>
  186. /// <param name="supplier">The supplier to map to.</param>
  187. /// <returns>The UID of the MYOB supplier.</returns>
  188. public static Result<Guid, Exception> MapSupplier(MYOBConnectionData data, Supplier supplier, MYOBGlobalPosterSettings settings)
  189. {
  190. if(Guid.TryParse(supplier.PostedReference, out var myobID))
  191. {
  192. return Result.Ok(myobID);
  193. }
  194. var service = new SupplierService(data.Configuration, null, data.AuthKey);
  195. var result = service.Query(data, new Filter<MYOBSupplier>(x => x.DisplayID).IsEqualTo(supplier.Code), top: 1);
  196. return result.MapOk(suppliers =>
  197. {
  198. if(suppliers.Items.Length == 0)
  199. {
  200. if(supplier.Code.Length > 15)
  201. {
  202. return Result.Error(new Exception("Customer code is longer than 15 characters"));
  203. }
  204. var myobSupplier = new MYOBSupplier();
  205. return UpdateSupplier(data, settings, supplier, myobSupplier, true)
  206. .MapOk(() => service.Save(data, myobSupplier)
  207. .MapOk(x =>
  208. {
  209. supplier.PostedReference = x.UID.ToString();
  210. // Marking as repost because a script may not have run.
  211. supplier.PostedStatus = PostedStatus.RequiresRepost;
  212. return x.UID;
  213. })).Flatten();
  214. }
  215. else
  216. {
  217. supplier.PostedReference = suppliers.Items[0].UID.ToString();
  218. supplier.PostedStatus = PostedStatus.RequiresRepost;
  219. return Result.Ok(suppliers.Items[0].UID);
  220. }
  221. }).Flatten();
  222. }
  223. public IPostResult<Supplier> Process(IDataModel<Supplier> model)
  224. {
  225. var results = new PostResult<Supplier>();
  226. var service = new SupplierService(ConnectionData.Configuration, null, ConnectionData.AuthKey);
  227. var suppliers = model.GetTable<Supplier>().ToArray<Supplier>();
  228. foreach(var supplier in suppliers)
  229. {
  230. if(supplier.Code.Length > 15)
  231. {
  232. results.AddFailed(supplier, "Code is longer than 15 characters.");
  233. continue;
  234. }
  235. bool isNew;
  236. MYOBSupplier myobSupplier;
  237. Exception? error;
  238. if(Guid.TryParse(supplier.PostedReference, out var myobID))
  239. {
  240. if(!service.Get(ConnectionData, myobID).Get(out var newSupplier, out error))
  241. {
  242. CoreUtils.LogException("", error, $"Failed to find Supplier in MYOB with id {myobID}");
  243. results.AddFailed(supplier, $"Failed to find Supplier in MYOB with id {myobID}: {error.Message}");
  244. continue;
  245. }
  246. myobSupplier = newSupplier;
  247. isNew = false;
  248. }
  249. else
  250. {
  251. if(service.Query(
  252. ConnectionData,
  253. new Filter<MYOBSupplier>(x => x.DisplayID).IsEqualTo(supplier.Code),
  254. top: 1).Get(out var myobSuppliers, out error))
  255. {
  256. if(myobSuppliers.Items.Length > 0)
  257. {
  258. myobSupplier = myobSuppliers.Items[0];
  259. isNew = false;
  260. }
  261. else
  262. {
  263. myobSupplier = new MYOBSupplier();
  264. isNew = true;
  265. }
  266. }
  267. else
  268. {
  269. CoreUtils.LogException("", error);
  270. results.AddFailed(supplier, error.Message);
  271. continue;
  272. }
  273. }
  274. if(UpdateSupplier(ConnectionData, GlobalSettings, supplier, myobSupplier, isNew)
  275. .MapOk(() => ProcessSupplier(model, supplier, myobSupplier)).Flatten()
  276. .MapOk(() => service.Save(ConnectionData, myobSupplier)).Flatten()
  277. .Get(out var result, out error))
  278. {
  279. supplier.PostedReference = result.UID.ToString();
  280. results.AddSuccess(supplier);
  281. }
  282. else
  283. {
  284. CoreUtils.LogException("", error, $"Error while posting supplier {supplier.ID}");
  285. results.AddFailed(supplier, error.Message);
  286. }
  287. }
  288. return results;
  289. }
  290. }
  291. public class SupplierMYOBPosterEngine<T> : MYOBPosterEngine<Supplier, SupplierMYOBPosterSettings> { }