diff --git a/PortaCapena.OdooJsonRpcClient.Example/OdooClientTests.cs b/PortaCapena.OdooJsonRpcClient.Example/OdooClientTests.cs index 8951854..298a585 100644 --- a/PortaCapena.OdooJsonRpcClient.Example/OdooClientTests.cs +++ b/PortaCapena.OdooJsonRpcClient.Example/OdooClientTests.cs @@ -262,7 +262,7 @@ public async Task Can_create_update_get_and_delete_customer() products.Succeed.Should().BeTrue(); products.Value.Should().BePositive(); - model.Add(x => x.Name, "new name"); + model.AddOrUpdate(x => x.Name, "new name"); var editedCustomer = await odooClient.UpdateAsync(model, products.Value); editedCustomer.Succeed.Should().BeTrue(); @@ -392,8 +392,8 @@ public async Task Can_create_product_from_dictionary_model() var dictModel3 = OdooDictionaryModel.Create(x => x.InvoicePolicy, InvoicingPolicyProductProductOdooEnum.DeliveredQuantities); - dictModel.Add(x => x.CombinationIndices, "sadasd"); - dictModel.Add(x => x.InvoicePolicy, InvoicingPolicyProductProductOdooEnum.DeliveredQuantities); + dictModel.AddOrUpdate(x => x.CombinationIndices, "sadasd"); + dictModel.AddOrUpdate(x => x.InvoicePolicy, InvoicingPolicyProductProductOdooEnum.DeliveredQuantities); var createResult = await odooClient.CreateAsync(dictModel); createResult.Succeed.Should().BeTrue(); @@ -551,7 +551,7 @@ public async Task Can_create_purchase_order() Name = "test purchase line", }) }; - purchaseOrder.Add(x => x.OrderLine, lines); + purchaseOrder.AddOrUpdate(x => x.OrderLine, lines); var createResult = await odooClient.CreateAsync(purchaseOrder); diff --git a/PortaCapena.OdooJsonRpcClient.Example/ProductTemplateOdooModelRepositoryTests.cs b/PortaCapena.OdooJsonRpcClient.Example/ProductTemplateOdooModelRepositoryTests.cs index f90edcc..993fe36 100644 --- a/PortaCapena.OdooJsonRpcClient.Example/ProductTemplateOdooModelRepositoryTests.cs +++ b/PortaCapena.OdooJsonRpcClient.Example/ProductTemplateOdooModelRepositoryTests.cs @@ -108,7 +108,7 @@ public async Task Update_name_in_nl() result.Value.Name.Should().Be("Acoustic Bloc Screens"); result.Value.Name.Should().NotBe("Akoestische blokschermen"); - var model = OdooDictionaryModel.Create().Add(x => x.Name, "Product new name NL"); + var model = OdooDictionaryModel.Create().AddOrUpdate(x => x.Name, "Product new name NL"); repo.Config.Context.Language = "nl_BE"; diff --git a/PortaCapena.OdooJsonRpcClient.Shared/Models/POSSessionOdooModel.cs b/PortaCapena.OdooJsonRpcClient.Shared/Models/POSSessionOdooModel.cs new file mode 100644 index 0000000..bc35640 --- /dev/null +++ b/PortaCapena.OdooJsonRpcClient.Shared/Models/POSSessionOdooModel.cs @@ -0,0 +1,55 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using PortaCapena.OdooJsonRpcClient.Attributes; +using PortaCapena.OdooJsonRpcClient.Converters; +using PortaCapena.OdooJsonRpcClient.Models; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.Serialization; +using System.Text; + +namespace PortaCapena.OdooJsonRpcClient.Shared.Models +{ + [OdooTableName("pos.session")] + [JsonConverter(typeof(OdooModelConverter))] + public class POSSessionOdooModel : IOdooModel + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("config_id")] + public int ConfigId { get; set; } + + [JsonProperty("user_id")] + public int UserId { get; set; } + + [JsonProperty("state")] + public POSSessionOdooState State { get; set; } + + + [JsonProperty("start_at")] + public DateTime StartAt { get; set; } + + + } + + [JsonConverter(typeof(StringEnumConverter))] + public enum POSSessionOdooState : int + { + [EnumMember(Value = "opening_control")] + OpeningControl = 1, + [EnumMember(Value = "opened")] + Opened = 2, + [EnumMember(Value = "closing_control")] + ClosingControl = 3, + [EnumMember(Value = "closed")] + Closed = 4, + + } + + +} diff --git a/PortaCapena.OdooJsonRpcClient.Tests/OdooDictionaryModelTests.cs b/PortaCapena.OdooJsonRpcClient.Tests/OdooDictionaryModelTests.cs index 9c1ee35..00cf4d4 100644 --- a/PortaCapena.OdooJsonRpcClient.Tests/OdooDictionaryModelTests.cs +++ b/PortaCapena.OdooJsonRpcClient.Tests/OdooDictionaryModelTests.cs @@ -48,7 +48,7 @@ public void Can_create_dictionary_with_create_instance() { Name = "test name", }); - model.Add(x => x.CreateDate, new DateTime()); + model.AddOrUpdate(x => x.CreateDate, new DateTime()); model.TableName.Should().NotBeEmpty(); model.Should().NotBeEmpty(); @@ -68,7 +68,7 @@ public void Can_create_dictionary_with_call_method() { Name = TestString(), }); - model.Add(x => x.DisplayName, TestString()); + model.AddOrUpdate(x => x.DisplayName, TestString()); model.TableName.Should().NotBeEmpty(); model.Should().NotBeEmpty(); @@ -88,7 +88,7 @@ public void Can_create_dictionary_with_call_method_with_params() { Name = TestString("123"), }); - model.Add(x => x.DisplayName, TestString("456")); + model.AddOrUpdate(x => x.DisplayName, TestString("456")); model.TableName.Should().NotBeEmpty(); model.Should().NotBeEmpty(); @@ -110,7 +110,7 @@ public void Can_create_dictionary_with_call_method_with_enum_params() { Name = TestString("123"), }); - model.Add(x => x.State, StatusPurchaseOrderOdooEnum.PurchaseOrder); + model.AddOrUpdate(x => x.State, StatusPurchaseOrderOdooEnum.PurchaseOrder); model.TableName.Should().NotBeEmpty(); model.Should().NotBeEmpty(); @@ -132,7 +132,7 @@ public void Can_create_dictionary_with_array() { ActivityIds = new long[] { 1, 2, 3 } }); - model.Add(x => x.MessageFollowerIds, new long[] { 4, 5, 6 }); + model.AddOrUpdate(x => x.MessageFollowerIds, new long[] { 4, 5, 6 }); model.TableName.Should().NotBeEmpty(); model.Should().NotBeEmpty(); diff --git a/PortaCapena.OdooJsonRpcClient/Converters/OdooModelMapper.cs b/PortaCapena.OdooJsonRpcClient/Converters/OdooModelMapper.cs index 8315446..4796b28 100644 --- a/PortaCapena.OdooJsonRpcClient/Converters/OdooModelMapper.cs +++ b/PortaCapena.OdooJsonRpcClient/Converters/OdooModelMapper.cs @@ -36,6 +36,7 @@ public static bool ConverOdooPropertyToDotNet(Type dotnetType, JToken value, out return true; case JTokenType.Integer when dotnetType == typeof(int) || dotnetType == typeof(int?) || dotnetType == typeof(long) || dotnetType == typeof(long?): + case JTokenType.Integer when dotnetType == typeof(float) || dotnetType == typeof(float?) || dotnetType == typeof(double) || dotnetType == typeof(double?): case JTokenType.Float: result = value.ToObject(dotnetType); return true; diff --git a/PortaCapena.OdooJsonRpcClient/Models/IOdooMethodResult.cs b/PortaCapena.OdooJsonRpcClient/Models/IOdooMethodResult.cs new file mode 100644 index 0000000..cc3f740 --- /dev/null +++ b/PortaCapena.OdooJsonRpcClient/Models/IOdooMethodResult.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace PortaCapena.OdooJsonRpcClient.Models +{ + public interface IOdooMethodResult + { + } +} diff --git a/PortaCapena.OdooJsonRpcClient/Models/OdooDictionaryModel.cs b/PortaCapena.OdooJsonRpcClient/Models/OdooDictionaryModel.cs index 46dee49..1a0b03e 100644 --- a/PortaCapena.OdooJsonRpcClient/Models/OdooDictionaryModel.cs +++ b/PortaCapena.OdooJsonRpcClient/Models/OdooDictionaryModel.cs @@ -11,6 +11,19 @@ namespace PortaCapena.OdooJsonRpcClient.Models { public class OdooDictionaryModel : Dictionary { + + public OdooRecordsListStructure GetRecord() + { + OdooRecordsListStructure odooRecordsListStructure = new OdooRecordsListStructure(); + + foreach (var key in this.Keys) + { + + odooRecordsListStructure.Add(key, this[key]); + } + return odooRecordsListStructure; + } + public string TableName { get; internal set; } public OdooDictionaryModel() { } @@ -42,12 +55,12 @@ public static OdooDictionaryModel Create(string tableName) public static OdooDictionaryModel Create(Expression> expression, object value) where T : IOdooAtributtesModel, new() { - return new OdooDictionaryModel().Add(expression, value); + return new OdooDictionaryModel().AddOrUpdate(expression, value); } //TODO: Rename to set / addOrUpdate ? - public OdooDictionaryModel Add(Expression> expression, object value) where T : IOdooAtributtesModel + public OdooDictionaryModel AddOrUpdate(Expression> expression, object value) where T : IOdooAtributtesModel { if (TableName != null && TryGetOdooTableName(expression, out var tableName)) TableName = tableName; diff --git a/PortaCapena.OdooJsonRpcClient/Models/OdooDictionaryModelOfT.cs b/PortaCapena.OdooJsonRpcClient/Models/OdooDictionaryModelOfT.cs index 08f2a69..70279c8 100644 --- a/PortaCapena.OdooJsonRpcClient/Models/OdooDictionaryModelOfT.cs +++ b/PortaCapena.OdooJsonRpcClient/Models/OdooDictionaryModelOfT.cs @@ -18,12 +18,12 @@ public static OdooDictionaryModel Create(Expression> expression) public static OdooDictionaryModel Create(Expression> expression, object value) { - return new OdooDictionaryModel().Add(expression, value); + return new OdooDictionaryModel().AddOrUpdate(expression, value); } - public OdooDictionaryModel Add(Expression> expression, object value) + public OdooDictionaryModel AddOrUpdate(Expression> expression, object value) { - Add(expression, value); + AddOrUpdate(expression, value); return this; } diff --git a/PortaCapena.OdooJsonRpcClient/Models/OdooRecordsListStructure.cs b/PortaCapena.OdooJsonRpcClient/Models/OdooRecordsListStructure.cs new file mode 100644 index 0000000..072c7c6 --- /dev/null +++ b/PortaCapena.OdooJsonRpcClient/Models/OdooRecordsListStructure.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace PortaCapena.OdooJsonRpcClient.Models +{ + + public class OdooRecordsListStructure : Hashtable + { + private readonly ArrayList _keys = new ArrayList(); + private readonly ArrayList _values = new ArrayList(); + + public override void Add(object key, object value) + { + if (!(key is string)) + { + throw new ArgumentException("key must be a string."); + } + + base.Add(key, value); + _keys.Add(key); + _values.Add(value); + } + + public override object this[object key] + { + get => base[key]; + set + { + if (!(key is string)) + { + throw new ArgumentException("key must be a string."); + } + + base[key] = value; + _keys.Add(key); + _values.Add(value); + } + } + + public override bool Equals(object obj) + { + if (obj == null) + return false; + + if (obj.GetType() != typeof(OdooRecordsListStructure)) + return false; + var xmlRpcStruct = (OdooRecordsListStructure)obj; + if (Keys.Count != xmlRpcStruct.Count) + return false; + + foreach (string key in Keys) + { + if (!xmlRpcStruct.ContainsKey(key)) + return false; + if (!this[key].Equals(xmlRpcStruct[key])) + return false; + } + return true; + } + + public override int GetHashCode() + { + return Values.Cast().Aggregate(0, (current, obj) => current ^ obj.GetHashCode()); + } + + public override void Clear() + { + base.Clear(); + _keys.Clear(); + _values.Clear(); + } + + public new IDictionaryEnumerator GetEnumerator() + { + return new Enumerator(_keys, _values); + } + + public override ICollection Keys => _keys; + + public override void Remove(object key) + { + base.Remove(key); + var idx = _keys.IndexOf(key); + if (idx >= 0) + { + _keys.RemoveAt(idx); + _values.RemoveAt(idx); + } + } + + public override ICollection Values => _values; + + private class Enumerator : IDictionaryEnumerator + { + private readonly ArrayList _keys; + private readonly ArrayList _values; + private int _index; + + public Enumerator(ArrayList keys, ArrayList values) + { + _keys = keys; + _values = values; + _index = -1; + } + + public void Reset() + { + _index = -1; + } + + public object Current + { + get + { + CheckIndex(); + return new DictionaryEntry(_keys[_index], _values[_index]); + } + } + + public bool MoveNext() + { + _index++; + return _index < _keys.Count; + } + + public DictionaryEntry Entry + { + get + { + CheckIndex(); + return new DictionaryEntry(_keys[_index], _values[_index]); + } + } + + public object Key + { + get + { + CheckIndex(); + return _keys[_index]; + } + } + + public object Value + { + get + { + CheckIndex(); + return _values[_index]; + } + } + + private void CheckIndex() + { + if (_index < 0 || _index >= _keys.Count) + throw new InvalidOperationException( + "Enumeration has either not started or has already finished."); + } + } + } +} diff --git a/PortaCapena.OdooJsonRpcClient/OdooClient.cs b/PortaCapena.OdooJsonRpcClient/OdooClient.cs index 99ff85b..3672298 100644 --- a/PortaCapena.OdooJsonRpcClient/OdooClient.cs +++ b/PortaCapena.OdooJsonRpcClient/OdooClient.cs @@ -4,6 +4,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Net.Security; +using System.Reflection; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; @@ -344,10 +345,30 @@ public static async Task> GetVersionAsync(OdooConfig odo { var request = OdooRequestModel.Version(odooConfig); return await CallAndDeserializeAsync(request, cancellationToken); + } + + #endregion + + #region Execute Odoo Method + public async Task> ExecuteMethod(string methodName, object[] parameters, OdooContext context = null, CancellationToken cancellationToken = default) where T : IOdooModel, new() where U : IOdooMethodResult, new() + { + return await ExecuteWitrAccesDenideRetryAsync(userUid => ExecuteMethod(userUid, methodName, parameters, SelectContext(context, Config.Context), cancellationToken)); } - + public async Task> ExecuteMethod(int userUid, string methodName, object[] parameters, OdooContext context = null, CancellationToken cancellationToken = default) where T : IOdooModel, new() where U : IOdooMethodResult, new() + { + return await ExecuteMethod(Config, userUid, methodName, parameters, SelectContext(context, Config.Context), cancellationToken); + } + public static async Task> ExecuteMethod(OdooConfig odooConfig, int userUid, string methodName, object[] parameters, OdooContext context = null, CancellationToken cancellationToken = default) where T : IOdooModel, new() where U : IOdooMethodResult, new() + { + var tableName = OdooExtensions.GetOdooTableName(); + var requestParams = new OdooRequestParams(odooConfig.ApiUrlJson, "object", "execute_kw", odooConfig.DbName, userUid, odooConfig.Password, tableName, methodName, parameters); + var requestModel = new OdooRequestModel(requestParams); + return await CallAndDeserializeAsync(requestModel); + } + #endregion + private async Task> ExecuteWitrAccesDenideRetryAsync(Func>> func, CancellationToken cancellationToken = default) { var userUid = await GetCurrentUserUidOrLoginAsync(cancellationToken); @@ -403,6 +424,10 @@ private static string GetTableName(OdooDictionaryModel model, string tableName = private OdooContext SelectContext(OdooContext paramContext, OdooContext mainContext) { return paramContext ?? mainContext; - } + } + + + + } }