Browse Source

Allowing for MYOB pulling

Kenric Nugteren 11 tháng trước cách đây
mục cha
commit
51be745be6

+ 3 - 3
InABox.Core/CoreUtils.cs

@@ -2718,10 +2718,10 @@ namespace InABox.Core
             return enumerable.ToArray<T>();
         }
 
-        public static U[] ToArray<T, U>(this T[] from, Func<T, U> mapFunc)
+        public static U[] ToArray<T, U>(this IList<T> from, Func<T, U> mapFunc)
         {
-            var to = new U[from.Length];
-            for(int i = 0; i < from.Length; ++i)
+            var to = new U[from.Count];
+            for(int i = 0; i < from.Count; ++i)
             {
                 to[i] = mapFunc(from[i]);
             }

+ 72 - 15
InABox.Core/Postable/PostExceptions.cs

@@ -5,24 +5,15 @@ using System.Text;
 
 namespace InABox.Core.Postable
 {
-    public class PostException : Exception
-    {
-        public PostException() : base() { }
+    #region BaseExceptions
 
-        public PostException(string message) : base(message) { }
-    }
-
-    public class EmptyPostException : PostException
+    public class BasePosterException : Exception
     {
-        public EmptyPostException() { }
-    }
+        public BasePosterException() : base() { }
 
-    public class PostFailedMessageException : PostException
-    {
-        public PostFailedMessageException(string message): base(message) { }
+        public BasePosterException(string message) : base(message) { }
     }
-
-    public class MissingSettingException : PostException
+    public class MissingSettingException : BasePosterException
     {
         public Type SettingsType { get; }
 
@@ -40,7 +31,7 @@ namespace InABox.Core.Postable
         public MissingSettingException(Expression<Func<T, object?>> setting) : base(typeof(T), CoreUtils.GetFullPropertyName(setting, ".")) { }
     }
 
-    public class MissingSettingsException : PostException
+    public class MissingSettingsException : BasePosterException
     {
         public Type PostableType { get; }
 
@@ -49,6 +40,28 @@ namespace InABox.Core.Postable
             PostableType = postableType;
         }
     }
+
+    #endregion
+
+    #region PostExceptions
+
+    public class PostException : BasePosterException
+    {
+        public PostException() : base() { }
+
+        public PostException(string message) : base(message) { }
+    }
+
+    public class EmptyPostException : PostException
+    {
+        public EmptyPostException() { }
+    }
+
+    public class PostFailedMessageException : PostException
+    {
+        public PostFailedMessageException(string message): base(message) { }
+    }
+
     public class RepostedException : PostException
     {
         public RepostedException() : base("Cannot process an item twice.")
@@ -61,4 +74,48 @@ namespace InABox.Core.Postable
         {
         }
     }
+
+    #endregion
+
+    #region PullExceptions
+
+    public class PullException : BasePosterException
+    {
+        public PullException() : base() { }
+
+        public PullException(string message) : base(message) { }
+    }
+
+    public class PullCancelledException : PullException
+    {
+        public PullCancelledException(): base("Import cancelled") { }
+    }
+
+    public class PullFailedMessageException : PullException
+    {
+        public PullFailedMessageException(string message): base(message) { }
+    }
+
+    public class InvalidPullerException : PullException
+    {
+        public Type PostableType { get; set; }
+
+        public Type? PosterType { get; set; }
+
+        public InvalidPullerException(Type postableType)
+            : base($"Cannot import {postableType.Name}")
+        {
+            PostableType = postableType;
+            PosterType = null;
+        }
+
+        public InvalidPullerException(Type postableType, Type posterType)
+            : base($"Cannot import {postableType.Name} with {posterType.GetCaptionOrNull(true) ?? posterType.Name}")
+        {
+            PostableType = postableType;
+            PosterType = posterType;
+        }
+    }
+
+    #endregion
 }

+ 44 - 0
InABox.Core/Postable/PostResult.cs

@@ -74,4 +74,48 @@ namespace InABox.Core
             fragmentsList.Add(fragment);
         }
     }
