MYOBPosterEngine.cs 8.0 KB

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