MYOBPosterEngine.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. using InABox.Core;
  2. using InABox.Core.Postable;
  3. using InABox.DynamicGrid;
  4. using Microsoft.Web.WebView2.Wpf;
  5. using MYOB.AccountRight.SDK;
  6. using MYOB.AccountRight.SDK.Contracts;
  7. using MYOB.AccountRight.SDK.Services;
  8. using System;
  9. using System.Collections.Generic;
  10. using System.Linq;
  11. using System.Text;
  12. using System.Text.RegularExpressions;
  13. using System.Threading.Tasks;
  14. using System.Web;
  15. using System.Windows;
  16. using System.Windows.Controls;
  17. using System.Windows.Threading;
  18. namespace InABox.Poster.MYOB;
  19. public class MYOBConnectionData(ApiConfiguration configuration, CompanyFileService cfService, IOAuthKeyService authKey)
  20. {
  21. public ApiConfiguration Configuration { get; set; } = configuration;
  22. public CompanyFileService CompanyFileService { get; set; } = cfService;
  23. public CompanyFile? CompanyFile { get; set; }
  24. public CompanyFileCredentials? CompanyFileCredentials { get; set; }
  25. public CompanyFileWithResources? ActiveCompanyFile { get; set; }
  26. public IOAuthKeyService AuthKey { get; set; } = authKey;
  27. }
  28. public static partial class MYOBPosterEngine
  29. {
  30. internal static readonly string DEV_KEY = "f3b27f88-2ef9-4d8e-95c1-d0a045d0afee";
  31. internal static readonly string SECRET_KEY = "ksm0e8yo6oumcPb63A8cduaN";
  32. internal const string AUTH_URL = "https://secure.myob.com/oauth2/account/authorize";
  33. internal const string AUTH_SCOPE = "CompanyFile";
  34. internal const string REDIRECT_URL = "http://desktop";
  35. private static MYOBConnectionData? _connectionData;
  36. public static bool GetAuthorisationCode(IApiConfiguration config, out string? code)
  37. {
  38. var url = $"{AUTH_URL}?client_id={config.ClientId}&redirect_uri={HttpUtility.UrlEncode(config.RedirectUrl)}&scope={AUTH_SCOPE}&response_type=code";
  39. var window = new Window
  40. {
  41. Width = 800,
  42. Height = 600,
  43. Title = "Sign in to MYOB"
  44. };
  45. string? resultCode = null;
  46. var view = new WebView2
  47. {
  48. };
  49. view.NavigationCompleted += (o, e) =>
  50. {
  51. view.ExecuteScriptAsync("document.documentElement.innerHTML;").ContinueWith(task =>
  52. {
  53. if (task.Result is null) return;
  54. var str = Serialization.Deserialize<string>(task.Result);
  55. if (str is null) return;
  56. var match = CodeRegex().Match(str);
  57. if (!match.Success) return;
  58. resultCode = match.Groups[1].Value;
  59. window.Dispatcher.BeginInvoke(() =>
  60. {
  61. if(window.DialogResult is null)
  62. {
  63. window.DialogResult = true;
  64. window.Close();
  65. }
  66. });
  67. });
  68. };
  69. view.Source = new Uri(url);
  70. window.Content = view;
  71. if(window.ShowDialog() == true)
  72. {
  73. code = resultCode;
  74. return true;
  75. }
  76. else
  77. {
  78. code = null;
  79. return false;
  80. }
  81. }
  82. public static MYOBConnectionData GetConnectionData()
  83. {
  84. if(_connectionData is MYOBConnectionData data)
  85. {
  86. return data;
  87. }
  88. var configuration = new ApiConfiguration(DEV_KEY, SECRET_KEY, REDIRECT_URL);
  89. var authService = new OAuthService(configuration);
  90. if(!GetAuthorisationCode(configuration, out var code))
  91. {
  92. throw new PostCancelledException();
  93. }
  94. if(code is null)
  95. {
  96. throw new PostFailedMessageException("No authorisation code was received.");
  97. }
  98. var keystore = new SimpleOAuthKeyService
  99. {
  100. OAuthResponse = authService.GetTokens(code)
  101. };
  102. var cfService = new CompanyFileService(configuration, null, keystore);
  103. _connectionData = new MYOBConnectionData(configuration, cfService, keystore);
  104. return _connectionData;
  105. }
  106. [GeneratedRegex("code=(.*)<")]
  107. private static partial Regex CodeRegex();
  108. }
  109. public abstract class MYOBPosterEngine<TPostable, TSettings> :
  110. PosterEngine<TPostable, IMYOBPoster<TPostable, TSettings>, TSettings>,
  111. IGlobalSettingsPosterEngine<IMYOBPoster<TPostable, TSettings>, MYOBGlobalPosterSettings>
  112. where TPostable : Entity, IPostable, IRemotable, IPersistent, new()
  113. where TSettings : MYOBPosterSettings, new()
  114. {
  115. private MYOBGlobalPosterSettings GetGlobalSettings() =>
  116. (this as IGlobalSettingsPosterEngine<IMYOBPoster<TPostable, TSettings>, MYOBGlobalPosterSettings>).GetGlobalSettings();
  117. private void SaveGlobalSettings(MYOBGlobalPosterSettings settings) =>
  118. (this as IGlobalSettingsPosterEngine<IMYOBPoster<TPostable, TSettings>, MYOBGlobalPosterSettings>).SaveGlobalSettings(settings);
  119. public override bool BeforePost(IDataModel<TPostable> model)
  120. {
  121. return Poster.BeforePost(model);
  122. }
  123. protected override IPostResult<TPostable> DoProcess(IDataModel<TPostable> model)
  124. {
  125. var data = MYOBPosterEngine.GetConnectionData();
  126. var globalSettings = GetGlobalSettings();
  127. if(data.CompanyFile is null || data.CompanyFile.Id != globalSettings.CompanyFile.ID)
  128. {
  129. CompanyFile? file;
  130. if(globalSettings.CompanyFile.ID == Guid.Empty)
  131. {
  132. file = MYOBCompanyFileSelectionDialog.SelectCompanyFile();
  133. if(file is null)
  134. {
  135. throw new PostCancelledException();
  136. }
  137. else
  138. {
  139. globalSettings.CompanyFile.ID = file.Id;
  140. globalSettings.CompanyFile.Name = file.Name;
  141. SaveGlobalSettings(globalSettings);
  142. globalSettings.CommitChanges();
  143. }
  144. }
  145. if(globalSettings.CompanyFileUserID.IsNullOrWhiteSpace())
  146. {
  147. var credentials = new MYOBCompanyFileCredentials
  148. {
  149. UserID = globalSettings.CompanyFileUserID,
  150. Password = globalSettings.CompanyFilePassword,
  151. };
  152. if (DynamicGridUtils.EditObject(credentials, customiseGrid: grid =>
  153. {
  154. grid.OnValidate += (grid, items, errors) =>
  155. {
  156. if(items.Any(x => x.UserID.IsNullOrWhiteSpace()))
  157. {
  158. errors.Add("[UserID] cannot be blank");
  159. }
  160. };
  161. }))
  162. {
  163. globalSettings.CompanyFileUserID = credentials.UserID;
  164. globalSettings.CompanyFilePassword = credentials.Password;
  165. SaveGlobalSettings(globalSettings);
  166. globalSettings.CommitChanges();
  167. }
  168. else
  169. {
  170. throw new PostCancelledException();
  171. }
  172. }
  173. var companyFile = data.CompanyFileService.GetRange().FirstOrDefault(x => x.Id == globalSettings.CompanyFile.ID);
  174. var fileCredentials = new CompanyFileCredentials(globalSettings.CompanyFileUserID, globalSettings.CompanyFilePassword);
  175. data.CompanyFile = companyFile;
  176. data.CompanyFileCredentials = fileCredentials;
  177. data.ActiveCompanyFile = data.CompanyFileService.Get(companyFile, fileCredentials);
  178. }
  179. Poster.ConnectionData = data;
  180. return Poster.Process(model);
  181. }
  182. public override void AfterPost(IDataModel<TPostable> model, IPostResult<TPostable> result)
  183. {
  184. }
  185. }