+
+    public enum PullResultType
+    {
+        New,
+        Linked,
+        Updated
+    }
+
+    public class PullResultItem<TPostable>
+        where TPostable : IPostable
+    {
+        public PullResultType Type { get; set; }
+
+        public TPostable Item { get; set; }
+
+        public PullResultItem(PullResultType type, TPostable item)
+        {
+            Type = type;
+            Item = item;
+        }
+    }
+
+    public interface IPullResult<TPostable>
+        where TPostable : IPostable
+    {
+        public IEnumerable<PullResultItem<TPostable>> PulledEntities { get; }
+    }
+
+    public class PullResult<TPostable> : IPullResult<TPostable>
+        where TPostable : IPostable
+    {
+        private List<PullResultItem<TPostable>> posts = new List<PullResultItem<TPostable>>();
+
+        /// <summary>
+        /// All successful or failed <typeparamref name="TPostable"/>s.
+        /// </summary>
+        public IEnumerable<PullResultItem<TPostable>> PulledEntities => posts;
+
+        public void AddEntity(PullResultType type, TPostable post)
+        {
+            post.Post();
+            posts.Add(new PullResultItem<TPostable>(type, post));
+        }
+    }
 }

+ 39 - 0
InABox.Core/Postable/PosterEngine.cs

@@ -195,4 +195,43 @@ namespace InABox.Core
             }
         }
     }
+
+    public interface IPullerEngine<TPostable>
+        where TPostable : Entity, IPostable, IRemotable, IPersistent, new()
+    {
+        IPullResult<TPostable>? Pull();
+    }
+
+    public interface IPullerEngine<TPostable, TPoster> : IPullerEngine<TPostable>
+        where TPostable : Entity, IPostable, IRemotable, IPersistent, new()
+    {
+        IPullResult<TPostable> DoPull();
+
+        IPullResult<TPostable>? IPullerEngine<TPostable>.Pull()
+        {
+            try
+            {
+                var result = DoPull();
+
+                return result;
+            }
+            catch (PullCancelledException)
+            {
+                throw;
+            }
+            catch (MissingSettingException)
+            {
+                throw;
+            }
+            catch (PullFailedMessageException)
+            {
+                throw;
+            }
+            catch(Exception e)
+            {
+                Logger.Send(LogType.Error, "", $"Post Failed: {CoreUtils.FormatException(e)}");
+                throw;
+            }
+        }
+    }
 }

+ 84 - 19
InABox.Core/Postable/PosterUtils.cs

@@ -23,10 +23,15 @@ namespace InABox.Core
             return settings;
         }
 
+        private static PostableSettings LoadPostableSettings(Type T)
+        {
+            return FixPostableSettings(T, new GlobalConfiguration<PostableSettings>(T.Name).Load());
+        }
+
         public static PostableSettings LoadPostableSettings<T>()
             where T : Entity, IPostable
         {
-            return FixPostableSettings(typeof(T), new GlobalConfiguration<PostableSettings>(typeof(T).Name).Load());
+            return LoadPostableSettings(typeof(T));
         }
 
         public static void SavePostableSettings<T>(PostableSettings settings)
@@ -140,6 +145,10 @@ namespace InABox.Core
             return GetPosters().Where(x => TPoster.IsAssignableFrom(x)).FirstOrDefault()
                 ?? throw new Exception($"No poster of type {TPoster}.");
         }
+        public static Type? GetPoster(string posterType)
+        {
+            return GetPosters()?.FirstOrDefault(x => x.EntityName() == posterType)!;
+        }
 
         private static EngineType[] GetPosterEngines()
         {
@@ -149,43 +158,71 @@ namespace InABox.Core
                     && !x.IsAbstract
                     && x.GetTypeInfo().GenericTypeParameters.Length == 1
                     && x.HasInterface(typeof(IPosterEngine<,,>))
-            ).Select(x => new EngineType
+            ).Select(x =>
             {
-                Engine = x,
-                Entity = x.GetInterfaceDefinition(typeof(IPosterEngine<,,>))!.GenericTypeArguments[0],
-                Poster = x.GetInterfaceDefinition(typeof(IPosterEngine<,,>))!.GenericTypeArguments[1].GetGenericTypeDefinition()
+                var poster = x.GetInterfaceDefinition(typeof(IPosterEngine<,,>))!.GenericTypeArguments[1];
+                return new EngineType
+                {
+                    Engine = x,
+                    Entity = x.GetInterfaceDefinition(typeof(IPosterEngine<,,>))!.GenericTypeArguments[0],
+                    Poster = poster.IsGenericType ? poster.GetGenericTypeDefinition() : poster
+                };
             }).ToArray();
             return _posterEngines;
         }
 
         /// <summary>
