MYOBPosterEngine.cs 8.2 KB

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