MYOBPosterEngine.cs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  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, TPoster, TSettings> :
  116. BasePosterEngine<TPostable, TPoster, TSettings>,
  117. IGlobalSettingsPosterEngine<TPoster, MYOBGlobalPosterSettings>
  118. where TPostable : Entity, IPostable, IRemotable, IPersistent, new()
  119. where TSettings : MYOBPosterSettings, new()
  120. where TPoster : class, IMYOBPoster<TPostable, TSettings>
  121. {
  122. private MYOBGlobalPosterSettings GetGlobalSettings() =>
  123. (this as IGlobalSettingsPosterEngine<TPoster, MYOBGlobalPosterSettings>).GetGlobalSettings();
  124. private void SaveGlobalSettings(MYOBGlobalPosterSettings settings) =>
  125. (this as IGlobalSettingsPosterEngine<TPoster, MYOBGlobalPosterSettings>).SaveGlobalSettings(settings);
  126. protected override TPoster CreatePoster()
  127. {
  128. var poster = base.CreatePoster();
  129. poster.Script = GetScriptDocument();
  130. return poster;
  131. }
  132. public override bool BeforePost(IDataModel<TPostable> model)
  133. {
  134. return Poster.BeforePost(model);
  135. }
  136. protected void LoadConnectionData()
  137. {
  138. var data = MYOBPosterEngine.GetConnectionData();
  139. var globalSettings = GetGlobalSettings();
  140. if(data.CompanyFile is null || data.CompanyFile.Id != globalSettings.CompanyFile.ID)
  141. {
  142. CompanyFile? file;
  143. if(globalSettings.CompanyFile.ID == Guid.Empty)
  144. {
  145. file = MYOBCompanyFileSelectionDialog.SelectCompanyFile();
  146. if(file is null)
  147. {
  148. throw new PostCancelledException();
  149. }
  150. else
  151. {
  152. globalSettings.CompanyFile.ID = file.Id;
  153. globalSettings.CompanyFile.Name = file.Name;
  154. SaveGlobalSettings(globalSettings);
  155. globalSettings.CommitChanges();
  156. }
  157. }
  158. if(!globalSettings.NoCredentials && globalSettings.CompanyFileUserID.IsNullOrWhiteSpace())
  159. {
  160. var credentials = new MYOBCompanyFileCredentials
  161. {
  162. UserID = globalSettings.CompanyFileUserID,
  163. Password = globalSettings.CompanyFilePassword,
  164. NoCredentials = globalSettings.NoCredentials
  165. };
  166. if (DynamicGridUtils.EditObject(credentials, customiseGrid: grid =>
  167. {
  168. grid.OnValidate += (grid, items, errors) =>
  169. {
  170. var item = items.FirstOrDefault();
  171. if (item is null) return;
  172. if(!item.NoCredentials && item.UserID.IsNullOrWhiteSpace())
  173. {
  174. errors.Add("[UserID] cannot be blank");
  175. }
  176. };
  177. }))
  178. {
  179. globalSettings.NoCredentials = credentials.NoCredentials;
  180. globalSettings.CompanyFileUserID = credentials.UserID;
  181. globalSettings.CompanyFilePassword = credentials.Password;
  182. SaveGlobalSettings(globalSettings);
  183. globalSettings.CommitChanges();
  184. }
  185. else
  186. {
  187. throw new PostCancelledException();
  188. }
  189. }
  190. var companyFile = data.CompanyFileService.GetRange().FirstOrDefault(x => x.Id == globalSettings.CompanyFile.ID);
  191. var fileCredentials = new CompanyFileCredentials(globalSettings.CompanyFileUserID, globalSettings.CompanyFilePassword);
  192. data.CompanyFile = companyFile;
  193. data.CompanyFileCredentials = fileCredentials;
  194. // data.ActiveCompanyFile = data.CompanyFileService.Get(companyFile, fileCredentials);
  195. }
  196. Poster.ConnectionData = data;
  197. }
  198. protected override IPostResult<TPostable> DoProcess(IDataModel<TPostable> model)
  199. {
  200. LoadConnectionData();
  201. return Poster.Process(model);
  202. }
  203. public override void AfterPost(IDataModel<TPostable> model, IPostResult<TPostable> result)
  204. {
  205. }
  206. }