-        /// Get the <see cref="IPosterEngine{TPostable,TPoster,TSettings}"/> for <typeparamref name="T"/>
-        /// based on the current <see cref="PostableSettings"/> for <typeparamref name="T"/>.
+        /// Get the <see cref="IPosterEngine{TPostable,TPoster,TSettings}"/> for <paramref name="T"/>
+        /// based on the current <see cref="PostableSettings"/> for <paramref name="T"/>.
         /// </summary>
-        /// <typeparam name="T"></typeparam>
         /// <returns></returns>
-        public static Type GetEngine<T>()
-            where T : Entity, IPostable, IRemotable, IPersistent, new()
+        public static Result<Type, Exception> GetEngine(Type T)
         {
-            var settings = LoadPostableSettings<T>();
+            var settings = LoadPostableSettings(T);
             if (string.IsNullOrWhiteSpace(settings.PosterType))
             {
-                throw new MissingSettingsException(typeof(T));
+                return Result.Error<Exception>(new MissingSettingsException(T));
+            }
+            var poster = GetPoster(settings.PosterType);
+            if(poster is null)
+            {
+                return Result.Error(new Exception($"No poster of type {settings.PosterType}."));
             }
-            var poster = GetPosters()?.FirstOrDefault(x => x.EntityName() == settings.PosterType)!;
 
-            var engines = GetPosterEngines().Where(x => poster.HasInterface(x.Poster)).ToList();
+            var engines = GetPosterEngines().Where(x =>
+            {
+                return x.Poster.IsInterface ? poster.HasInterface(x.Poster) : poster.IsSubclassOfRawGeneric(x.Poster);
+            }).ToList();
             if (!engines.Any())
             {
-                throw new Exception("No poster for the given settings.");
+                return Result.Error(new Exception("No poster for the given settings"));
             }
             else if(engines.Count == 1)
             {
-                return engines[0].Engine.MakeGenericType(typeof(T));
+                return Result.Ok(engines[0].Engine.MakeGenericType(T));
             }
             else
             {
-                return engines.Single(x => x.Entity == typeof(T)).Engine.MakeGenericType(typeof(T));
+                return Result.Ok(engines.Single(x => x.Entity == T).Engine.MakeGenericType(T));
+            }
+        }
+
+        /// <summary>
+        /// Get the <see cref="IPosterEngine{TPostable,TPoster,TSettings}"/> for <typeparamref name="T"/>
+        /// based on the current <see cref="PostableSettings"/> for <typeparamref name="T"/>.
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <returns></returns>
+        public static Type GetEngine<T>()
+            where T : Entity, IPostable, IRemotable, IPersistent, new()
+        {
+            if (GetEngine(typeof(T)).Get(out var engineType, out var e))
+            {
+                return engineType;
+            }
+            else
+            {
+                throw e;
             }
         }
 
@@ -226,7 +263,7 @@ namespace InABox.Core
             {
                 return false;
             }
-            var poster = GetPosters()?.FirstOrDefault(x => x.EntityName() == settings.PosterType);
+            var poster = GetPoster(settings.PosterType);
             if (poster is null)
             {
                 return false;
@@ -235,7 +272,7 @@ namespace InABox.Core
             if(iautoRefresh != null)
             {
                 var autoRefresher = Activator.CreateInstance(iautoRefresh.GenericTypeArguments[1]) as IAutoRefresher<T>;
-                if (autoRefresher != null && autoRefresher.ShouldRepost(item))
+                if (autoRefresher != null && autoRefresher.ShouldRepost(item!))
                 {
                     postable.PostedStatus = PostedStatus.RequiresRepost;
                     return true;
@@ -266,6 +303,34 @@ namespace InABox.Core
             return CreateEngine<T>().Process(model);
         }
 
+        /// <summary>
+        /// Import <typeparamref name="T"/> with the currently set <see cref="IPosterEngine{TPostable, TPoster, TSettings}"/>.
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <returns><see langword="null"/> if post was unsuccessful.</returns>
+        /// <exception cref="InvalidPullerException">If the engine is not a <see cref="IPullerEngine{TPostable,TPoster}"/></exception>
+        public static IPullResult<T>? Pull<T>()
+            where T : Entity, IPostable, IRemotable, IPersistent, new()
+        {
+            var engine = CreateEngine<T>();
+            if(engine is IPullerEngine<T> puller)
+            {
+                return puller.Pull();
+            }
+            else
+            {
+                var intDef = engine.GetType().GetInterfaceDefinition(typeof(IPosterEngine<,,>));
+                if(intDef != null)
+                {
+                    throw new InvalidPullerException(typeof(T), intDef.GenericTypeArguments[1]);
+                }
+                else
+                {
+                    throw new InvalidPullerException(typeof(T));
+                }
+            }
+        }
+
         #endregion
     }
 }

