diff --git a/source/XeroApi/AttachmentRepository.cs b/source/XeroApi/AttachmentRepository.cs index 089e2a9..04336a6 100644 --- a/source/XeroApi/AttachmentRepository.cs +++ b/source/XeroApi/AttachmentRepository.cs @@ -2,94 +2,114 @@ using System.Linq; using XeroApi.Integration; using XeroApi.Model; -using XeroApi.Model.Serialize; + namespace XeroApi { public class AttachmentRepository { private readonly IIntegrationProxy _integrationProxy; - private readonly IModelSerializer _serializer; + /// /// Initializes a new instance of the class. /// /// The integration proxy. - /// - internal AttachmentRepository(IIntegrationProxy integrationProxy, IModelSerializer serializer) + internal AttachmentRepository(IIntegrationProxy integrationProxy) { _integrationProxy = integrationProxy; - _serializer = serializer; } + + + // POST + public Attachment UpdateOrCreate(TModel model, FileInfo fileInfo) where TModel : ModelBase, IAttachmentParent { return UpdateOrCreate(model, new Attachment(fileInfo)); } + public Attachment UpdateOrCreate(TModel model, Attachment attachment) where TModel : ModelBase, IAttachmentParent { - string data = _integrationProxy.UpdateOrCreateAttachment( + string xml = _integrationProxy.UpdateOrCreateAttachment( typeof(TModel).Name, ModelTypeHelper.GetModelItemId(model), attachment); - var response = _serializer.DeserializeTo(data); + + Response response = ModelSerializer.DeserializeTo(xml); + return response.Attachments.First(); } + + // PUT + public Attachment Create(TModel model, FileInfo fileInfo) where TModel : ModelBase, IAttachmentParent { return Create(model, new Attachment(fileInfo)); } + public Attachment Create(TModel model, Attachment attachment) where TModel : ModelBase, IAttachmentParent { - string data = _integrationProxy.CreateAttachment( + string xml = _integrationProxy.CreateAttachment( typeof(TModel).Name, ModelTypeHelper.GetModelItemId(model), attachment); - return _serializer.DeserializeTo(data).Attachments.First(); + + return ModelSerializer.DeserializeTo(xml).Attachments.First(); } + + // GET (one) + public Attachment GetAttachmentFor(TModel model) where TModel : ModelBase, IAttachmentParent { // List the attachments against this model. var modelItemId = ModelTypeHelper.GetModelItemId(model); + var allAttachmentsXml = _integrationProxy.FindAttachments(typeof(TModel).Name, modelItemId); - var allAttachments = _serializer.DeserializeTo(allAttachmentsXml).Attachments; + + var allAttachments = ModelSerializer.DeserializeTo(allAttachmentsXml).Attachments; + if (allAttachments == null || allAttachments.Count == 0) { return null; } + var theFirstAttachment = allAttachments.First(); + // Get the attachment content var content = _integrationProxy.FindOneAttachment( - typeof (TModel).Name, + typeof(TModel).Name, modelItemId, - theFirstAttachment.AttachmentID.ToString()); + theFirstAttachment.FileName); + return theFirstAttachment.WithContent(content); } } + } diff --git a/source/XeroApi/CoreRepository.cs b/source/XeroApi/CoreRepository.cs index 9fd9773..715595b 100644 --- a/source/XeroApi/CoreRepository.cs +++ b/source/XeroApi/CoreRepository.cs @@ -118,7 +118,7 @@ public IQueryable ExpenseClaims public AttachmentRepository Attachments { - get { return new AttachmentRepository(Proxy, Serializer); } + get { return new AttachmentRepository(Proxy); } } public ReportRepository Reports diff --git a/source/XeroApi/Linq/ApiQueryTranslator.cs b/source/XeroApi/Linq/ApiQueryTranslator.cs index af9775c..82217d3 100644 --- a/source/XeroApi/Linq/ApiQueryTranslator.cs +++ b/source/XeroApi/Linq/ApiQueryTranslator.cs @@ -1,99 +1,99 @@ using System; using System.Collections; -using System.Linq; -using System.Linq.Expressions; - -namespace XeroApi.Linq -{ - /// - /// Translates a linq query into a . - /// - internal class ApiQueryTranslator : ExpressionVisitor - { - private LinqQueryDescription _query; - private ApiQuerystringName _currentQueryStringName = ApiQuerystringName.Unknown; - - - /// - /// Translates the specified linq expression into a . - /// - /// The linq expression. - /// - internal LinqQueryDescription Translate(Expression expression) - { - _query = new LinqQueryDescription(); - - if (expression.Type.IsGenericType) - { - _query.ElementType = expression.Type.GetGenericArguments()[0]; - } - - Visit(expression); - - return _query; - } - - protected override Expression VisitMethodCall(MethodCallExpression m) - { - switch (m.Method.Name) - { - case "Where": - - using (new QuerystringScope(this, ApiQuerystringName.Where)) - { - return base.VisitMethodCall(m); - } - - case "FirstOrDefault": - case "First": - case "SingleOrDefault": - case "Single": - - if (!string.IsNullOrEmpty(_query.ClientSideExpression) && !_query.ClientSideExpression.Equals(m.Method.Name)) - { - throw new NotImplementedException("Only 1 aggregator expression can currently be performed."); - } - - _query.ClientSideExpression = m.Method.Name; - - using (new QuerystringScope(this, ApiQuerystringName.Where)) - { - return base.VisitMethodCall(m); - } - - case "Count": - - if (!string.IsNullOrEmpty(_query.ClientSideExpression) && !_query.ClientSideExpression.Equals(m.Method.Name)) - { - throw new NotImplementedException("Only 1 aggregator expression can currently be performed."); - } - - _query.ClientSideExpression = m.Method.Name; - return base.VisitMethodCall(m); - - case "OrderBy": - case "ThenBy": - - using (new QuerystringScope(this, ApiQuerystringName.OrderBy)) - { - return base.VisitMethodCall(m); - } - - case "OrderByDescending": - case "ThenByDescending": - - using (new QuerystringScope(this, ApiQuerystringName.OrderBy)) - { - var expression = base.VisitMethodCall(m); - Append(" DESC"); - return expression; - } - - case "Skip": - using (new QuerystringScope(this, ApiQuerystringName.Skip)) - { - return base.VisitMethodCall(m); - } +using System.Linq; +using System.Linq.Expressions; + +namespace XeroApi.Linq +{ + /// + /// Translates a linq query into a . + /// + internal class ApiQueryTranslator : ExpressionVisitor + { + private LinqQueryDescription _query; + private ApiQuerystringName _currentQueryStringName = ApiQuerystringName.Unknown; + + + /// + /// Translates the specified linq expression into a . + /// + /// The linq expression. + /// + internal LinqQueryDescription Translate(Expression expression) + { + _query = new LinqQueryDescription(); + + if (expression.Type.IsGenericType) + { + _query.ElementType = expression.Type.GetGenericArguments()[0]; + } + + Visit(expression); + + return _query; + } + + protected override Expression VisitMethodCall(MethodCallExpression m) + { + switch (m.Method.Name) + { + case "Where": + + using (new QuerystringScope(this, ApiQuerystringName.Where)) + { + return base.VisitMethodCall(m); + } + + case "FirstOrDefault": + case "First": + case "SingleOrDefault": + case "Single": + + if (!string.IsNullOrEmpty(_query.ClientSideExpression) && !_query.ClientSideExpression.Equals(m.Method.Name)) + { + throw new NotImplementedException("Only 1 aggregator expression can currently be performed."); + } + + _query.ClientSideExpression = m.Method.Name; + + using (new QuerystringScope(this, ApiQuerystringName.Where)) + { + return base.VisitMethodCall(m); + } + + case "Count": + + if (!string.IsNullOrEmpty(_query.ClientSideExpression) && !_query.ClientSideExpression.Equals(m.Method.Name)) + { + throw new NotImplementedException("Only 1 aggregator expression can currently be performed."); + } + + _query.ClientSideExpression = m.Method.Name; + return base.VisitMethodCall(m); + + case "OrderBy": + case "ThenBy": + + using (new QuerystringScope(this, ApiQuerystringName.OrderBy)) + { + return base.VisitMethodCall(m); + } + + case "OrderByDescending": + case "ThenByDescending": + + using (new QuerystringScope(this, ApiQuerystringName.OrderBy)) + { + var expression = base.VisitMethodCall(m); + Append(" DESC"); + return expression; + } + + case "Skip": + using (new QuerystringScope(this, ApiQuerystringName.Skip)) + { + return base.VisitMethodCall(m); + } } var rootExpressionType = FindRootExpressionType(m); @@ -107,8 +107,8 @@ protected override Expression VisitMethodCall(MethodCallExpression m) case ExpressionType.Parameter: Append(ParseExpression(m)); return m; - } - + } + // If this is a method from a clr object, as opposed to an extension method, the API server just might be able to support it. if (m.Method.DeclaringType == typeof(string) || m.Method.DeclaringType == typeof(DateTime) || @@ -116,50 +116,50 @@ protected override Expression VisitMethodCall(MethodCallExpression m) { return VisitObjectMethodCall(m); } - - throw new NotImplementedException(string.Format("The method '{0}' can't currently be used in a XeroApi WHERE querystring.", m.Method.Name)); - } - - - protected Expression VisitObjectMethodCall(MethodCallExpression m) - { - // The .Contains and .StartsWith methods on string objects are supported by the API server - // e.g. - // c => c.Name.StartsWith("Jason") - // c => c.Name.Contains("ase") - - if (m.Method.IsStatic && m.Method.DeclaringType != null) - { - Append(m.Method.DeclaringType.Name); - } - - Expression obj = Visit(m.Object); - - Append("."); - Append(m.Method.Name); - Append("("); - var args = VisitExpressionList(m.Arguments); - Append(")"); - - return UpdateMethodCall(m, obj, m.Method, args); - } - - - protected override Expression VisitUnary(UnaryExpression u) - { - switch (u.NodeType) - { - case ExpressionType.Not: - Append("("); - Visit(u.Operand); - Append(" == false)"); - break; - default: - Visit(u.Operand); - break; - } - - return u; + + throw new NotImplementedException(string.Format("The method '{0}' can't currently be used in a XeroApi WHERE querystring.", m.Method.Name)); + } + + + protected Expression VisitObjectMethodCall(MethodCallExpression m) + { + // The .Contains and .StartsWith methods on string objects are supported by the API server + // e.g. + // c => c.Name.StartsWith("Jason") + // c => c.Name.Contains("ase") + + if (m.Method.IsStatic && m.Method.DeclaringType != null) + { + Append(m.Method.DeclaringType.Name); + } + + Expression obj = Visit(m.Object); + + Append("."); + Append(m.Method.Name); + Append("("); + var args = VisitExpressionList(m.Arguments); + Append(")"); + + return UpdateMethodCall(m, obj, m.Method, args); + } + + + protected override Expression VisitUnary(UnaryExpression u) + { + switch (u.NodeType) + { + case ExpressionType.Not: + Append("("); + Visit(u.Operand); + Append(" == false)"); + break; + default: + Visit(u.Operand); + break; + } + + return u; } @@ -178,75 +178,75 @@ protected override Expression VisitLambda(LambdaExpression lambda) } return base.VisitLambda(lambda); - } - - - /// - /// Determines whether the expression is to be rendered into the WHERE or ORDER clause - /// - /// The expression. - /// - /// true if [is not rendered into expression] [the specified expression]; otherwise, false. - /// - private bool IsNotRenderedIntoExpression(Expression expression) - { - var binaryExpression = expression as BinaryExpression; - - if (binaryExpression == null) - return false; - - var mExp = binaryExpression.Left as MemberExpression; - - // Check if the LHS is an ItemId, ItemNumber or UpdatedDate. If so, record away from the main where clause. - if (mExp != null && mExp.Member.DeclaringType != null && mExp.Member.DeclaringType.Name == _query.ElementName) - { - if (mExp.Member.Name == _query.ElementIdProperty.SafeName() - || mExp.Member.Name == _query.ElementNumberProperty.SafeName() - || mExp.Member.Name == _query.ElementUpdatedDateProperty.SafeName()) - { - return true; - } - } - - return false; - } - - - protected override Expression VisitBinary(BinaryExpression b) - { - var mExp = b.Left as MemberExpression; - - // Check if the LHS is an ItemId, ItemNumber or UpdatedDate. If so, record away from the main where clause. - if (mExp != null && mExp.Member.DeclaringType != null && mExp.Member.DeclaringType.Name == _query.ElementName) - { - if (mExp.Member.Name == _query.ElementIdProperty.SafeName()) - { - if (b.Right.Type == typeof (Guid?)) - _query.ElementId = EvaluateExpression(b.Right).ToString(); - else if (b.Right.Type == typeof (Guid)) - _query.ElementId = EvaluateExpression(b.Right).ToString(); - return b; - } - if (mExp.Member.Name == _query.ElementNumberProperty.SafeName()) - { - var rightValue = EvaluateExpression(b.Right); - - if (rightValue != null) - { - _query.ElementId = rightValue.ToString(); - return b; - } - } - if (mExp.Member.Name == _query.ElementUpdatedDateProperty.SafeName()) - { - if (b.Right.Type == typeof (DateTime?)) - _query.UpdatedSinceDate = EvaluateExpression(b.Right); - else if (b.Right.Type == typeof (DateTime)) - _query.UpdatedSinceDate = EvaluateExpression(b.Right); - return b; - } - } - + } + + + /// + /// Determines whether the expression is to be rendered into the WHERE or ORDER clause + /// + /// The expression. + /// + /// true if [is not rendered into expression] [the specified expression]; otherwise, false. + /// + private bool IsNotRenderedIntoExpression(Expression expression) + { + var binaryExpression = expression as BinaryExpression; + + if (binaryExpression == null) + return false; + + var mExp = binaryExpression.Left as MemberExpression; + + // Check if the LHS is an ItemId, ItemNumber or UpdatedDate. If so, record away from the main where clause. + if (mExp != null && mExp.Member.DeclaringType != null && mExp.Member.DeclaringType.Name == _query.ElementName) + { + if (mExp.Member.Name == _query.ElementIdProperty.SafeName() + || mExp.Member.Name == _query.ElementNumberProperty.SafeName() + || mExp.Member.Name == _query.ElementUpdatedDateProperty.SafeName()) + { + return true; + } + } + + return false; + } + + + protected override Expression VisitBinary(BinaryExpression b) + { + var mExp = b.Left as MemberExpression; + + // Check if the LHS is an ItemId, ItemNumber or UpdatedDate. If so, record away from the main where clause. + if (mExp != null && mExp.Member.DeclaringType != null && mExp.Member.DeclaringType.Name == _query.ElementName) + { + if (mExp.Member.Name == _query.ElementIdProperty.SafeName()) + { + if (b.Right.Type == typeof (Guid?)) + _query.ElementId = EvaluateExpression(b.Right).ToString(); + else if (b.Right.Type == typeof (Guid)) + _query.ElementId = EvaluateExpression(b.Right).ToString(); + return b; + } + if (mExp.Member.Name == _query.ElementNumberProperty.SafeName()) + { + var rightValue = EvaluateExpression(b.Right); + + if (rightValue != null) + { + _query.ElementId = rightValue.ToString(); + return b; + } + } + if (mExp.Member.Name == _query.ElementUpdatedDateProperty.SafeName()) + { + if (b.Right.Type == typeof (DateTime?)) + _query.UpdatedSinceDate = EvaluateExpression(b.Right); + else if (b.Right.Type == typeof (DateTime)) + _query.UpdatedSinceDate = EvaluateExpression(b.Right); + return b; + } + } + if (b.Left.NodeType == ExpressionType.Convert) { var expression = b.Left as UnaryExpression; @@ -275,47 +275,47 @@ protected override Expression VisitBinary(BinaryExpression b) } } } - - // http://answers.xero.com/developer/question/39411/ - // Check for rogue VB methods that have been slipped into the linq expression.. - var leftMethod = b.Left as MethodCallExpression; - - if (leftMethod != null && leftMethod.Method.Name == "CompareString") - { - var memberExpression = leftMethod.Arguments[0]; - var valueExpression = leftMethod.Arguments[1]; - - if (b.NodeType == ExpressionType.NotEqual) - return Visit(Expression.NotEqual(memberExpression, valueExpression)); - - return Visit(Expression.Equal(memberExpression, valueExpression)); - } - - // Check if either the left or right hand side of the expression is a ItemId, ItemNumber or ElementUpdatedDate - // binary expression. If so, visit the left and right hand sides separately. - if (IsNotRenderedIntoExpression(b.Left)) - { - VisitBinary((BinaryExpression)b.Left); - Visit(b.Right); - return b.Right; - } - if (IsNotRenderedIntoExpression(b.Right)) - { - Visit(b.Left); - VisitBinary((BinaryExpression)b.Right); - return b.Left; - } - - // Parse as a normal binary expression (operand1 operator operand2) - Append("("); - Visit(b.Left); - - AppendOperator(b); - - Visit(b.Right); - Append(")"); - - return b; + + // http://answers.xero.com/developer/question/39411/ + // Check for rogue VB methods that have been slipped into the linq expression.. + var leftMethod = b.Left as MethodCallExpression; + + if (leftMethod != null && leftMethod.Method.Name == "CompareString") + { + var memberExpression = leftMethod.Arguments[0]; + var valueExpression = leftMethod.Arguments[1]; + + if (b.NodeType == ExpressionType.NotEqual) + return Visit(Expression.NotEqual(memberExpression, valueExpression)); + + return Visit(Expression.Equal(memberExpression, valueExpression)); + } + + // Check if either the left or right hand side of the expression is a ItemId, ItemNumber or ElementUpdatedDate + // binary expression. If so, visit the left and right hand sides separately. + if (IsNotRenderedIntoExpression(b.Left)) + { + VisitBinary((BinaryExpression)b.Left); + Visit(b.Right); + return b.Right; + } + if (IsNotRenderedIntoExpression(b.Right)) + { + Visit(b.Left); + VisitBinary((BinaryExpression)b.Right); + return b.Left; + } + + // Parse as a normal binary expression (operand1 operator operand2) + Append("("); + Visit(b.Left); + + AppendOperator(b); + + Visit(b.Right); + Append(")"); + + return b; } private void AppendOperator(BinaryExpression b) @@ -353,18 +353,18 @@ private void AppendOperator(BinaryExpression b) } } - protected override Expression VisitConstant(ConstantExpression c) - { - var q = c.Value as IQueryable; - - if (q != null) - { - _query.ElementType = q.ElementType; - } - else if (c.Value == null) - { - Append("NULL"); - } + protected override Expression VisitConstant(ConstantExpression c) + { + var q = c.Value as IQueryable; + + if (q != null) + { + _query.ElementType = q.ElementType; + } + else if (c.Value == null) + { + Append("NULL"); + } else if (c.Value.GetType().IsEnum) { Append(c.Value.GetType().Name + "." + c.Value); @@ -397,17 +397,17 @@ protected override Expression VisitConstant(ConstantExpression c) } } - return c; - } - - protected override NewExpression VisitNew(NewExpression nex) + return c; + } + + protected override NewExpression VisitNew(NewExpression nex) + { + Append(EvaluateToLiteral(nex)); + return nex; + } + + protected override Expression VisitMemberAccess(MemberExpression m) { - Append(EvaluateToLiteral(nex)); - return nex; - } - - protected override Expression VisitMemberAccess(MemberExpression m) - { if (m.Expression == null) { throw new NotSupportedException("The MemberExpression.Expression property is null"); @@ -423,17 +423,17 @@ protected override Expression VisitMemberAccess(MemberExpression m) if (rootExpressionType == ExpressionType.Parameter) { - Append(ParseExpression(m)); - return m; + Append(ParseExpression(m)); + return m; } - + if (m.Expression.NodeType == ExpressionType.MemberAccess) { Append(ParseExpression(m)); - return m; - } - - throw new NotSupportedException(string.Format("The member '{0}' of type {1} is not supported", m.Expression, m.Expression.NodeType)); + return m; + } + + throw new NotSupportedException(string.Format("The member '{0}' of type {1} is not supported", m.Expression, m.Expression.NodeType)); } private static ExpressionType FindRootExpressionType(Expression m) @@ -510,7 +510,7 @@ private string ParseExpression(Expression expression) throw new NotSupportedException(string.Format("Expression type {0} was not expected in this scenario", expression.NodeType)); } } - + private string EvaluateToLiteral(Expression exp) { switch (exp.Type.Name) @@ -545,48 +545,51 @@ private string EvaluateToLiteral(Expression exp) case "Int64": var longValue = EvaluateExpression(exp); - return string.Format("\"{0}\"", longValue); + return string.Format("\"{0}\"", longValue); + case "Boolean": + var boolValue = EvaluateExpression(exp); + return string.Format("{0}", boolValue); } throw new NotSupportedException(string.Format("The Expression return type '{0}' is not supported", exp.Type.Name)); - } - - private static T EvaluateExpression(Expression expression) - { - Expression> lambda = Expression.Lambda>(expression); - Func func = lambda.Compile(); - return (func).Invoke(); - } - - private void Append(string term) - { - _query.AppendTerm(term, _currentQueryStringName); - } - + } + + private static T EvaluateExpression(Expression expression) + { + Expression> lambda = Expression.Lambda>(expression); + Func func = lambda.Compile(); + return (func).Invoke(); + } + + private void Append(string term) + { + _query.AppendTerm(term, _currentQueryStringName); + } + private static string ApplyDotNotation(params string[] input) { return input.Where(it => !string.IsNullOrEmpty(it)).Aggregate((s1, s2) => string.Concat(s1, ".", s2)); - } - - - private class QuerystringScope : IDisposable - { - private readonly ApiQuerystringName _originalQuerystringName; - private readonly ApiQueryTranslator _queryTranslator; - - public QuerystringScope(ApiQueryTranslator queryTranslator, ApiQuerystringName querystringName) - { - _originalQuerystringName = queryTranslator._currentQueryStringName; - _queryTranslator = queryTranslator; - - _queryTranslator._currentQueryStringName = querystringName; - } - - public void Dispose() - { - _queryTranslator._currentQueryStringName = _originalQuerystringName; - } - } - - } -} + } + + + private class QuerystringScope : IDisposable + { + private readonly ApiQuerystringName _originalQuerystringName; + private readonly ApiQueryTranslator _queryTranslator; + + public QuerystringScope(ApiQueryTranslator queryTranslator, ApiQuerystringName querystringName) + { + _originalQuerystringName = queryTranslator._currentQueryStringName; + _queryTranslator = queryTranslator; + + _queryTranslator._currentQueryStringName = querystringName; + } + + public void Dispose() + { + _queryTranslator._currentQueryStringName = _originalQuerystringName; + } + } + + } +} diff --git a/source/XeroApi/Linq/LinqQueryDescription.cs b/source/XeroApi/Linq/LinqQueryDescription.cs index af8de3d..51d13a9 100644 --- a/source/XeroApi/Linq/LinqQueryDescription.cs +++ b/source/XeroApi/Linq/LinqQueryDescription.cs @@ -140,6 +140,11 @@ public string Offset get { return _skipQuery.ToString(); } } + public string Page + { + get { return _skipQuery.ToString(); } + } + /// /// Gets the query string parameter collection. /// @@ -160,6 +165,9 @@ public NameValueCollection QueryStringParams if (!string.IsNullOrEmpty(Offset)) collectionToReturn.Add("offset", Offset); + if (!string.IsNullOrEmpty(Page)) + collectionToReturn.Add("Page", Page); + return collectionToReturn; } } @@ -184,6 +192,9 @@ public override string ToString() if (!string.IsNullOrEmpty(Offset)) sb.Append("Offset:" + Offset + " "); + if (!string.IsNullOrEmpty(Page)) + sb.Append("Page:" + Page + " "); + if (UpdatedSinceDate.HasValue) sb.Append("After:" + UpdatedSinceDate.Value.ToString("yyyy-MM-ddTHH:mm:ss") + " "); diff --git a/source/XeroApi/Model/Contact.cs b/source/XeroApi/Model/Contact.cs index 190cb3b..4d1d531 100644 --- a/source/XeroApi/Model/Contact.cs +++ b/source/XeroApi/Model/Contact.cs @@ -44,15 +44,42 @@ public class Contact : EndpointModelBase [ReadOnly] public bool IsCustomer { get; set; } - + + [ReadOnly] public string DefaultCurrency { get; set; } + [ReadOnly] + public BatchPayments BatchPayments { get; set; } + + [ReadOnly] + public Balances Balances { get; set; } + public override string ToString() { return string.Format("Contact:{0}", Name); } } + public class BatchPayments : ModelBase + { + public string BankAccountNumber { get; set; } + public string BankAccountName { get; set; } + public string Details { get; set; } + } + + public class Balances : ModelBase + { + public Balance AccountsReceivable { get; set; } + public Balance AccountsPayable { get; set; } + } + + public class Balance : ModelBase + { + public decimal Outstanding { get; set; } + public decimal Overdue { get; set; } + } + + public class Contacts : ModelList { } diff --git a/source/XeroApi/Model/Invoice.cs b/source/XeroApi/Model/Invoice.cs index 3eaf54b..5432dc7 100644 --- a/source/XeroApi/Model/Invoice.cs +++ b/source/XeroApi/Model/Invoice.cs @@ -40,6 +40,9 @@ public class Invoice : EndpointModelBase, IAttachmentParent [ReadOnly] public bool? SentToContact { get; set; } + [ReadOnly] + public bool? HasAttachments { get; set; } + public decimal? CurrencyRate { get; set; } public Contact Contact { get; set; } diff --git a/source/XeroApi/Model/ModelSerializer.cs b/source/XeroApi/Model/ModelSerializer.cs new file mode 100644 index 0000000..23d487f --- /dev/null +++ b/source/XeroApi/Model/ModelSerializer.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using System.Xml.Linq; +using System.Xml.Serialization; + +namespace XeroApi.Model +{ + public class ModelSerializer + { + [Obsolete("This uses an incorrect serializer implemetation", true)] + internal static IModelList Deserialize(string xml, Type modelListType) + where TModel : ModelBase + { + if (string.IsNullOrEmpty(xml)) + { + return null; + } + + var serializer = new System.Runtime.Serialization.DataContractSerializer(modelListType); + + using (TextReader tr = new StringReader(xml)) + using (XmlReader xr = new XmlTextReader(tr)) + { + return (IModelList)serializer.ReadObject(xr); + } + } + + internal static T DeserializeTo(string xml) + where T : class + { + if (string.IsNullOrEmpty(xml)) + { + return null; + } + + var serializer = new XmlSerializer(typeof(T)); + + using (TextReader tr = new StringReader(xml)) + using (XmlReader xr = new XmlTextReader(tr)) + { + return (T)serializer.Deserialize(xr); + } + } + + public static string Serialize(ICollection itemsToSerialise) + where TModel : ModelBase + { + // Specify the namespaces to be used for the serializer - rather than using the default ones. + XmlSerializerNamespaces xmlnsEmpty = new XmlSerializerNamespaces(); + xmlnsEmpty.Add("", ""); + + var serializer = new XmlSerializer(itemsToSerialise.GetType()); + + StringBuilder sb = new StringBuilder(); + + using (StringWriter sw = new StringWriter(sb)) + { + serializer.Serialize(sw, itemsToSerialise); + sw.Flush(); + } + + return CleanXml(sb.ToString()); + } + + public static string Serialize(TModel itemToSerialise) + where TModel : ModelBase + { + // Specify the namespaces to be used for the serializer - rather than using the default ones. + XmlSerializerNamespaces xmlnsEmpty = new XmlSerializerNamespaces(); + xmlnsEmpty.Add("", ""); + + var serializer = new XmlSerializer(typeof(TModel)); + + StringBuilder sb = new StringBuilder(); + + using (StringWriter sw = new StringWriter(sb)) + { + serializer.Serialize(sw, itemToSerialise, xmlnsEmpty); + sw.Flush(); + } + + return CleanXml(sb.ToString()); + } + + public static string Serialize2(TModel itemsToSerialise) + where TModel : ModelBase + { + StringBuilder sb = new StringBuilder(); + + XmlWriterSettings xmlWriterSettings = new XmlWriterSettings + { + OmitXmlDeclaration = true, + Indent = true, + }; + + using (StringWriter sw = new StringWriter(sb)) + using (XmlWriter xs = XmlWriter.Create(sw, xmlWriterSettings)) + using (ModelTreeNavigator navigator = new ModelTreeNavigator(xs)) + { + navigator.Navigate(itemsToSerialise); + + xs.Flush(); + sw.Flush(); + } + + return sb.ToString(); + } + + private static string CleanXml(string xml) + { + XElement xElement = XElement.Parse(xml); + + xElement.Descendants().Where(el => el.Name == "ValidationErrors" || el.Name == "Warnings").Remove(); + xElement.Descendants().Where(el => el.Attributes().Any(attribute => attribute.Name.LocalName == "nil")).Remove(); + + return xElement.ToString(); + } + } +} + diff --git a/source/XeroApi/Model/Payroll/Enums/RateType.cs b/source/XeroApi/Model/Payroll/Enums/RateType.cs index f3ecb43..cb3cae3 100644 --- a/source/XeroApi/Model/Payroll/Enums/RateType.cs +++ b/source/XeroApi/Model/Payroll/Enums/RateType.cs @@ -2,7 +2,7 @@ namespace XeroApi.Model.Payroll.Enums { public enum RateType { - FIXED, + FIXEDAMOUNT, MULTIPLE, RATEPERUNIT } diff --git a/source/XeroApi/Model/Payroll/Timesheet.cs b/source/XeroApi/Model/Payroll/Timesheet.cs index c4f2629..149c641 100644 --- a/source/XeroApi/Model/Payroll/Timesheet.cs +++ b/source/XeroApi/Model/Payroll/Timesheet.cs @@ -1,4 +1,5 @@ using System; +using System.Xml.Serialization; namespace XeroApi.Model.Payroll { @@ -13,6 +14,7 @@ public class Timesheet : EndpointModelBase public TimesheetLines TimesheetLines { get; set; } } + [XmlType("Timesheets")] public class Timesheets : ModelList { } diff --git a/source/XeroApi/Model/Payroll/TimesheetLine.cs b/source/XeroApi/Model/Payroll/TimesheetLine.cs index 495a757..0c4a87a 100644 --- a/source/XeroApi/Model/Payroll/TimesheetLine.cs +++ b/source/XeroApi/Model/Payroll/TimesheetLine.cs @@ -6,18 +6,13 @@ public class TimesheetLine : HasUpdatedDate { public Guid EarningsRateID { get; set; } public Guid TrackingItemID { get; set; } - public NumberOfUnits NumberOfUnits { get; set; } + + [System.Xml.Serialization.XmlArrayItem("NumberOfUnit")] + public decimal[] NumberOfUnits { get; set; } } public class TimesheetLines : ModelList { } - public class NumberOfUnits : ModelList - { - } - - public class NumberOfUnit : EndpointModelBase - { - } } \ No newline at end of file diff --git a/source/XeroApi/XeroApi.csproj b/source/XeroApi/XeroApi.csproj index 8494c16..84ddd94 100644 --- a/source/XeroApi/XeroApi.csproj +++ b/source/XeroApi/XeroApi.csproj @@ -49,6 +49,7 @@ +