CustomerMYOBPoster.cs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. using Comal.Classes;
  2. using FastReport.Data;
  3. using InABox.Core;
  4. using InABox.Core.Postable;
  5. using InABox.Poster.MYOB;
  6. using MYOB.AccountRight.SDK.Services;
  7. using MYOB.AccountRight.SDK.Services.Contact;
  8. using System;
  9. using System.Collections.Generic;
  10. using System.Linq;
  11. using System.Net;
  12. using System.Text;
  13. using System.Threading.Tasks;
  14. using Customer = Comal.Classes.Customer;
  15. using MYOBCustomer = MYOB.AccountRight.SDK.Contracts.Version2.Contact.Customer;
  16. using MYOBAddress = MYOB.AccountRight.SDK.Contracts.Version2.Contact.Address;
  17. using MYOB.AccountRight.SDK.Contracts.Version2.Sale;
  18. namespace PRS.Shared.Posters.MYOB;
  19. public class CustomerMYOBPosterSettings : MYOBPosterSettings
  20. {
  21. [TextBoxEditor(ToolTip = "The MYOB tax code which should be used when posting customers")]
  22. public string DefaultTaxCode { get; set; }
  23. }
  24. public class CustomerMYOBPoster : IMYOBPoster<Customer, CustomerMYOBPosterSettings>
  25. {
  26. public CustomerMYOBPosterSettings Settings { get; set; }
  27. public MYOBGlobalPosterSettings GlobalSettings { get; set; }
  28. public MYOBConnectionData ConnectionData { get; set; }
  29. private static void SplitName(string name, out string firstName, out string lastName)
  30. {
  31. var names = name.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
  32. firstName = names.Length > 0 ? names[0] : "";
  33. lastName = names.Length > 1 ? names[1] : "";
  34. }
  35. private static MYOBAddress ConvertAddress(Address address, int location, IContact contact)
  36. {
  37. return new MYOBAddress
  38. {
  39. Location = location,
  40. Street = address.Street.Truncate(255),
  41. City = address.City.Truncate(255),
  42. State = address.State.Truncate(255),
  43. PostCode = address.PostCode.Truncate(11),
  44. Phone1 = contact.Mobile.Truncate(21),
  45. Phone2 = contact.Telephone.Truncate(21),
  46. Email = contact.Email.Truncate(255),
  47. ContactName = contact.Name.Truncate(25)
  48. };
  49. }
  50. public bool BeforePost(IDataModel<Customer> model)
  51. {
  52. foreach (var (_, table) in model.ModelTables)
  53. {
  54. table.IsDefault = false;
  55. }
  56. model.SetIsDefault<Customer>(true);
  57. return true;
  58. }
  59. public static Result<Exception> UpdateCustomer(MYOBConnectionData data, CustomerMYOBPosterSettings settings, Customer customer, MYOBCustomer myobCustomer, bool isNew)
  60. {
  61. // Documentation: https://developer.myob.com/api/myob-business-api/v2/contact/customer/
  62. SplitName(customer.DefaultContact.Name, out var firstName, out var lastName);
  63. myobCustomer.CompanyName = customer.Name.Truncate(50);
  64. myobCustomer.FirstName = firstName.Truncate(30);
  65. myobCustomer.LastName = lastName.Truncate(20);
  66. myobCustomer.IsIndividual = false;
  67. myobCustomer.DisplayID = customer.Code.Truncate(15);
  68. myobCustomer.IsActive = customer.CustomerStatus.Active;
  69. myobCustomer.Addresses =
  70. [
  71. ConvertAddress(customer.Delivery, 1, customer.DefaultContact),
  72. ConvertAddress(customer.Postal, 2, customer.DefaultContact)
  73. ];
  74. // Notes =
  75. // PhotoURI =
  76. // RowVersion =
  77. myobCustomer.SellingDetails.SaleLayout = InvoiceLayoutType.NoDefault;
  78. // myobCustomer.SellingDetails.PrintedFOrm =
  79. myobCustomer.SellingDetails.InvoiceDelivery = DocumentAction.PrintAndEmail;
  80. // myobCustomer.SellingDetails.IncomeAccount =
  81. // myobCustomer.SellingDetails.ReceiptMemo =
  82. // myobCustomer.SellingDetails.SalesPerson =
  83. // myobCustomer.SellingDetails.SaleComment =
  84. // myobCustomer.SellingDetails.ShippingMethod =
  85. // myobCustomer.SellingDetails.HourlyBillRate =
  86. // myobCustomer.SellingDetails.ABNBranch =
  87. myobCustomer.SellingDetails.ABN = customer.ABN.Truncate(14);
  88. if (isNew)
  89. {
  90. if (settings.DefaultTaxCode.IsNullOrWhiteSpace())
  91. {
  92. throw new PostFailedMessageException("Default tax code has not been set up.");
  93. }
  94. else if(data.GetMYOBTaxCodeUID(settings.DefaultTaxCode).Get(out var taxID, out var error))
  95. {
  96. if (taxID == Guid.Empty)
  97. {
  98. return Result.Error(new Exception($"Failed to find TaxCode in MYOB with code {settings.DefaultTaxCode}"));
  99. }
  100. myobCustomer.SellingDetails.TaxCode.UID = taxID;
  101. myobCustomer.SellingDetails.FreightTaxCode.UID = taxID;
  102. }
  103. else
  104. {
  105. CoreUtils.LogException("", error, $"Failed to find TaxCode in MYOB with code {settings.DefaultTaxCode}");
  106. return Result.Error(new Exception($"Failed to find TaxCode in MYOB with code {settings.DefaultTaxCode}: {error.Message}", error));
  107. }
  108. }
  109. return Result.Ok();
  110. }
  111. /// <summary>
  112. /// Try to find a customer in MYOB which matches <paramref name="customer"/>, and if this fails, create a new one.
  113. /// </summary>
  114. /// <remarks>
  115. /// After this has finished, <paramref name="customer"/> will be updated with <see cref="Customer.PostedReference"/> set to the correct ID.
  116. /// <br/>
  117. /// <paramref name="customer"/> needs to have at least <see cref="Customer.Code"/> and <see cref="Customer.PostedReference"/> as loaded columns.
  118. /// </remarks>
  119. /// <param name="data"></param>
  120. /// <param name="customer">The customer to map to.</param>
  121. /// <returns>The UID of the MYOB customer.</returns>
  122. public static Result<Guid, Exception> MapCustomer(MYOBConnectionData data, Customer customer)
  123. {
  124. if(Guid.TryParse(customer.PostedReference, out var myobID))
  125. {
  126. return new Result<Guid, Exception>(myobID);
  127. }
  128. var service = new CustomerService(data.Configuration, null, data.AuthKey);
  129. var result = service.Query(data, new Filter<MYOBCustomer>(x => x.DisplayID).IsEqualTo(customer.Code), top: 1);
  130. return result.MapOk(customers =>
  131. {
  132. if(customers.Count == 0)
  133. {
  134. if(customer.Code.Length > 15)
  135. {
  136. return Result.Error(new Exception("Customer code is longer than 15 characters"));
  137. }
  138. var myobCustomer = new MYOBCustomer();
  139. return UpdateCustomer(data, PosterUtils.LoadPosterSettings<Customer, CustomerMYOBPosterSettings>(), customer, myobCustomer, true)
  140. .MapOk<Result<Guid, Exception>>(() =>
  141. {
  142. try
  143. {
  144. var result = service.UpdateEx(data.CompanyFile, myobCustomer, data.CompanyFileCredentials);
  145. customer.PostedReference = result.UID.ToString();
  146. return Result.Ok(result.UID);
  147. }
  148. catch (Exception e)
  149. {
  150. CoreUtils.LogException("", e, $"Error while posting customer {customer.ID}");
  151. return Result.Error(e);
  152. }
  153. }).Flatten();
  154. }
  155. else
  156. {
  157. customer.PostedReference = customers.Items[0].UID.ToString();
  158. return Result.Ok(customers.Items[0].UID);
  159. }
  160. }).Flatten();
  161. }
  162. public IPostResult<Customer> Process(IDataModel<Customer> model)
  163. {
  164. var results = new PostResult<Customer>();
  165. var service = new CustomerService(ConnectionData.Configuration, null, ConnectionData.AuthKey);
  166. var customers = model.GetTable<Customer>().ToArray<Customer>();
  167. foreach(var customer in customers)
  168. {
  169. if(customer.Code.Length > 15)
  170. {
  171. results.AddFailed(customer, "Code is longer than 15 characters.");
  172. continue;
  173. }
  174. bool isNew;
  175. MYOBCustomer myobCustomer;
  176. Exception? error;
  177. if(Guid.TryParse(customer.PostedReference, out var myobID))
  178. {
  179. if(!service.Get(ConnectionData, myobID).Get(out var newCustomer, out error))
  180. {
  181. CoreUtils.LogException("", error, $"Failed to find Customer in MYOB with id {myobID}");
  182. results.AddFailed(customer, $"Failed to find Customer in MYOB with id {myobID}: {error.Message}");
  183. continue;
  184. }
  185. myobCustomer = newCustomer;
  186. isNew = false;
  187. }
  188. else
  189. {
  190. myobCustomer = new MYOBCustomer();
  191. isNew = true;
  192. }
  193. if(UpdateCustomer(ConnectionData, Settings, customer, myobCustomer, isNew).Get(out error))
  194. {
  195. try
  196. {
  197. var result = service.Update(ConnectionData.CompanyFile, myobCustomer, ConnectionData.CompanyFileCredentials);
  198. results.AddSuccess(customer);
  199. }
  200. catch(Exception e)
  201. {
  202. CoreUtils.LogException("", e, $"Error while posting customer {customer.ID}");
  203. results.AddFailed(customer, e.Message);
  204. }
  205. }
  206. else
  207. {
  208. results.AddFailed(customer, error.Message);
  209. }
  210. }
  211. return results;
  212. }
  213. }
  214. public class CustomerMYOBPosterEngine<T> : MYOBPosterEngine<Customer, CustomerMYOBPosterSettings> { }