using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Linq.Expressions; using System.Reflection; using InABox.Clients; using InABox.Core; namespace InABox.Core { public class Credentials { public virtual string UserID { get; set; } public virtual string Password { get; set; } } public interface IEntity { Guid ID { get; set; } Guid Deleted { get; set; } bool IsChanged(); void CommitChanges(); void CancelChanges(); } public interface ITaxable { double ExTax { get; set; } double TaxRate { get; set; } double Tax { get; set; } double IncTax { get; set; } } public interface IIssues { string Issues { get; set; } } public interface IExportable { } public interface IImportable { } public interface IMergeable { } public interface ISecure { } public interface IDuplicatable { IEntityDuplicator GetDuplicator(); } public interface IEntityDuplicator { //void Duplicate(IFilter filter); void Duplicate(IEnumerable entities); } public class EntityDuplicator : IEntityDuplicator where TEntity : Entity, IRemotable, IPersistent { private interface IRelationship { Type ParentType { get; } Type ChildType { get; } IFilter GetFilter(Entity parent); } private class EntityLinkRelationship : IRelationship { public Type ParentType => typeof(TParent); public Type ChildType => typeof(TChild); public Column Column { get; set; } public IFilter GetFilter(Entity parent) { return new Filter(Column).IsEqualTo(parent.ID); } } private class GenericRelationship : IRelationship where TParent : Entity { public Type ParentType => typeof(TParent); public Type ChildType => typeof(TChild); public Column Column { get; set; } public Func Func { get; set; } public IFilter GetFilter(Entity parent) { return new Filter(Column).IsEqualTo(Func(parent as TParent)); } } private readonly List _relationships = new List(); public void Duplicate(IEnumerable entites) => Duplicate(typeof(TEntity), new Filter(x => x.ID).InList(entites.Select(x => x.ID).ToArray())); private void Duplicate(Type parent, IFilter filter) { var table = ClientFactory.CreateClient(parent) .Query(filter, Columns.Local(parent)); foreach (var row in table.Rows) { var update = (row.ToObject(parent) as Entity)!; var id = update.ID; update.ID = Guid.Empty; update.CommitChanges(); ClientFactory.CreateClient(parent).Save(update, "Duplicated Record"); foreach (var relationship in _relationships.Where(x => x.ParentType == parent)) { Duplicate(relationship.ChildType, relationship.GetFilter(update)); } } } public void AddChild(Expression> childkey) where TParent : Entity, IRemotable, IPersistent where TChild : Entity, IRemotable, IPersistent where TParentLink : IEntityLink { _relationships.Add(new EntityLinkRelationship { Column = new Column(CoreUtils.GetFullPropertyName(childkey, ".") + ".ID") }); } public void AddChild(Column linkColumn, Func value) where TParent : Entity, IRemotable, IPersistent where TChild : Entity, IRemotable, IPersistent { _relationships.Add(new GenericRelationship { Column = linkColumn, Func = value }); } void IEntityDuplicator.Duplicate(IEnumerable entities) => Duplicate(entities.Cast()); } /// /// An is required if it has the defined on it.
/// If it is part of an (or ), then it is only required /// if the property on the parent class also has . ///
public class RequiredColumnAttribute : Attribute { } public abstract class Entity : BaseObject, IEntity { private bool bTaxing; //public String Name { get; set; } [TimestampEditor(Visible = Visible.Optional, Editable = Editable.Hidden)] [RequiredColumn] public virtual DateTime LastUpdate { get; set; } = DateTime.Now; [CodeEditor(Visible = Visible.Optional, Editable = Editable.Hidden)] [RequiredColumn] public string LastUpdateBy { get; set; } = ClientFactory.UserID; [NullEditor] [RequiredColumn] public virtual DateTime Created { get; set; } = DateTime.Now; [NullEditor] [RequiredColumn] public virtual string CreatedBy { get; set; } = ClientFactory.UserID; [NullEditor] [RequiredColumn] public Guid ID { get; set; } = Guid.Empty; /// /// If the entity is deleted, holds the ID of the Deletion. Otherwise, it holds Guid.Empty /// [NullEditor] [DoNotSerialize] [Obsolete] public Guid Deleted { get; set; } = Guid.Empty; public static Type ClassVersion(Type t) { //Type t = MethodBase.GetCurrentMethod().DeclaringType; var ti = t.GetTypeInfo(); var interfaces = ti.GetInterfaces(); if (ti.GetInterfaces().Contains(typeof(IPersistent))) { if (ti.BaseType != null) throw new Exception(t.Name + " hase no Base Type"); if (ti.BaseType.Equals(typeof(Entity))) throw new Exception(t.Name + " may not derive directly from TEntity"); var props = t.GetTypeInfo().GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance); if (props.Count() > 0) throw new Exception(t.Name + "may not declare properties"); } return t.GetTypeInfo().BaseType; } //[NullEditor] //public List History { get; set; } //public Entity() : base() //{ // CommitChanges(); //} //public Entity(Guid id) : base() //{ // ID = id; // History = new List(); // UserProperties = new Dictionary(); // DataModel.InitializeEntity(this); // CheckSequence(); // CommitChanges(); //} public static bool IsEntityLinkValid(Expression> expression, CoreRow arg) where U : IEntityLink { return arg.IsEntityLinkValid(expression); } /// /// Gets the ID of an entity link of an entity, doing a validity check (see ) /// /// The entity type /// The entity link type /// An expression to the entity link of type /// The row representing the entity of type /// The ID on the entity link, or null if the entity link is invalid public static Guid? EntityLinkID(Expression> expression, CoreRow arg) where U : IEntityLink { var col = CoreUtils.GetFullPropertyName(expression, "."); var id = arg.Get(col + ".ID"); if (id != Guid.Empty && arg.Get(col + ".Deleted") == Guid.Empty) return id; return null; } protected override void SetChanged(string name, object? before, object? after) { base.SetChanged(name, before, after); CheckTax(name, before, after); } private void CheckTax(string name, object? before, object? after) { if (this is ITaxable taxable && !bTaxing) { bTaxing = true; try { if (name.Equals("ExTax")) { taxable.Tax = (double)after * (taxable.TaxRate / 100.0F); taxable.IncTax = (double)after + taxable.Tax; } else if (name.Equals("TaxRate")) { taxable.Tax = taxable.ExTax * ((double)after / 100.0F); taxable.IncTax = taxable.ExTax + taxable.Tax; } else if (name.Equals("Tax")) { taxable.ExTax = taxable.IncTax - (double)after; } else if (name.Equals("IncTax")) { taxable.ExTax = (double)after / ((100.0F + taxable.TaxRate) / 100.0F); taxable.Tax = (double)after - taxable.ExTax; } } catch (Exception e) { Logger.Send(LogType.Error, "", String.Join("\n",e.Message,e.StackTrace)); } bTaxing = false; } } protected override void DoPropertyChanged(string name, object? before, object? after) { if (!IsObserving()) return; //CheckSequence(); if (!name.Equals("LastUpdate")) LastUpdate = DateTime.Now; LastUpdateBy = ClientFactory.UserID; // This doesn;t work - keeps being updated to current date // Created => null ::Set ID = guid.empty -> now :: any other change -> unchanged! // Moved to Create(), should not simply be overwritten on deserialise from json //if (Created.Equals(DateTime.MinValue)) //{ // Created = DateTime.Now; // CreatedBy = ClientFactory.UserID; //} } } public interface ILicense where TLicenseToken : LicenseToken { } public interface IPersistent { } public interface IRemotable { } //public interface IRemoteQuery //{ //} //public interface IRemoteUpdate //{ //} //public interface IRemoteDelete //{ //} public interface ISequenceable { long Sequence { get; set; } } public interface IAutoIncrement { Expression> AutoIncrementField(); Filter? AutoIncrementFilter(); } public interface INumericAutoIncrement : IAutoIncrement { } public interface IStringAutoIncrement { string AutoIncrementPrefix(); string AutoIncrementFormat(); } public interface IStringAutoIncrement : IAutoIncrement, IStringAutoIncrement { } /// /// Used to flag an entity as exhibiting the properties of a ManyToMany relationship, allowing PRS to auto-generate things like grids and datamodels based on /// entity relationships. /// /// /// This will cause a ManyToMany grid of to appear on all editors. /// Hence, if one wishes to cause both grids to appear (that is, for to appear for and /// vice versa, one must flag the entity with both IManyToMany<, > and /// IManyToMany<, >. /// /// /// public interface IManyToMany where TLeft : Entity where TRight : Entity { } public interface IOneToMany where TOne : Entity { } public static class EntityFactory { public delegate object ObjectActivator(params object[] args); private static readonly Dictionary _cache = new Dictionary(); public static ObjectActivator GetActivator(ConstructorInfo ctor) { var type = ctor.DeclaringType; var paramsInfo = ctor.GetParameters(); //create a single param of type object[] var param = Expression.Parameter(typeof(object[]), "args"); var argsExp = new Expression[paramsInfo.Length]; //pick each arg from the params array //and create a typed expression of them for (var i = 0; i < paramsInfo.Length; i++) { Expression index = Expression.Constant(i); var paramType = paramsInfo[i].ParameterType; Expression paramAccessorExp = Expression.ArrayIndex(param, index); Expression paramCastExp = Expression.Convert(paramAccessorExp, paramType); argsExp[i] = paramCastExp; } //make a NewExpression that calls the //ctor with the args we just created var newExp = Expression.New(ctor, argsExp); //create a lambda with the New //Expression as body and our param object[] as arg var lambda = Expression.Lambda(typeof(ObjectActivator), newExp, param); //compile it var compiled = (ObjectActivator)lambda.Compile(); return compiled; } public static T CreateEntity() where T : BaseObject { if (!_cache.ContainsKey(typeof(T))) { var ctor = typeof(T).GetConstructors().First(); _cache[typeof(T)] = GetActivator(ctor); } var createdActivator = _cache[typeof(T)]; return (T)createdActivator(); } public static object CreateEntity(Type type) { if (!_cache.ContainsKey(type)) { var ctor = type.GetConstructors().First(); var activator = typeof(EntityFactory).GetMethod("GetActivator").MakeGenericMethod(type); _cache[type] = (ObjectActivator)activator.Invoke(null, new object[] { ctor }); } var createdActivator = _cache[type]; return createdActivator(); } } }