+ 1 - 1
InABox.Database/Stores/Store.cs

@@ -402,7 +402,7 @@ namespace InABox.Database
                     if (string.IsNullOrWhiteSpace(code))
                         throw new NullCodeException(typeof(T), key);
 
-                    var expr = CoreUtils.GetPropertyExpression<T, object>(key); //CoreUtils.GetMemberExpression(typeof(T),key)
+                    var expr = CoreUtils.GetPropertyExpression<T, object?>(key); //CoreUtils.GetMemberExpression(typeof(T),key)
                     codes = codes == null ? new Filter<T>(expr).IsEqualTo(code) : codes.Or(expr).IsEqualTo(code);
                     columns.Add(key);
                 }

+ 14 - 9
InABox.Poster.MYOB/MYOBPosterEngine.cs

@@ -139,20 +139,21 @@ public static partial class MYOBPosterEngine
     private static partial Regex CodeRegex();
 }
 
-public abstract class MYOBPosterEngine<TPostable, TSettings> :
-    BasePosterEngine<TPostable, IMYOBPoster<TPostable, TSettings>, TSettings>,
-    IGlobalSettingsPosterEngine<IMYOBPoster<TPostable, TSettings>, MYOBGlobalPosterSettings>
+public abstract class MYOBPosterEngine<TPostable, TPoster, TSettings> :
+    BasePosterEngine<TPostable, TPoster, TSettings>,
+    IGlobalSettingsPosterEngine<TPoster, MYOBGlobalPosterSettings>
 
     where TPostable : Entity, IPostable, IRemotable, IPersistent, new()
     where TSettings : MYOBPosterSettings, new()
+    where TPoster : class, IMYOBPoster<TPostable, TSettings>
 {
     private MYOBGlobalPosterSettings GetGlobalSettings() =>
-        (this as IGlobalSettingsPosterEngine<IMYOBPoster<TPostable, TSettings>, MYOBGlobalPosterSettings>).GetGlobalSettings();
+        (this as IGlobalSettingsPosterEngine<TPoster, MYOBGlobalPosterSettings>).GetGlobalSettings();
 
     private void SaveGlobalSettings(MYOBGlobalPosterSettings settings) =>
-        (this as IGlobalSettingsPosterEngine<IMYOBPoster<TPostable, TSettings>, MYOBGlobalPosterSettings>).SaveGlobalSettings(settings);
+        (this as IGlobalSettingsPosterEngine<TPoster, MYOBGlobalPosterSettings>).SaveGlobalSettings(settings);
 
-    protected override IMYOBPoster<TPostable, TSettings> CreatePoster()
+    protected override TPoster CreatePoster()
     {
         var poster = base.CreatePoster();
         poster.Script = GetScriptDocument();
@@ -163,8 +164,8 @@ public abstract class MYOBPosterEngine<TPostable, TSettings> :
     {
         return Poster.BeforePost(model);
     }
-    
-    protected override IPostResult<TPostable> DoProcess(IDataModel<TPostable> model)
+
+    protected void LoadConnectionData()
     {
         var data = MYOBPosterEngine.GetConnectionData();
 
@@ -231,7 +232,11 @@ public abstract class MYOBPosterEngine<TPostable, TSettings> :
             // data.ActiveCompanyFile = data.CompanyFileService.Get(companyFile, fileCredentials);
         }
         Poster.ConnectionData = data;
-
+    }
+    
+    protected override IPostResult<TPostable> DoProcess(IDataModel<TPostable> model)
+    {
+        LoadConnectionData();
         return Poster.Process(model);
     }