├── RemoveBinObjs.bat ├── PortaCapena.OdooJsonRpcClient.Tests ├── PortaCapena.OdooJsonRpcClient.Tests.v3.ncrunchproject ├── OdooConfigTests.cs ├── PortaCapena.OdooJsonRpcClient.Tests.csproj ├── OdooModelMapperTests.cs ├── OdooContextTests.cs ├── OdooRequestModelTests.cs ├── OdooDictionaryModelTests.cs └── OdooQueryOfTTests.cs ├── PortaCapena.OdooJsonRpcClient ├── ic_project.png ├── Models │ ├── IOdooAtributtesModel.cs │ ├── IOdooCreateModel.cs │ ├── IOdooModel.cs │ ├── OdooVersion.cs │ ├── OdooDictionaryModelOfT.cs │ ├── OdooConfig.cs │ ├── OdooContext.cs │ ├── OdooPropertyInfo.cs │ └── OdooDictionaryModel.cs ├── PortaCapena.OdooJsonRpcClient.v3.ncrunchproject ├── Consts │ ├── OdooExceptionName.cs │ ├── OdooConsts.cs │ ├── OdooOperation.cs │ └── OdooOperators.cs ├── Result │ ├── OdooException.cs │ ├── OdooError.cs │ └── OdooResult.cs ├── Attributes │ └── OdooTableNameAttribute.cs ├── Request │ ├── OdooQuery.cs │ ├── OdooRequestParams.cs │ ├── OdooFilterOfT.cs │ ├── OdooFilter.cs │ ├── OdooRequestModel.cs │ ├── OdooQueryBuilder.cs │ └── OdooQueryOfT.cs ├── Extensions │ ├── EnumExtension.cs │ ├── OdooAtributtesModelExtensions.cs │ └── OdooExtensions.cs ├── IOdooRepository.cs ├── PortaCapena.OdooJsonRpcClient.csproj ├── Utils │ └── OdooFilterMapper.cs ├── Converters │ ├── OdooModelConverter.cs │ └── OdooExpresionMapper.cs └── OdooRepository.cs ├── PortaCapena.OdooJsonRpcClient.v3.ncrunchsolution ├── PortaCapena.OdooJsonRpcClient.Shared ├── PortaCapena.OdooJsonRpcClient.Shared.csproj ├── RequestTestBase.cs └── Models │ ├── Create │ ├── OdooCreateProduct.cs │ └── OdooVoucherCreateOrUpdate.cs │ ├── AccountPaymentTermOdooModel.cs │ ├── ProductPricelistOdooDto.cs │ ├── ResCurrencyOdooModel.cs │ ├── ResPartnerBankOdooModel.cs │ ├── AccountAccountTypeOdooModel .cs │ ├── ResCountryOdooModel .cs │ ├── StockPickingTypeOdooModel.cs │ ├── CouponProgramOdooDto.cs │ ├── StockProductionLotOdooDto.cs │ ├── AccountTaxOdooModel.cs │ ├── AccountAccountOdooModel .cs │ ├── PurchaseOrderLineOdooModel.cs │ ├── SaleOrderLineOdooDto.cs │ └── PurchaseOrderOdooModel.cs ├── .dockerignore ├── LICENSE.md ├── .github └── workflows │ ├── code-coverage-badge.yml │ ├── release.yml │ ├── codeql-analysis.yml │ └── pr_build.yml ├── PortaCapena.OdooJsonRpcClient.Example ├── PortaCapena.OdooJsonRpcClient.Example.csproj ├── CompanyOdooRepositoryTests.cs └── ProductTemplateOdooModelRepositoryTests.cs ├── PortaCapena.OdooJsonRpcClient.sln └── .gitignore /RemoveBinObjs.bat: -------------------------------------------------------------------------------- 1 | for /d /r . %%d in (bin,obj) do @if exist "%%d" rd /s/q "%%d" -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Tests/PortaCapena.OdooJsonRpcClient.Tests.v3.ncrunchproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/ic_project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Intechnity-com/OdooJsonRpcClient/HEAD/PortaCapena.OdooJsonRpcClient/ic_project.png -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Models/IOdooAtributtesModel.cs: -------------------------------------------------------------------------------- 1 | namespace PortaCapena.OdooJsonRpcClient.Models 2 | { 3 | public interface IOdooAtributtesModel 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Models/IOdooCreateModel.cs: -------------------------------------------------------------------------------- 1 | namespace PortaCapena.OdooJsonRpcClient.Models 2 | { 3 | public interface IOdooCreateModel : IOdooAtributtesModel 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Models/IOdooModel.cs: -------------------------------------------------------------------------------- 1 | namespace PortaCapena.OdooJsonRpcClient.Models 2 | { 3 | public interface IOdooModel : IOdooAtributtesModel 4 | { 5 | long Id { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/PortaCapena.OdooJsonRpcClient.v3.ncrunchproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Consts/OdooExceptionName.cs: -------------------------------------------------------------------------------- 1 | namespace PortaCapena.OdooJsonRpcClient.Consts 2 | { 3 | public class OdooExceptionName 4 | { 5 | public const string AccessDenied = "odoo.exceptions.AccessDenied"; 6 | } 7 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.v3.ncrunchsolution: -------------------------------------------------------------------------------- 1 | 2 | 3 | False 4 | True 5 | 6 | -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Consts/OdooConsts.cs: -------------------------------------------------------------------------------- 1 | namespace PortaCapena.OdooJsonRpcClient.Consts 2 | { 3 | public class OdooConsts 4 | { 5 | public const string DateFormat = "yyyy-MM-dd"; 6 | public const string DateTimeFormat = "yyyy-MM-dd HH:mm:ss"; 7 | } 8 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Shared/PortaCapena.OdooJsonRpcClient.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Result/OdooException.cs: -------------------------------------------------------------------------------- 1 | namespace PortaCapena.OdooJsonRpcClient.Result 2 | { 3 | public class OdooException 4 | { 5 | public string Name { get; set; } 6 | public string Debug { get; set; } 7 | public string Message { get; set; } 8 | public string[] Arguments { get; set; } 9 | public object Context { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Attributes/OdooTableNameAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PortaCapena.OdooJsonRpcClient.Attributes 4 | { 5 | [AttributeUsage(AttributeTargets.Class)] 6 | public class OdooTableNameAttribute : Attribute 7 | { 8 | public string Name; 9 | public double Version; 10 | 11 | public OdooTableNameAttribute(string name) 12 | { 13 | this.Name = name; 14 | Version = 1.0; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Shared/RequestTestBase.cs: -------------------------------------------------------------------------------- 1 | using PortaCapena.OdooJsonRpcClient.Models; 2 | 3 | namespace PortaCapena.OdooJsonRpcClient.Shared 4 | { 5 | public class RequestTestBase 6 | { 7 | protected static readonly OdooConfig TestConfig = new OdooConfig( 8 | apiUrl: "http://localhost:8069", // "https://db-name.dev.odoo.com" 9 | dbName: "db-test", 10 | userName: "admin", 11 | password: "admin" 12 | ); 13 | } 14 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Models/OdooVersion.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace PortaCapena.OdooJsonRpcClient.Models 4 | { 5 | public class OdooVersion 6 | { 7 | [JsonProperty("server_version")] 8 | public string ServerVersion { get; set; } 9 | 10 | [JsonProperty("server_version_info")] 11 | public object[] ServerVersionInfo { get; set; } 12 | 13 | [JsonProperty("server_serie")] 14 | public string ServerSerie { get; set; } 15 | 16 | [JsonProperty("protocol_version")] 17 | public int ProtocolVersion { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Shared/Models/Create/OdooCreateProduct.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using PortaCapena.OdooJsonRpcClient.Attributes; 3 | using PortaCapena.OdooJsonRpcClient.Models; 4 | 5 | namespace PortaCapena.OdooJsonRpcClient.Shared.Models.Create 6 | { 7 | [OdooTableName("product.product")] 8 | public class OdooCreateProduct : IOdooCreateModel 9 | { 10 | [JsonProperty("name")] 11 | public string Name { get; set; } 12 | 13 | [JsonProperty("uom_id")] 14 | public int UomId { get; set; } 15 | 16 | [JsonProperty("uom_po_id")] 17 | public int UomPoId { get; set; } 18 | 19 | } 20 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Consts/OdooOperation.cs: -------------------------------------------------------------------------------- 1 | namespace PortaCapena.OdooJsonRpcClient.Consts 2 | { 3 | public static class OdooOperation 4 | { 5 | public const string Read = "read"; 6 | public const string Search = "search"; 7 | 8 | public const string SearchRead = "search_read"; 9 | public const string FieldsGet = "fields_get"; 10 | 11 | public const string Create = "create"; 12 | public const string Update = "write"; 13 | public const string Delete = "unlink"; 14 | 15 | public const string NameGet = "name_get"; 16 | public const string SearchCount = "search_count"; 17 | public const string GetMetadata = "get_metadata"; 18 | 19 | public const string OnChage = "onchage"; 20 | } 21 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Result/OdooError.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace PortaCapena.OdooJsonRpcClient.Result 4 | { 5 | public class OdooError 6 | { 7 | public int Code { get; set; } 8 | public string Message { get; set; } 9 | 10 | [JsonProperty("http_status")] 11 | public string HttpStatus { get; set; } 12 | public OdooException Data { get; set; } 13 | 14 | 15 | public OdooError(string message, int code = 0, string httpStatus = "") 16 | { 17 | Message = message; 18 | Code = code; 19 | HttpStatus = httpStatus; 20 | } 21 | 22 | public override string ToString() 23 | { 24 | return $"{Message}, \n {Data?.Message}"; ; 25 | } 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-present Patryk Bujalla and Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Shared/Models/Create/OdooVoucherCreateOrUpdate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using PortaCapena.OdooJsonRpcClient.Attributes; 4 | using PortaCapena.OdooJsonRpcClient.Converters; 5 | using PortaCapena.OdooJsonRpcClient.Models; 6 | 7 | namespace PortaCapena.OdooJsonRpcClient.Shared.Models.Create 8 | { 9 | [OdooTableName("coupon.program")] 10 | [JsonConverter(typeof(OdooModelConverter))] 11 | public class OdooVoucherCreateOrUpdate : IOdooCreateModel 12 | { 13 | [JsonProperty("name")] 14 | public string Name { get; set; } 15 | [JsonProperty("active")] 16 | public bool Active { get; set; } 17 | [JsonProperty("promo_code")] 18 | public string PromoCode { get; set; } 19 | [JsonProperty("rule_date_to")] 20 | public DateTime? RuleDateTo { get; set; } 21 | [JsonProperty("program_type")] 22 | public string ProgramType { get; set; } 23 | [JsonProperty("discount_fixed_amount")] 24 | public double DiscountFixedAmount { get; set; } 25 | [JsonProperty("discount_type")] 26 | public string DiscountType { get; set; } 27 | } 28 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Request/OdooQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PortaCapena.OdooJsonRpcClient.Models; 3 | 4 | namespace PortaCapena.OdooJsonRpcClient.Request 5 | { 6 | public class OdooQuery 7 | { 8 | /// 9 | /// Get records with condition 10 | /// 11 | public OdooFilter Filters { get; set; } 12 | 13 | /// 14 | /// Get records with ony selected names 15 | /// 16 | public HashSet ReturnFields { get; set; } 17 | 18 | 19 | /// 20 | /// Skip records 21 | /// 22 | public int? Offset { get; set; } 23 | 24 | /// 25 | /// Take records 26 | /// 27 | public int? Limit { get; set; } 28 | 29 | /// 30 | /// Order by field 31 | /// "{odooPropertyName} ASC" or "{odooPropertyName} DESC"; 32 | /// 33 | public string Order { get; set; } 34 | 35 | 36 | public OdooQuery() 37 | { 38 | ReturnFields = new HashSet(); 39 | Filters = new OdooFilter(); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Request/OdooRequestParams.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Newtonsoft.Json; 3 | 4 | namespace PortaCapena.OdooJsonRpcClient.Request 5 | { 6 | public class OdooRequestParams 7 | { 8 | [JsonProperty("service")] 9 | public string Service { get; } 10 | 11 | [JsonProperty("method")] 12 | public string Method { get; } 13 | 14 | [JsonProperty("args")] 15 | public object[] Args { get; } 16 | 17 | 18 | [JsonIgnore] 19 | public string Url { get; } 20 | 21 | public OdooRequestParams(string url, string service, string method, params object[] paramethers) 22 | { 23 | this.Url = url; 24 | this.Service = service; 25 | this.Method = method; 26 | this.Args = PrepareParams(paramethers); 27 | } 28 | 29 | protected object[] PrepareParams(object[] paramethers) 30 | { 31 | var list = paramethers.ToList(); 32 | for (int i = list.Count - 1; i >= 0; i--) 33 | { 34 | if (list[i] == null) list.RemoveAt(i); 35 | else break; 36 | } 37 | return list.ToArray(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Models/OdooDictionaryModelOfT.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using PortaCapena.OdooJsonRpcClient.Extensions; 4 | 5 | namespace PortaCapena.OdooJsonRpcClient.Models 6 | { 7 | public class OdooDictionaryModel : OdooDictionaryModel where T : IOdooAtributtesModel, new() 8 | { 9 | public OdooDictionaryModel() 10 | { 11 | TableName = OdooExtensions.GetOdooTableName(); 12 | } 13 | 14 | public static OdooDictionaryModel Create(Expression> expression) 15 | { 16 | return new OdooDictionaryModel().Add(expression); 17 | } 18 | 19 | public static OdooDictionaryModel Create(Expression> expression, object value) 20 | { 21 | return new OdooDictionaryModel().Add(expression, value); 22 | } 23 | 24 | public OdooDictionaryModel Add(Expression> expression, object value) 25 | { 26 | Add(expression, value); 27 | return this; 28 | } 29 | 30 | public OdooDictionaryModel Add(Expression> expression) 31 | { 32 | AddFromExpresion(expression); 33 | return this; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Extensions/EnumExtension.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using PortaCapena.OdooJsonRpcClient.Converters; 3 | using System; 4 | using System.ComponentModel; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Runtime.Serialization; 8 | 9 | namespace PortaCapena.OdooJsonRpcClient.Extensions 10 | { 11 | public static class EnumExtension 12 | { 13 | public static string Description(this Enum value) 14 | { 15 | try 16 | { 17 | FieldInfo fi = value.GetType().GetField(value.ToString()); 18 | 19 | DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes( 20 | typeof(DescriptionAttribute), false); 21 | 22 | if (attributes != null && attributes.Length > 0) return attributes[0].Description; 23 | else return value.ToString(); 24 | } 25 | catch (Exception) 26 | { 27 | return value.ToString(); 28 | } 29 | } 30 | 31 | public static string OdooValue(this Enum value) 32 | { 33 | var type = value.GetType().GetField(value.ToString()); 34 | return OdooModelMapper.GetOdooEnumName(type); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /.github/workflows/code-coverage-badge.yml: -------------------------------------------------------------------------------- 1 | name: Unit Test With Coverage 2 | on: 3 | workflow_dispatch: 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | - name: Setup .NET 10 | uses: actions/setup-dotnet@v4 11 | with: 12 | dotnet-version: 6.0.x 13 | - name: Restore dependencies 14 | run: dotnet restore 15 | - name: Build 16 | run: dotnet build --no-restore 17 | - name: Run Unit Tests 18 | run: dotnet test -p:CollectCoverage=true -p:CoverletOutput=TestResults/ -p:CoverletOutputFormat=opencover --no-build --verbosity normal PortaCapena.OdooJsonRpcClient.Tests/ 19 | - name: Create Test Coverage Badge 20 | uses: simon-k/dotnet-code-coverage-badge@v1.0.0 21 | id: create_coverage_badge 22 | with: 23 | label: Test Coverage 24 | color: brightgreen 25 | path: PortaCapena.OdooJsonRpcClient.Tests/TestResults/coverage.opencover.xml 26 | gist-filename: code-coverage.json 27 | gist-id: 54596b8c061e5c3c25b3072822a105a0 28 | gist-auth-token: ${{ secrets.GIST_AUTH_TOKEN }} 29 | - name: Print code coverage 30 | run: echo "Code coverage percentage ${{steps.create_coverage_badge.outputs.percentage}}%" 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Extensions/OdooAtributtesModelExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using PortaCapena.OdooJsonRpcClient.Attributes; 5 | using PortaCapena.OdooJsonRpcClient.Converters; 6 | using PortaCapena.OdooJsonRpcClient.Models; 7 | 8 | namespace PortaCapena.OdooJsonRpcClient.Extensions 9 | { 10 | public static class OdooAtributtesModelExtensions 11 | { 12 | public static string OdooTableName(this IOdooAtributtesModel model) 13 | { 14 | if (!(model.GetType().GetCustomAttributes(typeof(OdooTableNameAttribute), true).FirstOrDefault() is OdooTableNameAttribute attribute)) 15 | throw new ArgumentException($"Mising attribute '{nameof(OdooTableNameAttribute)}' for model '{model.GetType().Name}'"); 16 | return attribute.Name; 17 | } 18 | 19 | public static string OdooPropertyName(this T model, string name) where T : IOdooAtributtesModel 20 | { 21 | return OdooExtensions.GetOdooPropertyName(name); 22 | } 23 | 24 | public static string OdooPropertyName(this T model, Expression> expression) where T : IOdooAtributtesModel 25 | { 26 | return OdooExpresionMapper.GetOdooPropertyName(expression); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/IOdooRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using PortaCapena.OdooJsonRpcClient.Models; 3 | using PortaCapena.OdooJsonRpcClient.Request; 4 | using PortaCapena.OdooJsonRpcClient.Result; 5 | 6 | namespace PortaCapena.OdooJsonRpcClient 7 | { 8 | public interface IOdooRepository where T : IOdooModel, new() 9 | { 10 | OdooQueryBuilder Query(); 11 | 12 | Task> CreateAsync(IOdooCreateModel model, OdooContext context = null); 13 | Task> CreateAsync(OdooDictionaryModel model, OdooContext context = null); 14 | 15 | Task> UpdateAsync(IOdooCreateModel model, long id, OdooContext context = null); 16 | Task> UpdateRangeAsync(IOdooCreateModel model, long[] ids, OdooContext context = null); 17 | Task> UpdateAsync(OdooDictionaryModel model, long id, OdooContext context = null); 18 | Task> UpdateRangeAsync(OdooDictionaryModel model, long[] ids, OdooContext context = null); 19 | 20 | Task> DeleteAsync(T model, OdooContext context = null); 21 | Task> DeleteAsync(long id, OdooContext context = null); 22 | Task> DeleteRangeAsync(T[] models, OdooContext context = null); 23 | } 24 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Consts/OdooOperators.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace PortaCapena.OdooJsonRpcClient.Consts 4 | { 5 | public enum OdooOperator 6 | { 7 | [Description("=")] 8 | EqualsTo, 9 | [Description("!=")] 10 | NotEqualsTo, 11 | [Description("=?")] 12 | UnsetOrEqualsTo, 13 | 14 | [Description(">")] 15 | GreaterThan, 16 | [Description(">=")] 17 | GreaterThanOrEqualTo, 18 | 19 | [Description("<")] 20 | LessThan, 21 | [Description("<=")] 22 | LessThanOrEqualTo, 23 | 24 | [Description("like")] 25 | Like, 26 | [Description("not like")] 27 | NotLike, 28 | [Description("=like")] 29 | EqualsLike, 30 | 31 | [Description("ilike")] 32 | CaseInsensitiveLike, 33 | [Description("not ilike")] 34 | CaseInsensitiveNotLike, 35 | [Description("=ilike")] 36 | CaseInsensitiveEqualsLike, 37 | 38 | [Description("in")] 39 | In, 40 | [Description("not in")] 41 | NotIn, 42 | 43 | [Description("child_of")] 44 | ChildOf, 45 | [Description("parent_of")] 46 | ParentOf, 47 | 48 | [Description("&")] 49 | And, 50 | [Description("|")] 51 | Or, 52 | [Description("!")] 53 | Not 54 | } 55 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/PortaCapena.OdooJsonRpcClient.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | Patryk Bujalla 6 | Patryk Bujalla 7 | true 8 | https://github.com/patricoos/PortaCapena.OdooJsonRpcClient 9 | ic_project.png 10 | 11 | https://github.com/patricoos/PortaCapena.OdooJsonRpcClient 12 | Patryk Bujalla 13 | Odoo JsonRpc Client 14 | JsonRpc Odoo Client 15 | 1.0.20 16 | README.md 17 | LICENSE.md 18 | 19 | 20 | 21 | 22 | 23 | 24 | True 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Result/OdooResult.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace PortaCapena.OdooJsonRpcClient.Result 4 | { 5 | public class OdooResult 6 | { 7 | public int Id { get; set; } 8 | public string Jsonrpc { get; set; } 9 | public OdooError Error { get; set; } 10 | 11 | [JsonProperty("result")] 12 | public TResult Value { get; set; } 13 | 14 | 15 | public string Message => Error?.Message; 16 | public bool Succeed => Error == null; 17 | public bool Failed => !Succeed; 18 | 19 | public static OdooResult SucceedResult(TResult value) => new OdooResult(value); 20 | public static OdooResult FailedResult(string message, int code = 0, string httpStatus = "") => new OdooResult(message, code, httpStatus); 21 | public static OdooResult FailedResult(OdooResult result) => new OdooResult(result.Id, result.Jsonrpc, result.Error); 22 | 23 | public OdooResult() { } 24 | 25 | private OdooResult(TResult value) 26 | { 27 | Value = value; 28 | } 29 | 30 | private OdooResult(int id, string jsonrpc, OdooError error) 31 | { 32 | Id = id; 33 | Jsonrpc = jsonrpc; 34 | Error = error; 35 | } 36 | private OdooResult(string message, int code = 0, string httpStatus = "") 37 | { 38 | Error = new OdooError(message, code, httpStatus); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Example/PortaCapena.OdooJsonRpcClient.Example.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | false 7 | 8 | Linux 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | 21 | 22 | all 23 | runtime; build; native; contentfiles; analyzers; buildtransitive 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Tests/OdooConfigTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using FluentAssertions; 3 | using PortaCapena.OdooJsonRpcClient.Models; 4 | using Xunit; 5 | 6 | namespace PortaCapena.OdooJsonRpcClient.Tests 7 | { 8 | public class OdooConfigTests 9 | { 10 | [Fact] 11 | public void Can_create_OdooConfig() 12 | { 13 | var context = new OdooConfig("https://odoo-api-url.com", "odoo-db-name", "admin", "admin"); 14 | 15 | context.ApiUrl.Should().Be("https://odoo-api-url.com"); 16 | 17 | context.ApiUrlJson.Should().Be("https://odoo-api-url.com/jsonrpc"); 18 | 19 | context.DbName.Should().Be("odoo-db-name"); 20 | context.UserName.Should().Be("admin"); 21 | context.Password.Should().Be("admin"); 22 | 23 | context.Context.Should().NotBeNull(); 24 | context.Context.Should().BeEmpty(); 25 | } 26 | 27 | [Fact] 28 | public void Can_create_with_slash_on_end() 29 | { 30 | var context = new OdooConfig("https://odoo-api-url.com/", "odoo-db-name", "admin", "admin"); 31 | 32 | context.ApiUrl.Should().Be("https://odoo-api-url.com"); 33 | 34 | context.ApiUrlJson.Should().Be("https://odoo-api-url.com/jsonrpc"); 35 | 36 | context.DbName.Should().Be("odoo-db-name"); 37 | context.UserName.Should().Be("admin"); 38 | context.Password.Should().Be("admin"); 39 | 40 | context.Context.Should().NotBeNull(); 41 | context.Context.Should().BeEmpty(); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release to nuget 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | publish-nuget: 9 | runs-on: ubuntu-latest 10 | env: 11 | config: 'Release' 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Setup .NET 16 | uses: actions/setup-dotnet@v4 17 | with: 18 | dotnet-version: 6.0.x 19 | 20 | - name: Install dependencies 21 | run: dotnet restore 22 | 23 | - name: Build 24 | run: dotnet build --configuration Release --no-restore 25 | 26 | - name: Test 27 | run: dotnet test PortaCapena.OdooJsonRpcClient.Tests/PortaCapena.OdooJsonRpcClient.Tests.csproj --configuration Release --no-restore --no-build 28 | 29 | - name: Create Nuget Package 30 | run: dotnet pack --configuration Release /p:Version=${{github.event.release.tag_name}} /p:PackageReleaseNotes="See https://github.com/patricoos/PortaCapena.OdooJsonRpcClient/releases/tag/${{github.event.release.tag_name}}" 31 | working-directory: ./PortaCapena.OdooJsonRpcClient 32 | 33 | - name: Archive Nuget Package 34 | uses: actions/upload-artifact@v1 35 | with: 36 | name: PortaCapena.OdooJsonRpcClient.${{github.event.release.tag_name}}.nupkg 37 | path: ./PortaCapena.OdooJsonRpcClient/bin/Release/PortaCapena.OdooJsonRpcClient.${{github.event.release.tag_name}}.nupkg 38 | 39 | - name: Publish NuGet Package 40 | run: dotnet nuget push **/PortaCapena.OdooJsonRpcClient.${{github.event.release.tag_name}}.nupkg --api-key ${{secrets.NUGET_API_KEY}} --source https://api.nuget.org/v3/index.json --no-symbols true 41 | -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Models/OdooConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PortaCapena.OdooJsonRpcClient.Models 4 | { 5 | public class OdooConfig 6 | { 7 | public string ApiUrl { get; } 8 | public string ApiUrlJson => this.ApiUrl + "/jsonrpc"; 9 | public string DbName { get; } 10 | public string UserName { get; } 11 | public string Password { get; } 12 | public TimeSpan Timeout { get; } 13 | 14 | public OdooContext Context { get; } 15 | 16 | 17 | public OdooConfig(string apiUrl, string dbName, string userName, string password, TimeSpan timeout = default(TimeSpan)) 18 | { 19 | this.ApiUrl = apiUrl.TrimEnd(new[] { '/' }); 20 | this.DbName = dbName; 21 | this.UserName = userName; 22 | this.Password = password; 23 | this.Context = new OdooContext(); 24 | this.Timeout = timeout; 25 | } 26 | 27 | public OdooConfig(string apiUrl, string dbName, string userName, string password, OdooContext context, TimeSpan timeout = default(TimeSpan)) : this(apiUrl, dbName, userName, password, timeout) 28 | { 29 | this.Context = new OdooContext(context); 30 | } 31 | 32 | public OdooConfig(string apiUrl, string dbName, string userName, string password, string language, TimeSpan timeout = default(TimeSpan)) : this(apiUrl, dbName, userName, password, timeout) 33 | { 34 | this.Context = new OdooContext(language); 35 | } 36 | 37 | public OdooConfig(string apiUrl, string dbName, string userName, string password, string language, string timezone, TimeSpan timeout = default(TimeSpan)) : this(apiUrl, dbName, userName, password, timeout) 38 | { 39 | this.Context = new OdooContext(language, timezone); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Extensions/OdooExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using Newtonsoft.Json; 5 | using PortaCapena.OdooJsonRpcClient.Attributes; 6 | using PortaCapena.OdooJsonRpcClient.Consts; 7 | using PortaCapena.OdooJsonRpcClient.Models; 8 | using PortaCapena.OdooJsonRpcClient.Result; 9 | 10 | namespace PortaCapena.OdooJsonRpcClient.Extensions 11 | { 12 | public static class OdooExtensions 13 | { 14 | public static string GetOdooTableName() where T : IOdooAtributtesModel 15 | { 16 | if (!(typeof(T).GetCustomAttributes(typeof(OdooTableNameAttribute), true).FirstOrDefault() is OdooTableNameAttribute attribute)) 17 | throw new ArgumentException($"Mising attribute '{nameof(OdooTableNameAttribute)}' for model '{typeof(T).Name}'"); 18 | return attribute.Name; 19 | } 20 | 21 | public static string GetOdooPropertyName(string name) where T : IOdooAtributtesModel 22 | { 23 | var property = typeof(T).GetProperties().First(x => string.Equals(x.Name, name)); 24 | var attribute = property.GetCustomAttributes(); 25 | 26 | return attribute.FirstOrDefault()?.PropertyName; 27 | } 28 | 29 | public static OdooResult ToResult(this OdooResult result, TResult newValue) 30 | { 31 | return new OdooResult { Id = result.Id, Jsonrpc = result.Jsonrpc, Value = newValue }; 32 | } 33 | 34 | public static string ToOdooDateTimeString(this DateTime date) 35 | { 36 | return date.ToString(OdooConsts.DateTimeFormat); 37 | } 38 | public static string ToOdooDateString(this DateTime date) 39 | { 40 | return date.ToString(OdooConsts.DateFormat); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Shared/Models/AccountPaymentTermOdooModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using PortaCapena.OdooJsonRpcClient.Attributes; 4 | using PortaCapena.OdooJsonRpcClient.Converters; 5 | using PortaCapena.OdooJsonRpcClient.Models; 6 | 7 | namespace PortaCapena.OdooJsonRpcClient.Shared.Models 8 | { 9 | [OdooTableName("account.payment.term")] 10 | [JsonConverter(typeof(OdooModelConverter))] 11 | public class AccountPaymentTermOdooModel : IOdooModel 12 | { 13 | 14 | // required 15 | [JsonProperty("name")] 16 | public string Name { get; set; } 17 | 18 | [JsonProperty("active")] 19 | public bool? Active { get; set; } 20 | 21 | [JsonProperty("note")] 22 | public string Note { get; set; } 23 | 24 | // account.payment.term.line 25 | [JsonProperty("line_ids")] 26 | public long[] LineIds { get; set; } 27 | 28 | // res.company 29 | [JsonProperty("company_id")] 30 | public long? CompanyId { get; set; } 31 | 32 | // required 33 | [JsonProperty("sequence")] 34 | public int Sequence { get; set; } 35 | 36 | [JsonProperty("id")] 37 | public long Id { get; set; } 38 | 39 | [JsonProperty("display_name")] 40 | public string DisplayName { get; set; } 41 | 42 | // res.users 43 | [JsonProperty("create_uid")] 44 | public long? CreateUid { get; set; } 45 | 46 | [JsonProperty("create_date")] 47 | public DateTime? CreateDate { get; set; } 48 | 49 | // res.users 50 | [JsonProperty("write_uid")] 51 | public long? WriteUid { get; set; } 52 | 53 | [JsonProperty("write_date")] 54 | public DateTime? WriteDate { get; set; } 55 | 56 | [JsonProperty("__last_update")] 57 | public DateTime? LastUpdate { get; set; } 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Tests/PortaCapena.OdooJsonRpcClient.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | false 7 | 8 | Linux 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | all 30 | runtime; build; native; contentfiles; analyzers; buildtransitive 31 | 32 | 33 | all 34 | runtime; build; native; contentfiles; analyzers; buildtransitive 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Shared/Models/ProductPricelistOdooDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using PortaCapena.OdooJsonRpcClient.Attributes; 4 | using PortaCapena.OdooJsonRpcClient.Converters; 5 | using PortaCapena.OdooJsonRpcClient.Models; 6 | 7 | namespace PortaCapena.OdooJsonRpcClient.Shared.Models 8 | { 9 | [OdooTableName("product.pricelist")] 10 | [JsonConverter(typeof(OdooModelConverter))] 11 | public class ProductPriceListOdooDto : IOdooModel 12 | { 13 | 14 | [JsonProperty("name")] 15 | public string Name { get; set; } 16 | 17 | [JsonProperty("active")] 18 | public bool? Active { get; set; } 19 | 20 | // product.pricelist.item 21 | [JsonProperty("item_ids")] 22 | public long[] ItemIds { get; set; } 23 | 24 | // res.currency 25 | [JsonProperty("currency_id")] 26 | public long CurrencyId { get; set; } 27 | 28 | // res.company 29 | [JsonProperty("company_id")] 30 | public long? CompanyId { get; set; } 31 | 32 | [JsonProperty("sequence")] 33 | public int? Sequence { get; set; } 34 | 35 | // res.country.group 36 | [JsonProperty("country_group_ids")] 37 | public long[] CountryGroupIds { get; set; } 38 | 39 | [JsonProperty("discount_policy")] 40 | public string DiscountPolicy { get; set; } 41 | 42 | [JsonProperty("id")] 43 | public long Id { get; set; } 44 | 45 | [JsonProperty("display_name")] 46 | public string DisplayName { get; set; } 47 | 48 | // res.users 49 | [JsonProperty("create_uid")] 50 | public long? CreateUid { get; set; } 51 | 52 | [JsonProperty("create_date")] 53 | public DateTime? CreateDate { get; set; } 54 | 55 | // res.users 56 | [JsonProperty("write_uid")] 57 | public long? WriteUid { get; set; } 58 | 59 | [JsonProperty("write_date")] 60 | public DateTime? WriteDate { get; set; } 61 | 62 | [JsonProperty("__last_update")] 63 | public DateTime? LastUpdate { get; set; } 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Tests/OdooModelMapperTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using System.Linq; 4 | using PortaCapena.OdooJsonRpcClient.Models; 5 | using PortaCapena.OdooJsonRpcClient.Shared.Models; 6 | using Xunit; 7 | using PortaCapena.OdooJsonRpcClient.Converters; 8 | using Newtonsoft.Json; 9 | using Xunit.Abstractions; 10 | using System.IO; 11 | 12 | namespace PortaCapena.OdooJsonRpcClient.Tests 13 | { 14 | 15 | public class OdooModelMapperTests 16 | { 17 | [Fact] 18 | public void CanMap_StringToEnum() 19 | { 20 | object output; 21 | 22 | /* 23 | * In StatusPurchaseOrderOdooEnum we have this declaration : 24 | * 25 | * [EnumMember(Value = "done")] 26 | * Locked = 5, 27 | * 28 | * So the JValue should be "done" (String) and the output should be Locked 29 | * */ 30 | 31 | OdooModelMapper.ConverOdooPropertyToDotNet( 32 | typeof(StatusPurchaseOrderOdooEnum), 33 | new Newtonsoft.Json.Linq.JValue("done"), 34 | out output 35 | ); 36 | 37 | var result = (StatusPurchaseOrderOdooEnum)output; 38 | 39 | Assert.Equal(StatusPurchaseOrderOdooEnum.Locked, result); 40 | } 41 | 42 | 43 | [Fact] 44 | public void CanMap_IntegerToEnum() 45 | { 46 | object output; 47 | 48 | /* 49 | * In PriorityPurchaseOrderOdooEnum we have this declaration : 50 | * 51 | * [EnumMember(Value = "1")] 52 | * Urgent = 2, 53 | * 54 | * So the JValue should be 1 (Integer) and the output should be Urgent 55 | * */ 56 | 57 | OdooModelMapper.ConverOdooPropertyToDotNet( 58 | typeof(PriorityPurchaseOrderOdooEnum), 59 | new Newtonsoft.Json.Linq.JValue(1), 60 | out output 61 | ); 62 | 63 | var result = (PriorityPurchaseOrderOdooEnum)output; 64 | 65 | Assert.Equal(PriorityPurchaseOrderOdooEnum.Urgent, result); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Utils/OdooFilterMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using PortaCapena.OdooJsonRpcClient.Consts; 4 | using PortaCapena.OdooJsonRpcClient.Extensions; 5 | using PortaCapena.OdooJsonRpcClient.Models; 6 | using PortaCapena.OdooJsonRpcClient.Request; 7 | 8 | namespace PortaCapena.OdooJsonRpcClient.Utils 9 | { 10 | public class OdooFilterMapper 11 | { 12 | public static OdooFilter ToOdooExpresion(string propertyname, OdooOperator odooOperator, object value) where T : IOdooModel, new() 13 | { 14 | return new OdooFilter { OdooExtensions.GetOdooPropertyName(propertyname), odooOperator.Description(), value }; 15 | } 16 | 17 | public static OdooFilter ToOdooExpresion(object left, OdooOperator odooOperator, object right) where T : IOdooModel, new() 18 | { 19 | OdooFilter result; 20 | 21 | if (left is string member && right is object value) 22 | result = ToOdooExpresion(member, odooOperator, value); 23 | 24 | else if (left is object constant && right is string memberExpression) 25 | result = ToOdooExpresion(memberExpression, odooOperator, constant); 26 | 27 | else if (left.GetType().IsArray && right.GetType().IsArray) 28 | { 29 | var leftArray = left as object[]; 30 | var rightArray = right as object[]; 31 | if ((leftArray != null && leftArray.Length == 3 && leftArray.All(x => x != null) && leftArray[1] is OdooOperator leftOperator) && 32 | (rightArray != null && rightArray.Length == 3 && rightArray.All(x => x != null) && rightArray[1] is OdooOperator rightOperator)) 33 | { 34 | result = new OdooFilter 35 | { 36 | ToOdooExpresion(leftArray[0], leftOperator, leftArray[2]), 37 | odooOperator.Description(), 38 | ToOdooExpresion(rightArray[0], rightOperator, rightArray[2]) 39 | }; 40 | } 41 | else 42 | throw new ArgumentException("Invalid expresion"); 43 | } 44 | else 45 | throw new ArgumentException("Incalid Expresion"); 46 | 47 | return result; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Converters/OdooModelConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Serialization; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Linq; 7 | 8 | namespace PortaCapena.OdooJsonRpcClient.Converters 9 | { 10 | public class OdooModelConverter : JsonConverter 11 | { 12 | public override bool CanWrite { get { return false; } } 13 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 14 | { 15 | throw new NotImplementedException(); 16 | } 17 | 18 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 19 | { 20 | var propertiesMap = new Dictionary(); 21 | 22 | foreach (var propertyInfo in objectType.GetProperties()) 23 | { 24 | var atributes = Attribute.GetCustomAttributes(propertyInfo); 25 | var jsonAttribute = atributes.FirstOrDefault(x => x is JsonPropertyAttribute) as JsonPropertyAttribute; 26 | if (jsonAttribute == null) 27 | { 28 | if (atributes.Any(x => x is JsonIgnoreAttribute)) 29 | continue; 30 | throw new ArgumentException($"Mising attribute '{nameof(JsonPropertyAttribute)}' for property '{propertyInfo.Name}' in model '{objectType.Name}'"); 31 | } 32 | propertiesMap.Add(jsonAttribute.PropertyName, propertyInfo.Name); 33 | } 34 | 35 | var instance = Activator.CreateInstance(objectType); 36 | 37 | JObject jObject = JObject.Load(reader); 38 | 39 | foreach (var keyValuePair in jObject) 40 | { 41 | if (!propertiesMap.TryGetValue(keyValuePair.Key, out var dotNetName)) 42 | continue; 43 | 44 | var dotNetType = objectType.GetProperty(dotNetName); 45 | 46 | if (OdooModelMapper.ConverOdooPropertyToDotNet(dotNetType.PropertyType, keyValuePair.Value, out var result)) 47 | dotNetType.SetValue(instance, result); 48 | } 49 | 50 | return instance; 51 | } 52 | 53 | public override bool CanConvert(Type objectType) 54 | { 55 | return Attribute.GetCustomAttributes(objectType).Any(v => v is KnownTypeAttribute); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Tests/OdooContextTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using FluentAssertions; 4 | using PortaCapena.OdooJsonRpcClient.Models; 5 | using Xunit; 6 | 7 | namespace PortaCapena.OdooJsonRpcClient.Tests 8 | { 9 | public class OdooContextTests 10 | { 11 | [Fact] 12 | public void Can_create_with_lang_param() 13 | { 14 | var context = new OdooContext("pl_PL"); 15 | 16 | context.Count.Should().Be(1); 17 | context.First().Key.Should().Be("lang"); 18 | context.First().Value.Should().Be("pl_PL"); 19 | } 20 | 21 | [Fact] 22 | public void Can_create_with_lang_and_timezone_param() 23 | { 24 | var context = new OdooContext("pl_PL", "time1"); 25 | 26 | context.Count.Should().Be(2); 27 | context.First().Key.Should().Be("lang"); 28 | context.First().Value.Should().Be("pl_PL"); 29 | 30 | context.Skip(1).First().Key.Should().Be("tz"); 31 | context.Skip(1).First().Value.Should().Be("time1"); 32 | } 33 | 34 | [Fact] 35 | public void Can_update_one_prop() 36 | { 37 | var context = new OdooContext("pl_PL", "time1"); 38 | 39 | context.Language = "new Language"; 40 | 41 | context.Count.Should().Be(2); 42 | context.First().Key.Should().Be("lang"); 43 | context.First().Value.Should().Be("new Language"); 44 | 45 | context.Skip(1).First().Key.Should().Be("tz"); 46 | context.Skip(1).First().Value.Should().Be("time1"); 47 | } 48 | 49 | [Fact] 50 | public void Can_create_with_lang_param_ctor_and_with_dict() 51 | { 52 | var context = new OdooContext("pl_PL") 53 | { 54 | {"test_prop", "test value"} 55 | }; 56 | 57 | context.Count.Should().Be(2); 58 | context.First().Key.Should().Be("lang"); 59 | context.First().Value.Should().Be("pl_PL"); 60 | 61 | context.Skip(1).First().Key.Should().Be("test_prop"); 62 | context.Skip(1).First().Value.Should().Be("test value"); 63 | } 64 | 65 | [Fact] 66 | public void Can_set_null_to_remove_value() 67 | { 68 | var context = new OdooContext("pl_PL", "time1"); 69 | 70 | context.Timezone = null; 71 | 72 | context.Count.Should().Be(1); 73 | context.First().Key.Should().Be("lang"); 74 | context.First().Value.Should().Be("pl_PL"); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master, release ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '40 18 * * 2' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'csharp' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v3 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v3 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v3 71 | -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Tests/OdooRequestModelTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using FluentAssertions; 3 | using PortaCapena.OdooJsonRpcClient.Models; 4 | using PortaCapena.OdooJsonRpcClient.Request; 5 | using Xunit; 6 | 7 | namespace PortaCapena.OdooJsonRpcClient.Tests 8 | { 9 | public class OdooRequestModelTests 10 | { 11 | private OdooConfig Confing = new OdooConfig("url","db", "user", "pass"); 12 | [Fact] 13 | public void RequestModel_should_return_request_with_corect_params_count() 14 | { 15 | var query = new OdooQuery() 16 | { 17 | ReturnFields = new HashSet { "name" }, 18 | Filters = new OdooFilter { new object[] { "id", "=", 66 } } 19 | }; 20 | var request = OdooRequestModel.SearchRead(Confing, 2, "table", query); 21 | request.Params.Args.Length.Should().Be(7); 22 | 23 | 24 | var query2 = new OdooQuery(){Filters = new OdooFilter{ new object[] { "id", "=", 66 } }}; 25 | var request2 = OdooRequestModel.SearchRead(Confing, 2, "table", query2); 26 | request2.Params.Args.Length.Should().Be(6); 27 | 28 | 29 | var query3 = new OdooQuery(){ReturnFields = new HashSet { "name" }}; 30 | var request3 = OdooRequestModel.SearchRead(Confing, 2, "table", query3); 31 | request3.Params.Args.Length.Should().Be(7); 32 | 33 | 34 | var request4 = OdooRequestModel.SearchRead(Confing, 2, "table"); 35 | request4.Params.Args.Length.Should().Be(6); 36 | 37 | 38 | var query5 = new OdooQuery(){Offset = 10}; 39 | var request5 = OdooRequestModel.SearchRead(Confing, 2, "table", query5); 40 | request5.Params.Args.Length.Should().Be(7); 41 | 42 | 43 | var query6 = new OdooQuery() { Limit = 10 }; 44 | var request6 = OdooRequestModel.SearchRead(Confing, 2, "table", query6); 45 | request6.Params.Args.Length.Should().Be(7); 46 | 47 | 48 | var query7 = new OdooQuery() { Order = "id" }; 49 | var request7 = OdooRequestModel.SearchRead(Confing, 2, "table", query7); 50 | request7.Params.Args.Length.Should().Be(7); 51 | 52 | 53 | var queryTest = new OdooQuery() 54 | { 55 | ReturnFields = new HashSet { "name" }, 56 | Filters = OdooFilter.Create().EqualTo("id", 66) 57 | }; 58 | 59 | var requestTest = OdooRequestModel.SearchRead(Confing, 2, "table", queryTest); 60 | requestTest.Params.Args.Length.Should().Be(7); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Converters/OdooExpresionMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using PortaCapena.OdooJsonRpcClient.Extensions; 4 | using PortaCapena.OdooJsonRpcClient.Models; 5 | 6 | namespace PortaCapena.OdooJsonRpcClient.Converters 7 | { 8 | public static class OdooExpresionMapper 9 | { 10 | internal static string GetOdooPropertyName(Expression> expression) where T : IOdooAtributtesModel 11 | { 12 | return OdooExtensions.GetOdooPropertyName(GetPropertyName(expression)); 13 | } 14 | 15 | internal static string GetPropertyName(Expression> expression) where T : IOdooAtributtesModel 16 | { 17 | if (expression.Body is MemberExpression body) 18 | return body.Member.Name; 19 | 20 | if (expression.Body is UnaryExpression unar && unar.Operand is MemberExpression member) 21 | return member.Member.Name; 22 | 23 | return null; 24 | } 25 | internal static string GetPropertyName(Expression> expression) where T : IOdooAtributtesModel 26 | { 27 | if (expression.Body is MemberExpression body) 28 | return body.Member.Name; 29 | 30 | if (expression.Body is UnaryExpression unar && unar.Operand is MemberExpression member) 31 | return member.Member.Name; 32 | 33 | return null; 34 | } 35 | internal static string GetPropertyName(Expression> expression) where T : IOdooAtributtesModel 36 | { 37 | if (expression.Body is MemberExpression body) 38 | return body.Member.Name; 39 | 40 | if (expression.Body is UnaryExpression unar && unar.Operand is MemberExpression member) 41 | return member.Member.Name; 42 | 43 | return null; 44 | } 45 | 46 | internal static string GetPropertyName(Expression> expression) where T : IOdooAtributtesModel 47 | { 48 | if (expression.Body is MemberExpression body) 49 | return body.Member.Name; 50 | 51 | if (expression.Body is UnaryExpression unar && unar.Operand is MemberExpression member) 52 | return member.Member.Name; 53 | 54 | return null; 55 | } 56 | 57 | internal static string GetPropertyName(Expression> expression) where T : IOdooAtributtesModel 58 | { 59 | if (expression.Body is MemberExpression body) 60 | return body.Member.Name; 61 | 62 | if (expression.Body is UnaryExpression unar && unar.Operand is MemberExpression member) 63 | return member.Member.Name; 64 | 65 | return null; 66 | } 67 | 68 | } 69 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Shared/Models/ResCurrencyOdooModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Converters; 5 | using PortaCapena.OdooJsonRpcClient.Attributes; 6 | using PortaCapena.OdooJsonRpcClient.Converters; 7 | using PortaCapena.OdooJsonRpcClient.Models; 8 | 9 | namespace PortaCapena.OdooJsonRpcClient.Shared.Models 10 | { 11 | [OdooTableName("res.currency")] 12 | [JsonConverter(typeof(OdooModelConverter))] 13 | public class ResCurrencyOdooModel : IOdooModel 14 | { 15 | 16 | // required 17 | [JsonProperty("name")] 18 | public string Name { get; set; } 19 | 20 | // required 21 | [JsonProperty("symbol")] 22 | public string Symbol { get; set; } 23 | 24 | [JsonProperty("rate")] 25 | public double? Rate { get; set; } 26 | 27 | // res.currency.rate 28 | [JsonProperty("rate_ids")] 29 | public long[] RateIds { get; set; } 30 | 31 | [JsonProperty("rounding")] 32 | public double? Rounding { get; set; } 33 | 34 | [JsonProperty("decimal_places")] 35 | public int? DecimalPlaces { get; set; } 36 | 37 | [JsonProperty("active")] 38 | public bool? Active { get; set; } 39 | 40 | [JsonProperty("position")] 41 | public SymbolPositionResCurrencyOdooEnum? Position { get; set; } 42 | 43 | [JsonProperty("date")] 44 | public DateTime? Date { get; set; } 45 | 46 | [JsonProperty("currency_unit_label")] 47 | public string CurrencyUnitLabel { get; set; } 48 | 49 | [JsonProperty("currency_subunit_label")] 50 | public string CurrencySubunitLabel { get; set; } 51 | 52 | [JsonProperty("display_rounding_warning")] 53 | public bool? DisplayRoundingWarning { get; set; } 54 | 55 | [JsonProperty("id")] 56 | public long Id { get; set; } 57 | 58 | [JsonProperty("display_name")] 59 | public string DisplayName { get; set; } 60 | 61 | // res.users 62 | [JsonProperty("create_uid")] 63 | public long? CreateUid { get; set; } 64 | 65 | [JsonProperty("create_date")] 66 | public DateTime? CreateDate { get; set; } 67 | 68 | // res.users 69 | [JsonProperty("write_uid")] 70 | public long? WriteUid { get; set; } 71 | 72 | [JsonProperty("write_date")] 73 | public DateTime? WriteDate { get; set; } 74 | 75 | [JsonProperty("__last_update")] 76 | public DateTime? LastUpdate { get; set; } 77 | } 78 | 79 | 80 | // Determines where the currency symbol should be placed after or before the amount. 81 | [JsonConverter(typeof(StringEnumConverter))] 82 | public enum SymbolPositionResCurrencyOdooEnum 83 | { 84 | [EnumMember(Value = "after")] 85 | AfterAmount = 1, 86 | 87 | [EnumMember(Value = "before")] 88 | BeforeAmount = 2, 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29806.167 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PortaCapena.OdooJsonRpcClient", "PortaCapena.OdooJsonRpcClient\PortaCapena.OdooJsonRpcClient.csproj", "{71528764-0A12-467E-AB4F-21F2E694A32B}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PortaCapena.OdooJsonRpcClient.Tests", "PortaCapena.OdooJsonRpcClient.Tests\PortaCapena.OdooJsonRpcClient.Tests.csproj", "{974F781B-1B71-4805-8422-490915D28ABB}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PortaCapena.OdooJsonRpcClient.Example", "PortaCapena.OdooJsonRpcClient.Example\PortaCapena.OdooJsonRpcClient.Example.csproj", "{051178E2-51C3-4617-A025-5CB6A5E38F14}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PortaCapena.OdooJsonRpcClient.Shared", "PortaCapena.OdooJsonRpcClient.Shared\PortaCapena.OdooJsonRpcClient.Shared.csproj", "{77298CC5-411E-47AE-BABA-AA687C9EB7EE}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {71528764-0A12-467E-AB4F-21F2E694A32B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {71528764-0A12-467E-AB4F-21F2E694A32B}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {71528764-0A12-467E-AB4F-21F2E694A32B}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {71528764-0A12-467E-AB4F-21F2E694A32B}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {974F781B-1B71-4805-8422-490915D28ABB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {974F781B-1B71-4805-8422-490915D28ABB}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {974F781B-1B71-4805-8422-490915D28ABB}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {974F781B-1B71-4805-8422-490915D28ABB}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {051178E2-51C3-4617-A025-5CB6A5E38F14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {051178E2-51C3-4617-A025-5CB6A5E38F14}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {051178E2-51C3-4617-A025-5CB6A5E38F14}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {051178E2-51C3-4617-A025-5CB6A5E38F14}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {77298CC5-411E-47AE-BABA-AA687C9EB7EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {77298CC5-411E-47AE-BABA-AA687C9EB7EE}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {77298CC5-411E-47AE-BABA-AA687C9EB7EE}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {77298CC5-411E-47AE-BABA-AA687C9EB7EE}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {BB38E419-340B-47FE-AB76-4EB95888435B} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Models/OdooContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace PortaCapena.OdooJsonRpcClient.Models 5 | { 6 | public class OdooContext : Dictionary 7 | { 8 | public string Language 9 | { 10 | get => TryGetValue("lang", out var result) ? result as string : default; 11 | set => SetValue("lang", value); 12 | } 13 | 14 | public string Timezone 15 | { 16 | get => TryGetValue("tz", out var result) ? result as string : default; 17 | set => SetValue("tz", value); 18 | } 19 | 20 | public string Tag 21 | { 22 | get => TryGetValue("tag", out var result) ? result as string : default; 23 | set => SetValue("tag", value); 24 | } 25 | 26 | public string ActiveModel 27 | { 28 | get => TryGetValue("active_model", out var result) ? result as string : default; 29 | set => SetValue("active_model", value); 30 | } 31 | 32 | public bool? DefaultIsCompany 33 | { 34 | get => TryGetValue("default_is_company", out var result) ? result as bool? : default; 35 | set => SetValue("default_is_company", value); 36 | } 37 | 38 | public long? ForceCompany 39 | { 40 | get => TryGetValue("force_company", out var result) ? result as long? : default; 41 | set => SetValue("force_company", value); 42 | } 43 | 44 | public long? AllowedCompanyId 45 | { 46 | get => TryGetValue("allowed_company_ids", out var result) ? (result as long[])?.FirstOrDefault() : default; 47 | set => SetValue("allowed_company_ids", value.HasValue ? new long[] { value.Value } : default); 48 | } 49 | 50 | 51 | public OdooContext() { } 52 | 53 | public OdooContext(string language) 54 | { 55 | Language = language; 56 | } 57 | 58 | public OdooContext(string language, string timezone) : this(language) 59 | { 60 | Timezone = timezone; 61 | } 62 | 63 | public OdooContext(Dictionary dict) 64 | { 65 | if (dict == null) Clear(); 66 | 67 | foreach (var keyValuePair in dict) 68 | SetValue(keyValuePair.Key, keyValuePair.Value); 69 | } 70 | 71 | public OdooContext(params OdooContext[] dicts) 72 | { 73 | if (dicts == null) Clear(); 74 | 75 | foreach (var dict in dicts) 76 | foreach (var keyValuePair in dict) 77 | SetValue(keyValuePair.Key, keyValuePair.Value); 78 | } 79 | 80 | 81 | private void SetValue(string key, object value) 82 | { 83 | if (value == null && ContainsKey(key)) 84 | Remove(key); 85 | else 86 | this[key] = value; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Shared/Models/ResPartnerBankOdooModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Converters; 5 | using PortaCapena.OdooJsonRpcClient.Attributes; 6 | using PortaCapena.OdooJsonRpcClient.Converters; 7 | using PortaCapena.OdooJsonRpcClient.Models; 8 | 9 | namespace PortaCapena.OdooJsonRpcClient.Shared.Models 10 | { 11 | [OdooTableName("res.partner.bank")] 12 | [JsonConverter(typeof(OdooModelConverter))] 13 | public class ResPartnerBankOdooModel : IOdooModel 14 | { 15 | 16 | [JsonProperty("active")] 17 | public bool? Active { get; set; } 18 | 19 | [JsonProperty("acc_type")] 20 | public TypeResPartnerBankOdooEnum? AccType { get; set; } 21 | 22 | // required 23 | [JsonProperty("acc_number")] 24 | public string AccNumber { get; set; } 25 | 26 | [JsonProperty("sanitized_acc_number")] 27 | public string SanitizedAccNumber { get; set; } 28 | 29 | [JsonProperty("acc_holder_name")] 30 | public string AccHolderName { get; set; } 31 | 32 | // res.partner 33 | // required 34 | [JsonProperty("partner_id")] 35 | public long PartnerId { get; set; } 36 | 37 | // res.bank 38 | [JsonProperty("bank_id")] 39 | public long? BankId { get; set; } 40 | 41 | [JsonProperty("bank_name")] 42 | public string BankName { get; set; } 43 | 44 | [JsonProperty("bank_bic")] 45 | public string BankBic { get; set; } 46 | 47 | [JsonProperty("sequence")] 48 | public int? Sequence { get; set; } 49 | 50 | // res.currency 51 | [JsonProperty("currency_id")] 52 | public long? CurrencyId { get; set; } 53 | 54 | // res.company 55 | [JsonProperty("company_id")] 56 | public long? CompanyId { get; set; } 57 | 58 | // account.journal 59 | [JsonProperty("journal_id")] 60 | public long[] JournalId { get; set; } 61 | 62 | [JsonProperty("id")] 63 | public long Id { get; set; } 64 | 65 | [JsonProperty("display_name")] 66 | public string DisplayName { get; set; } 67 | 68 | // res.users 69 | [JsonProperty("create_uid")] 70 | public long? CreateUid { get; set; } 71 | 72 | [JsonProperty("create_date")] 73 | public DateTime? CreateDate { get; set; } 74 | 75 | // res.users 76 | [JsonProperty("write_uid")] 77 | public long? WriteUid { get; set; } 78 | 79 | [JsonProperty("write_date")] 80 | public DateTime? WriteDate { get; set; } 81 | 82 | [JsonProperty("__last_update")] 83 | public DateTime? LastUpdate { get; set; } 84 | } 85 | 86 | 87 | // Bank account type: Normal or IBAN. Inferred from the bank account number. 88 | [JsonConverter(typeof(StringEnumConverter))] 89 | public enum TypeResPartnerBankOdooEnum 90 | { 91 | [EnumMember(Value = "bank")] 92 | Normal = 1, 93 | 94 | [EnumMember(Value = "iban")] 95 | IBAN = 2, 96 | } 97 | 98 | } -------------------------------------------------------------------------------- /.github/workflows/pr_build.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | push: 5 | branches: [ master, release ] 6 | pull_request: 7 | branches: [ master, release ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | env: 14 | config: 'Release' 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Setup .NET 19 | uses: actions/setup-dotnet@v4 20 | with: 21 | dotnet-version: 6.0.x 22 | 23 | - name: Restore dependencies 24 | run: dotnet restore 25 | 26 | - name: Build PR 27 | run: dotnet build --configuration $config --no-restore 28 | 29 | # set pr number, if it's a pr build (if pr) 30 | - name: set pr build number 31 | id: PRNUMBER 32 | if: ${{ github.event_name == 'pull_request' }} 33 | uses: kkak10/pr-number-action@v1.3 34 | 35 | # set report file and title (if pr) 36 | - name: Set Test Title 37 | run: | 38 | if ${{ github.event_name == 'pull_request' }} 39 | then 40 | echo "title=Test Run for PR #${{steps.PRNUMBER.outputs.pr}} (${{github.run_number}})" >> $GITHUB_ENV 41 | echo "file_name=TestReport.${{steps.PRNUMBER.outputs.pr}}.${{github.run_number}}.md" >> $GITHUB_ENV 42 | else 43 | echo "title=Test Run ${{github.run_number}}" >> $GITHUB_ENV 44 | echo "file_name=TestReport.${{github.run_number}}.md" >> $GITHUB_ENV 45 | fi 46 | 47 | - name: Test 48 | run: dotnet test PortaCapena.OdooJsonRpcClient.Tests/PortaCapena.OdooJsonRpcClient.Tests.csproj --configuration $config --no-build --no-restore --logger:"liquid.md;LogFileName=${{github.workspace}}/${{env.file_name}};Title=${{env.title}};" --collect:"XPlat Code Coverage" --results-directory ./coverage 49 | 50 | # upload report as build artifact 51 | - name: Upload a Build Artifact 52 | uses: actions/upload-artifact@v4 53 | if: ${{always()}} 54 | with: 55 | name: 'Test Run' 56 | path: ${{github.workspace}}/${{env.file_name}} 57 | 58 | # add report as PR comment (if PR) 59 | - name: Comment on PR 60 | uses: peter-evans/create-or-update-comment@v4 61 | if: github.event_name == 'pull_request' 62 | with: 63 | token: ${{ secrets.GITHUB_TOKEN }} 64 | issue-number: ${{ github.event.pull_request.number }} 65 | body-path: ${{ env.file_name }} 66 | edit-mode: replace 67 | 68 | - name: Code Coverage Report 69 | uses: irongut/CodeCoverageSummary@v1.3.0 70 | with: 71 | filename: coverage/**/coverage.cobertura.xml 72 | badge: true 73 | fail_below_min: true 74 | format: markdown 75 | hide_branch_rate: false 76 | hide_complexity: true 77 | indicators: true 78 | output: both 79 | thresholds: '5 60' 80 | 81 | - name: Add Coverage Comment 82 | uses: marocchino/sticky-pull-request-comment@v2 83 | if: github.event_name == 'pull_request' 84 | with: 85 | recreate: true 86 | path: code-coverage-results.md 87 | 88 | -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Shared/Models/AccountAccountTypeOdooModel .cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Converters; 5 | using PortaCapena.OdooJsonRpcClient.Attributes; 6 | using PortaCapena.OdooJsonRpcClient.Converters; 7 | using PortaCapena.OdooJsonRpcClient.Models; 8 | 9 | namespace PortaCapena.OdooJsonRpcClient.Shared.Models 10 | { 11 | [OdooTableName("account.account.type")] 12 | [JsonConverter(typeof(OdooModelConverter))] 13 | public class AccountAccountTypeOdooModel : IOdooModel 14 | { 15 | 16 | // required 17 | [JsonProperty("name")] 18 | public string Name { get; set; } 19 | 20 | [JsonProperty("include_initial_balance")] 21 | public bool? IncludeInitialBalance { get; set; } 22 | 23 | // required 24 | [JsonProperty("type")] 25 | public TypeAccountAccountTypeOdooEnum Type { get; set; } 26 | 27 | // required 28 | [JsonProperty("internal_group")] 29 | public InternalGroupAccountAccountTypeOdooEnum InternalGroup { get; set; } 30 | 31 | [JsonProperty("note")] 32 | public string Note { get; set; } 33 | 34 | [JsonProperty("id")] 35 | public long Id { get; set; } 36 | 37 | [JsonProperty("display_name")] 38 | public string DisplayName { get; set; } 39 | 40 | // res.users 41 | [JsonProperty("create_uid")] 42 | public long? CreateUid { get; set; } 43 | 44 | [JsonProperty("create_date")] 45 | public DateTime? CreateDate { get; set; } 46 | 47 | // res.users 48 | [JsonProperty("write_uid")] 49 | public long? WriteUid { get; set; } 50 | 51 | [JsonProperty("write_date")] 52 | public DateTime? WriteDate { get; set; } 53 | 54 | [JsonProperty("__last_update")] 55 | public DateTime? LastUpdate { get; set; } 56 | } 57 | 58 | 59 | // The 'Internal Type' is used for features available on different types of accounts: liquidity type is for cash or bank accounts, payable/receivable is for vendor/customer accounts. 60 | [JsonConverter(typeof(StringEnumConverter))] 61 | public enum TypeAccountAccountTypeOdooEnum 62 | { 63 | [EnumMember(Value = "other")] 64 | Regular = 1, 65 | 66 | [EnumMember(Value = "receivable")] 67 | Receivable = 2, 68 | 69 | [EnumMember(Value = "payable")] 70 | Payable = 3, 71 | 72 | [EnumMember(Value = "liquidity")] 73 | Liquidity = 4, 74 | } 75 | 76 | 77 | // The 'Internal Group' is used to filter accounts based on the internal group set on the account type. 78 | [JsonConverter(typeof(StringEnumConverter))] 79 | public enum InternalGroupAccountAccountTypeOdooEnum 80 | { 81 | [EnumMember(Value = "equity")] 82 | Equity = 1, 83 | 84 | [EnumMember(Value = "asset")] 85 | Asset = 2, 86 | 87 | [EnumMember(Value = "liability")] 88 | Liability = 3, 89 | 90 | [EnumMember(Value = "income")] 91 | Income = 4, 92 | 93 | [EnumMember(Value = "expense")] 94 | Expense = 5, 95 | 96 | [EnumMember(Value = "off_balance")] 97 | OffBalance = 6, 98 | } 99 | 100 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Shared/Models/ResCountryOdooModel .cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Converters; 5 | using PortaCapena.OdooJsonRpcClient.Attributes; 6 | using PortaCapena.OdooJsonRpcClient.Converters; 7 | using PortaCapena.OdooJsonRpcClient.Models; 8 | 9 | namespace PortaCapena.OdooJsonRpcClient.Shared.Models 10 | { 11 | [OdooTableName("res.country")] 12 | [JsonConverter(typeof(OdooModelConverter))] 13 | public class ResCountryOdooModel : IOdooModel 14 | { 15 | 16 | // required 17 | [JsonProperty("name")] 18 | public string Name { get; set; } 19 | 20 | [JsonProperty("code")] 21 | public string Code { get; set; } 22 | 23 | [JsonProperty("address_format")] 24 | public string AddressFormat { get; set; } 25 | 26 | // ir.ui.view 27 | [JsonProperty("address_view_id")] 28 | public long? AddressViewId { get; set; } 29 | 30 | // res.currency 31 | [JsonProperty("currency_id")] 32 | public long? CurrencyId { get; set; } 33 | 34 | [JsonProperty("image_url")] 35 | public string ImageUrl { get; set; } 36 | 37 | [JsonProperty("phone_code")] 38 | public int? PhoneCode { get; set; } 39 | 40 | // res.country.group 41 | [JsonProperty("country_group_ids")] 42 | public long[] CountryGroupIds { get; set; } 43 | 44 | // res.country.state 45 | [JsonProperty("state_ids")] 46 | public long[] StateIds { get; set; } 47 | 48 | [JsonProperty("name_position")] 49 | public CustomerNamePositionResCountryOdooEnum? NamePosition { get; set; } 50 | 51 | [JsonProperty("vat_label")] 52 | public string VatLabel { get; set; } 53 | 54 | [JsonProperty("state_required")] 55 | public bool? StateRequired { get; set; } 56 | 57 | [JsonProperty("zip_required")] 58 | public bool? ZipRequired { get; set; } 59 | 60 | // required 61 | [JsonProperty("street_format")] 62 | public string StreetFormat { get; set; } 63 | 64 | [JsonProperty("id")] 65 | public long Id { get; set; } 66 | 67 | [JsonProperty("display_name")] 68 | public string DisplayName { get; set; } 69 | 70 | // res.users 71 | [JsonProperty("create_uid")] 72 | public long? CreateUid { get; set; } 73 | 74 | [JsonProperty("create_date")] 75 | public DateTime? CreateDate { get; set; } 76 | 77 | // res.users 78 | [JsonProperty("write_uid")] 79 | public long? WriteUid { get; set; } 80 | 81 | [JsonProperty("write_date")] 82 | public DateTime? WriteDate { get; set; } 83 | 84 | [JsonProperty("__last_update")] 85 | public DateTime? LastUpdate { get; set; } 86 | 87 | [JsonProperty("x_studio_is_in_eu")] 88 | public bool? XStudioIsInEu { get; set; } 89 | } 90 | 91 | 92 | // Determines where the customer/company name should be placed, i.e. after or before the address. 93 | [JsonConverter(typeof(StringEnumConverter))] 94 | public enum CustomerNamePositionResCountryOdooEnum 95 | { 96 | [EnumMember(Value = "before")] 97 | BeforeAddress = 1, 98 | 99 | [EnumMember(Value = "after")] 100 | AfterAddress = 2, 101 | } 102 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/OdooRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using PortaCapena.OdooJsonRpcClient.Extensions; 4 | using PortaCapena.OdooJsonRpcClient.Models; 5 | using PortaCapena.OdooJsonRpcClient.Request; 6 | using PortaCapena.OdooJsonRpcClient.Result; 7 | 8 | namespace PortaCapena.OdooJsonRpcClient 9 | { 10 | public class OdooRepository : IOdooRepository where T : IOdooModel, new() 11 | { 12 | public string TableName { get; } 13 | protected readonly OdooClient OdooClient; 14 | public OdooConfig Config => OdooClient.Config; 15 | 16 | public OdooQueryBuilder Query() => new OdooQueryBuilder(OdooClient); 17 | 18 | public OdooRepository(OdooConfig config) 19 | { 20 | OdooClient = new OdooClient(config); 21 | TableName = OdooExtensions.GetOdooTableName(); 22 | } 23 | 24 | public async Task> CreateAsync(IOdooCreateModel model, OdooContext context = null) 25 | { 26 | return await OdooClient.CreateAsync(model, context); 27 | } 28 | public async Task> CreateAsync(OdooDictionaryModel model, OdooContext context = null) 29 | { 30 | return await OdooClient.CreateAsync(model, context); 31 | } 32 | public async Task> CreateAsync(IEnumerable models, OdooContext context = null) 33 | { 34 | return await OdooClient.CreateAsync(models, context); 35 | } 36 | public async Task> CreateAsync(IEnumerable models, OdooContext context = null) 37 | { 38 | return await OdooClient.CreateAsync(models, context); 39 | } 40 | 41 | public async Task> UpdateAsync(IOdooCreateModel model, long id, OdooContext context = null) 42 | { 43 | return await OdooClient.UpdateAsync(model, id, context); 44 | } 45 | public async Task> UpdateRangeAsync(IOdooCreateModel model, long[] ids, OdooContext context = null) 46 | { 47 | return await OdooClient.UpdateRangeAsync(model, ids, context); 48 | } 49 | public async Task> UpdateAsync(OdooDictionaryModel model, long id, OdooContext context = null) 50 | { 51 | return await OdooClient.UpdateAsync(model, id, context); 52 | } 53 | public async Task> UpdateRangeAsync(OdooDictionaryModel model, long[] ids, OdooContext context = null) 54 | { 55 | model.TableName = TableName; 56 | return await OdooClient.UpdateRangeAsync(model, ids, context); 57 | } 58 | 59 | public async Task> DeleteAsync(T model, OdooContext context = null) 60 | { 61 | return await OdooClient.DeleteAsync(model, context); 62 | } 63 | public async Task> DeleteAsync(long id, OdooContext context = null) 64 | { 65 | return await OdooClient.DeleteAsync(TableName, id, context); 66 | } 67 | public async Task> DeleteRangeAsync(T[] models, OdooContext context = null) 68 | { 69 | return await OdooClient.DeleteRangeAsync(models as IOdooModel[], context); 70 | } 71 | public async Task> ActionAsync(string action, object args, OdooContext context = null) 72 | { 73 | return await OdooClient.ActionAsync(TableName, action, args, context); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Shared/Models/StockPickingTypeOdooModel.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using PortaCapena.OdooJsonRpcClient.Attributes; 3 | using PortaCapena.OdooJsonRpcClient.Converters; 4 | using PortaCapena.OdooJsonRpcClient.Models; 5 | using System; 6 | 7 | namespace PortaCapena.OdooJsonRpcClient.Shared.Models 8 | { 9 | [OdooTableName("stock.picking.type")] 10 | [JsonConverter(typeof(OdooModelConverter))] 11 | public class StockPickingTypeOdooModel : IOdooModel 12 | { 13 | 14 | // required 15 | [JsonProperty("name")] 16 | public string Name { get; set; } 17 | 18 | [JsonProperty("color")] 19 | public int? Color { get; set; } 20 | 21 | [JsonProperty("sequence")] 22 | public int? Sequence { get; set; } 23 | 24 | // ir.sequence 25 | [JsonProperty("sequence_id")] 26 | public long? SequenceId { get; set; } 27 | 28 | // required 29 | [JsonProperty("sequence_code")] 30 | public string SequenceCode { get; set; } 31 | 32 | // stock.location 33 | [JsonProperty("default_location_src_id")] 34 | public long? DefaultLocationSrcId { get; set; } 35 | 36 | // stock.location 37 | [JsonProperty("default_location_dest_id")] 38 | public long? DefaultLocationDestId { get; set; } 39 | 40 | // required 41 | [JsonProperty("code")] 42 | public string Code { get; set; } 43 | 44 | // stock.picking.type 45 | [JsonProperty("return_picking_type_id")] 46 | public long? ReturnPickingTypeId { get; set; } 47 | 48 | [JsonProperty("show_entire_packs")] 49 | public bool? ShowEntirePacks { get; set; } 50 | 51 | // stock.warehouse 52 | [JsonProperty("warehouse_id")] 53 | public long? WarehouseId { get; set; } 54 | 55 | [JsonProperty("active")] 56 | public bool? Active { get; set; } 57 | 58 | [JsonProperty("use_create_lots")] 59 | public bool? UseCreateLots { get; set; } 60 | 61 | [JsonProperty("use_existing_lots")] 62 | public bool? UseExistingLots { get; set; } 63 | 64 | [JsonProperty("show_operations")] 65 | public bool? ShowOperations { get; set; } 66 | 67 | [JsonProperty("show_reserved")] 68 | public bool? ShowReserved { get; set; } 69 | 70 | [JsonProperty("count_picking_draft")] 71 | public int? CountPickingDraft { get; set; } 72 | 73 | [JsonProperty("count_picking_ready")] 74 | public int? CountPickingReady { get; set; } 75 | 76 | [JsonProperty("count_picking")] 77 | public int? CountPicking { get; set; } 78 | 79 | [JsonProperty("count_picking_waiting")] 80 | public int? CountPickingWaiting { get; set; } 81 | 82 | [JsonProperty("count_picking_late")] 83 | public int? CountPickingLate { get; set; } 84 | 85 | [JsonProperty("count_picking_backorders")] 86 | public int? CountPickingBackorders { get; set; } 87 | 88 | [JsonProperty("rate_picking_late")] 89 | public int? RatePickingLate { get; set; } 90 | 91 | [JsonProperty("rate_picking_backorders")] 92 | public int? RatePickingBackorders { get; set; } 93 | 94 | [JsonProperty("barcode")] 95 | public string Barcode { get; set; } 96 | 97 | // res.company 98 | // required 99 | [JsonProperty("company_id")] 100 | public long CompanyId { get; set; } 101 | 102 | [JsonProperty("id")] 103 | public long Id { get; set; } 104 | 105 | [JsonProperty("display_name")] 106 | public string DisplayName { get; set; } 107 | 108 | // res.users 109 | [JsonProperty("create_uid")] 110 | public long? CreateUid { get; set; } 111 | 112 | [JsonProperty("create_date")] 113 | public DateTime? CreateDate { get; set; } 114 | 115 | // res.users 116 | [JsonProperty("write_uid")] 117 | public long? WriteUid { get; set; } 118 | 119 | [JsonProperty("write_date")] 120 | public DateTime? WriteDate { get; set; } 121 | 122 | [JsonProperty("__last_update")] 123 | public DateTime? LastUpdate { get; set; } 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Example/CompanyOdooRepositoryTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using FluentAssertions; 3 | using PortaCapena.OdooJsonRpcClient.Consts; 4 | using PortaCapena.OdooJsonRpcClient.Extensions; 5 | using PortaCapena.OdooJsonRpcClient.Request; 6 | using PortaCapena.OdooJsonRpcClient.Shared; 7 | using PortaCapena.OdooJsonRpcClient.Shared.Models; 8 | using Xunit; 9 | 10 | namespace PortaCapena.OdooJsonRpcClient.Example 11 | { 12 | public class CompanyOdooRepositoryTests : RequestTestBase 13 | { 14 | private class CompanyRepository : OdooRepository 15 | { 16 | public CompanyRepository() : base(TestConfig) { } 17 | } 18 | 19 | [Fact] 20 | public async Task Get_All() 21 | { 22 | var repo = new CompanyRepository(); 23 | 24 | var result = await repo.Query().ToListAsync(); 25 | 26 | result.Error.Should().BeNull(); 27 | result.Succeed.Should().BeTrue(); 28 | result.Value.Should().NotBeNull().And.NotBeEmpty(); 29 | } 30 | 31 | [Fact] 32 | public async Task Get_With_filter() 33 | { 34 | var repo = new CompanyRepository(); 35 | 36 | var filter = OdooFilter.Create() 37 | .Or() 38 | .EqualTo("name", "My Company (San Francisco)") 39 | .EqualTo("name", "PL Company"); 40 | 41 | var result = await repo.Query() 42 | .Where(filter) 43 | .ToListAsync(); 44 | 45 | result.Error.Should().BeNull(); 46 | result.Succeed.Should().BeTrue(); 47 | result.Value.Should().NotBeNull().And.NotBeEmpty(); 48 | } 49 | 50 | [Fact] 51 | public async Task Get_With_filter_of_t() 52 | { 53 | var repo = new CompanyRepository(); 54 | 55 | var filter = OdooFilter.Create() 56 | .Or() 57 | .EqualTo(x => x.Name, "My Company (San Francisco)") 58 | .EqualTo(x => x.Name, "PL Company"); 59 | 60 | var result = await repo.Query() 61 | .Where(filter) 62 | .ToListAsync(); 63 | 64 | result.Error.Should().BeNull(); 65 | result.Succeed.Should().BeTrue(); 66 | result.Value.Should().NotBeNull().And.NotBeEmpty(); 67 | } 68 | 69 | [Fact] 70 | public async Task Get_By_Id() 71 | { 72 | var repo = new CompanyRepository(); 73 | 74 | var result = await repo.Query().ById(1).FirstOrDefaultAsync(); 75 | 76 | result.Error.Should().BeNull(); 77 | result.Succeed.Should().BeTrue(); 78 | result.Value.Should().NotBeNull(); 79 | } 80 | 81 | [Fact] 82 | public async Task Get_By_Ids() 83 | { 84 | var repo = new CompanyRepository(); 85 | 86 | var result = await repo.Query().ByIds(1, 2).ToListAsync(); 87 | 88 | result.Error.Should().BeNull(); 89 | result.Succeed.Should().BeTrue(); 90 | result.Value.Should().NotBeNull().And.NotBeEmpty(); 91 | result.Value.Length.Should().Be(2); 92 | } 93 | 94 | [Fact] 95 | public async Task Get_By_Name() 96 | { 97 | var repo = new CompanyRepository(); 98 | 99 | var result = await repo.Query().Where(x => x.Name, OdooOperator.EqualsTo, "My Company (San Francisco)").FirstOrDefaultAsync(); 100 | 101 | result.Error.Should().BeNull(); 102 | result.Succeed.Should().BeTrue(); 103 | result.Value.Should().NotBeNull(); 104 | } 105 | 106 | [Fact] 107 | public async Task Get_By_Country() 108 | { 109 | var repo = new CompanyRepository(); 110 | 111 | var result = await repo.Query().Where(x => x.CountryCode, OdooOperator.EqualsTo, "BE").ToListAsync(); 112 | 113 | result.Error.Should().BeNull(); 114 | result.Succeed.Should().BeTrue(); 115 | result.Value.Should().NotBeNull().And.NotBeEmpty(); 116 | } 117 | 118 | [Fact] 119 | public async Task Get_By_Country_name() 120 | { 121 | var repo = new CompanyRepository(); 122 | 123 | var result = await repo.Query().Where(x => x.CountryId, x => x.Name, OdooOperator.EqualsTo, "Belgium").ToListAsync(); 124 | 125 | result.Error.Should().BeNull(); 126 | result.Succeed.Should().BeTrue(); 127 | result.Value.Should().NotBeNull().And.NotBeEmpty(); 128 | } 129 | } 130 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Models/OdooPropertyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using PortaCapena.OdooJsonRpcClient.Converters; 4 | 5 | namespace PortaCapena.OdooJsonRpcClient.Models 6 | { 7 | [JsonConverter(typeof(OdooModelConverter))] 8 | public class OdooPropertyInfo : IOdooAtributtesModel 9 | { 10 | [JsonProperty("type")] 11 | public string Type { get; set; } 12 | 13 | [JsonProperty("name")] 14 | public string Name { get; set; } 15 | 16 | [JsonProperty("change_default")] 17 | public bool ChangeDefault { get; set; } 18 | 19 | [JsonProperty("company_dependent")] 20 | public bool CompanyDependent { get; set; } 21 | 22 | [JsonProperty("depends")] 23 | public string[] Depends { get; set; } 24 | 25 | [JsonProperty("manual")] 26 | public bool Manual { get; set; } 27 | 28 | [JsonProperty("readonly")] 29 | public bool Readonly { get; set; } 30 | 31 | [JsonProperty("required")] 32 | public bool ResultRequired { get; set; } 33 | 34 | [JsonProperty("searchable")] 35 | public bool Searchable { get; set; } 36 | 37 | [JsonProperty("sortable")] 38 | public bool Sortable { get; set; } 39 | 40 | [JsonProperty("store")] 41 | public bool Store { get; set; } 42 | 43 | [JsonProperty("string")] 44 | public string String { get; set; } 45 | 46 | [JsonProperty("help")] 47 | public string Help { get; set; } 48 | 49 | [JsonProperty("groups")] 50 | public string Groups { get; set; } 51 | 52 | [JsonProperty("selection")] 53 | public string[][] Selection { get; set; } 54 | 55 | [JsonProperty("translate")] 56 | public bool? Translate { get; set; } 57 | 58 | [JsonProperty("trim")] 59 | public bool? Trim { get; set; } 60 | 61 | 62 | [JsonProperty("relation")] 63 | public string Relation { get; set; } 64 | 65 | [JsonProperty("relation_field")] 66 | public string RelationField { get; set; } 67 | 68 | [JsonProperty("related")] 69 | public string Related { get; set; } 70 | 71 | [JsonProperty("group_operator")] 72 | public string GroupOperator { get; set; } 73 | 74 | [JsonProperty("digits")] 75 | public long[] Digits { get; set; } 76 | 77 | [JsonProperty("attachment")] 78 | public bool? Attachment { get; set; } 79 | 80 | [JsonIgnore] 81 | public OdooValueTypeEnum PropertyValueType => ToOdooValueTypeEnum(this.Type); 82 | 83 | public static OdooValueTypeEnum ToOdooValueTypeEnum(string value) 84 | { 85 | switch (value) 86 | { 87 | case "binary": 88 | return OdooValueTypeEnum.Binary; 89 | case "boolean": 90 | return OdooValueTypeEnum.Boolean; 91 | case "char": 92 | case "json": 93 | return OdooValueTypeEnum.Char; 94 | case "date": 95 | return OdooValueTypeEnum.Date; 96 | case "datetime": 97 | return OdooValueTypeEnum.Datetime; 98 | case "float": 99 | return OdooValueTypeEnum.Float; 100 | case "integer": 101 | return OdooValueTypeEnum.Integer; 102 | case "many2many": 103 | return OdooValueTypeEnum.Many2Many; 104 | case "many2one": 105 | return OdooValueTypeEnum.Many2One; 106 | case "many2one_reference": 107 | return OdooValueTypeEnum.Many2OneReference; 108 | case "one2many": 109 | return OdooValueTypeEnum.One2Many; 110 | case "selection": 111 | return OdooValueTypeEnum.Selection; 112 | case "text": 113 | return OdooValueTypeEnum.Text; 114 | case "html": 115 | return OdooValueTypeEnum.Html; 116 | case "related": 117 | return OdooValueTypeEnum.Reference; 118 | case "one2one": 119 | return OdooValueTypeEnum.One2One; 120 | case "monetary": 121 | return OdooValueTypeEnum.Monetary; 122 | case "properties": 123 | return OdooValueTypeEnum.Properties; 124 | case "properties_definition": 125 | return OdooValueTypeEnum.PropertiesDefinition; 126 | } 127 | throw new Exception($"Cannot unmarshal Enum '{nameof(OdooValueTypeEnum)}' - '{value}'"); 128 | } 129 | } 130 | 131 | 132 | public enum OdooValueTypeEnum 133 | { 134 | Binary, 135 | Boolean, 136 | Char, 137 | Date, 138 | Datetime, 139 | Float, 140 | Integer, 141 | 142 | Monetary, 143 | 144 | Many2Many, 145 | Many2One, 146 | Many2OneReference, 147 | One2Many, 148 | One2One, 149 | Reference, 150 | 151 | Selection, 152 | Text, 153 | Html, 154 | PropertiesDefinition, 155 | Properties 156 | }; 157 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Shared/Models/CouponProgramOdooDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using PortaCapena.OdooJsonRpcClient.Attributes; 4 | using PortaCapena.OdooJsonRpcClient.Converters; 5 | using PortaCapena.OdooJsonRpcClient.Models; 6 | 7 | namespace PortaCapena.OdooJsonRpcClient.Shared.Models 8 | { 9 | [OdooTableName("coupon.program")] 10 | [JsonConverter(typeof(OdooModelConverter))] 11 | public class CouponProgramOdooDto : IOdooModel 12 | { 13 | 14 | [JsonProperty("name")] 15 | public string Name { get; set; } 16 | 17 | [JsonProperty("active")] 18 | public bool? Active { get; set; } 19 | 20 | // coupon.rule 21 | [JsonProperty("rule_id")] 22 | public long RuleId { get; set; } 23 | 24 | // coupon.reward 25 | [JsonProperty("reward_id")] 26 | public long RewardId { get; set; } 27 | 28 | [JsonProperty("sequence")] 29 | public int? Sequence { get; set; } 30 | 31 | [JsonProperty("maximum_use_number")] 32 | public int? MaximumUseNumber { get; set; } 33 | 34 | [JsonProperty("program_type")] 35 | public string ProgramType { get; set; } 36 | 37 | [JsonProperty("promo_code_usage")] 38 | public string PromoCodeUsage { get; set; } 39 | 40 | [JsonProperty("promo_code")] 41 | public string PromoCode { get; set; } 42 | 43 | [JsonProperty("promo_applicability")] 44 | public string PromoApplicability { get; set; } 45 | 46 | // coupon.coupon 47 | [JsonProperty("coupon_ids")] 48 | public long[] CouponIds { get; set; } 49 | 50 | [JsonProperty("coupon_count")] 51 | public int? CouponCount { get; set; } 52 | 53 | // res.company 54 | [JsonProperty("company_id")] 55 | public long? CompanyId { get; set; } 56 | 57 | // res.currency 58 | [JsonProperty("currency_id")] 59 | public long? CurrencyId { get; set; } 60 | 61 | [JsonProperty("validity_duration")] 62 | public int? ValidityDuration { get; set; } 63 | 64 | [JsonProperty("order_count")] 65 | public int? OrderCount { get; set; } 66 | 67 | // website 68 | [JsonProperty("website_id")] 69 | public long? WebsiteId { get; set; } 70 | 71 | [JsonProperty("id")] 72 | public long Id { get; set; } 73 | 74 | [JsonProperty("display_name")] 75 | public string DisplayName { get; set; } 76 | 77 | // res.users 78 | [JsonProperty("create_uid")] 79 | public long? CreateUid { get; set; } 80 | 81 | [JsonProperty("create_date")] 82 | public DateTime? CreateDate { get; set; } 83 | 84 | // res.users 85 | [JsonProperty("write_uid")] 86 | public long? WriteUid { get; set; } 87 | 88 | [JsonProperty("write_date")] 89 | public DateTime? WriteDate { get; set; } 90 | 91 | [JsonProperty("__last_update")] 92 | public DateTime? LastUpdate { get; set; } 93 | 94 | [JsonProperty("rule_date_from")] 95 | public DateTime? RuleDateFrom { get; set; } 96 | 97 | [JsonProperty("rule_date_to")] 98 | public DateTime? RuleDateTo { get; set; } 99 | 100 | [JsonProperty("rule_partners_domain")] 101 | public string RulePartnersDomain { get; set; } 102 | 103 | [JsonProperty("rule_products_domain")] 104 | public string RuleProductsDomain { get; set; } 105 | 106 | [JsonProperty("rule_min_quantity")] 107 | public int? RuleMinQuantity { get; set; } 108 | 109 | [JsonProperty("rule_minimum_amount")] 110 | public double? RuleMinimumAmount { get; set; } 111 | 112 | [JsonProperty("rule_minimum_amount_tax_inclusion")] 113 | public string RuleMinimumAmountTaxInclusion { get; set; } 114 | 115 | [JsonProperty("reward_description")] 116 | public string RewardDescription { get; set; } 117 | 118 | // product.product 119 | [JsonProperty("reward_product_id")] 120 | public long? RewardProductId { get; set; } 121 | 122 | [JsonProperty("reward_product_quantity")] 123 | public int? RewardProductQuantity { get; set; } 124 | 125 | [JsonProperty("discount_type")] 126 | public string DiscountType { get; set; } 127 | 128 | [JsonProperty("discount_percentage")] 129 | public double? DiscountPercentage { get; set; } 130 | 131 | [JsonProperty("discount_apply_on")] 132 | public string DiscountApplyOn { get; set; } 133 | 134 | // product.product 135 | [JsonProperty("discount_specific_product_ids")] 136 | public long[] DiscountSpecificProductIds { get; set; } 137 | 138 | [JsonProperty("discount_max_amount")] 139 | public double? DiscountMaxAmount { get; set; } 140 | 141 | [JsonProperty("discount_fixed_amount")] 142 | public double? DiscountFixedAmount { get; set; } 143 | 144 | // uom.uom 145 | [JsonProperty("reward_product_uom_id")] 146 | public long? RewardProductUomId { get; set; } 147 | 148 | // product.product 149 | [JsonProperty("discount_line_product_id")] 150 | public long? DiscountLineProductId { get; set; } 151 | 152 | [JsonProperty("reward_type")] 153 | public string RewardType { get; set; } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Example/ProductTemplateOdooModelRepositoryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using FluentAssertions; 4 | using PortaCapena.OdooJsonRpcClient.Consts; 5 | using PortaCapena.OdooJsonRpcClient.Extensions; 6 | using PortaCapena.OdooJsonRpcClient.Models; 7 | using PortaCapena.OdooJsonRpcClient.Request; 8 | using PortaCapena.OdooJsonRpcClient.Shared; 9 | using PortaCapena.OdooJsonRpcClient.Shared.Models; 10 | using Xunit; 11 | 12 | namespace PortaCapena.OdooJsonRpcClient.Example 13 | { 14 | public class ProductTemplateOdooModelRepositoryTests : RequestTestBase 15 | { 16 | private class ProductTemplateRepository : OdooRepository 17 | { 18 | public ProductTemplateRepository() : base(TestConfig) { } 19 | } 20 | 21 | [Fact] 22 | public async Task Get_All() 23 | { 24 | var repo = new ProductTemplateRepository(); 25 | 26 | var result = await repo.Query().ToListAsync(); 27 | 28 | result.Error.Should().BeNull(); 29 | result.Succeed.Should().BeTrue(); 30 | result.Value.Should().NotBeNull().And.NotBeEmpty(); 31 | } 32 | 33 | [Fact] 34 | public async Task Get_By_Id() 35 | { 36 | var repo = new ProductTemplateRepository(); 37 | 38 | var result = await repo.Query().ById(1).FirstOrDefaultAsync(); 39 | 40 | result.Error.Should().BeNull(); 41 | result.Succeed.Should().BeTrue(); 42 | result.Value.Should().NotBeNull(); 43 | } 44 | 45 | [Fact] 46 | public async Task Get_By_Ids() 47 | { 48 | var repo = new ProductTemplateRepository(); 49 | 50 | var result = await repo.Query().ByIds(1, 2).ToListAsync(); 51 | 52 | result.Error.Should().BeNull(); 53 | result.Succeed.Should().BeTrue(); 54 | result.Value.Should().NotBeNull().And.NotBeEmpty(); 55 | result.Value.Length.Should().Be(2); 56 | } 57 | 58 | 59 | 60 | [Fact] 61 | public async Task Get_name_in_nl() 62 | { 63 | var repo = new ProductTemplateRepository(); 64 | 65 | var result = await repo.Query().ById(23).FirstOrDefaultAsync(); 66 | 67 | result.Error.Should().BeNull(); 68 | result.Succeed.Should().BeTrue(); 69 | result.Value.Should().NotBeNull(); 70 | result.Value.Name.Should().Be("Acoustic Bloc Screens"); 71 | result.Value.Name.Should().NotBe("Akoestische blokschermen"); 72 | 73 | 74 | repo.Config.Context.Language = "nl_NL"; 75 | 76 | var resultInNL = await repo.Query().ById(result.Value.Id).Select(x => new { x.Name}).FirstOrDefaultAsync(); 77 | 78 | resultInNL.Error.Should().BeNull(); 79 | resultInNL.Succeed.Should().BeTrue(); 80 | resultInNL.Value.Should().NotBeNull(); 81 | 82 | resultInNL.Value.Name.Should().NotBe("Acoustic Bloc Screens"); 83 | resultInNL.Value.Name.Should().Be("Akoestische blokschermen"); 84 | 85 | repo.Config.Context.Language = "en_US"; 86 | 87 | var resultInEn = await repo.Query().ById(result.Value.Id).FirstOrDefaultAsync(); 88 | 89 | resultInEn.Error.Should().BeNull(); 90 | resultInEn.Succeed.Should().BeTrue(); 91 | resultInEn.Value.Should().NotBeNull(); 92 | 93 | resultInEn.Value.Name.Should().Be("Acoustic Bloc Screens"); 94 | resultInEn.Value.Name.Should().NotBe("Akoestische blokschermen"); 95 | } 96 | 97 | //[Fact] 98 | [Fact(Skip = "Test for working on Odoo")] 99 | public async Task Update_name_in_nl() 100 | { 101 | var repo = new ProductTemplateRepository(); 102 | 103 | var result = await repo.Query().ById(15).FirstOrDefaultAsync(); 104 | 105 | result.Error.Should().BeNull(); 106 | result.Succeed.Should().BeTrue(); 107 | result.Value.Should().NotBeNull(); 108 | result.Value.Name.Should().Be("Acoustic Bloc Screens"); 109 | result.Value.Name.Should().NotBe("Akoestische blokschermen"); 110 | 111 | var model = OdooDictionaryModel.Create().Add(x => x.Name, "Product new name NL"); 112 | 113 | repo.Config.Context.Language = "nl_BE"; 114 | 115 | var updateResult = await repo.UpdateAsync(model, result.Value.Id); 116 | 117 | updateResult.Error.Should().BeNull(); 118 | updateResult.Succeed.Should().BeTrue(); 119 | updateResult.Value.Should().BeTrue(); 120 | 121 | var resultInNL = await repo.Query().ById(result.Value.Id).FirstOrDefaultAsync(); 122 | 123 | resultInNL.Error.Should().BeNull(); 124 | resultInNL.Succeed.Should().BeTrue(); 125 | resultInNL.Value.Should().NotBeNull(); 126 | 127 | result.Value.Name.Should().NotBe("Acoustic Bloc Screens"); 128 | result.Value.Name.Should().Be("Akoestische blokschermen"); 129 | 130 | 131 | repo.Config.Context.Language = "en_US"; 132 | 133 | var resultInEn = await repo.Query().ById(result.Value.Id).FirstOrDefaultAsync(); 134 | 135 | resultInEn.Error.Should().BeNull(); 136 | resultInEn.Succeed.Should().BeTrue(); 137 | resultInEn.Value.Should().NotBeNull(); 138 | 139 | resultInEn.Value.Name.Should().Be("Acoustic Bloc Screens"); 140 | resultInEn.Value.Name.Should().NotBe("Akoestische blokschermen"); 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Shared/Models/StockProductionLotOdooDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using PortaCapena.OdooJsonRpcClient.Attributes; 4 | using PortaCapena.OdooJsonRpcClient.Converters; 5 | using PortaCapena.OdooJsonRpcClient.Models; 6 | 7 | namespace PortaCapena.OdooJsonRpcClient.Shared.Models 8 | { 9 | [OdooTableName("stock.production.lot")] 10 | [JsonConverter(typeof(OdooModelConverter))] 11 | public class StockProductionLotOdooDto : IOdooModel 12 | { 13 | 14 | [JsonProperty("name")] 15 | public string Name { get; set; } 16 | 17 | [JsonProperty("ref")] 18 | public string Ref { get; set; } 19 | 20 | // product.product 21 | [JsonProperty("product_id")] 22 | public long ProductId { get; set; } 23 | 24 | // uom.uom 25 | [JsonProperty("product_uom_id")] 26 | public long? ProductUomId { get; set; } 27 | 28 | // stock.quant 29 | [JsonProperty("quant_ids")] 30 | public long[] QuantIds { get; set; } 31 | 32 | [JsonProperty("product_qty")] 33 | public double? ProductQty { get; set; } 34 | 35 | [JsonProperty("note")] 36 | public string Note { get; set; } 37 | 38 | [JsonProperty("display_complete")] 39 | public bool? DisplayComplete { get; set; } 40 | 41 | // res.company 42 | [JsonProperty("company_id")] 43 | public long CompanyId { get; set; } 44 | 45 | // purchase.order 46 | [JsonProperty("purchase_order_ids")] 47 | public long[] PurchaseOrderIds { get; set; } 48 | 49 | [JsonProperty("purchase_order_count")] 50 | public int? PurchaseOrderCount { get; set; } 51 | 52 | // sale.order 53 | [JsonProperty("sale_order_ids")] 54 | public long[] SaleOrderIds { get; set; } 55 | 56 | [JsonProperty("sale_order_count")] 57 | public int? SaleOrderCount { get; set; } 58 | 59 | // mail.activity 60 | [JsonProperty("activity_ids")] 61 | public long[] ActivityIds { get; set; } 62 | 63 | [JsonProperty("activity_state")] 64 | public string ActivityState { get; set; } 65 | 66 | // res.users 67 | [JsonProperty("activity_user_id")] 68 | public long? ActivityUserId { get; set; } 69 | 70 | // mail.activity.type 71 | [JsonProperty("activity_type_id")] 72 | public long? ActivityTypeId { get; set; } 73 | 74 | [JsonProperty("activity_type_icon")] 75 | public string ActivityTypeIcon { get; set; } 76 | 77 | [JsonProperty("activity_date_deadline")] 78 | public DateTime? ActivityDateDeadline { get; set; } 79 | 80 | [JsonProperty("activity_summary")] 81 | public string ActivitySummary { get; set; } 82 | 83 | [JsonProperty("activity_exception_decoration")] 84 | public string ActivityExceptionDecoration { get; set; } 85 | 86 | [JsonProperty("activity_exception_icon")] 87 | public string ActivityExceptionIcon { get; set; } 88 | 89 | [JsonProperty("message_is_follower")] 90 | public bool? MessageIsFollower { get; set; } 91 | 92 | // mail.followers 93 | [JsonProperty("message_follower_ids")] 94 | public long[] MessageFollowerIds { get; set; } 95 | 96 | // res.partner 97 | [JsonProperty("message_partner_ids")] 98 | public long[] MessagePartnerIds { get; set; } 99 | 100 | // mail.channel 101 | [JsonProperty("message_channel_ids")] 102 | public long[] MessageChannelIds { get; set; } 103 | 104 | // mail.message 105 | [JsonProperty("message_ids")] 106 | public long[] MessageIds { get; set; } 107 | 108 | [JsonProperty("message_unread")] 109 | public bool? MessageUnread { get; set; } 110 | 111 | [JsonProperty("message_unread_counter")] 112 | public int? MessageUnreadCounter { get; set; } 113 | 114 | [JsonProperty("message_needaction")] 115 | public bool? MessageNeedaction { get; set; } 116 | 117 | [JsonProperty("message_needaction_counter")] 118 | public int? MessageNeedactionCounter { get; set; } 119 | 120 | [JsonProperty("message_has_error")] 121 | public bool? MessageHasError { get; set; } 122 | 123 | [JsonProperty("message_has_error_counter")] 124 | public int? MessageHasErrorCounter { get; set; } 125 | 126 | [JsonProperty("message_attachment_count")] 127 | public int? MessageAttachmentCount { get; set; } 128 | 129 | // ir.attachment 130 | [JsonProperty("message_main_attachment_id")] 131 | public long? MessageMainAttachmentId { get; set; } 132 | 133 | // mail.message 134 | [JsonProperty("website_message_ids")] 135 | public long[] WebsiteMessageIds { get; set; } 136 | 137 | [JsonProperty("message_has_sms_error")] 138 | public bool? MessageHasSmsError { get; set; } 139 | 140 | [JsonProperty("id")] 141 | public long Id { get; set; } 142 | 143 | [JsonProperty("display_name")] 144 | public string DisplayName { get; set; } 145 | 146 | // res.users 147 | [JsonProperty("create_uid")] 148 | public long? CreateUid { get; set; } 149 | 150 | [JsonProperty("create_date")] 151 | public DateTime? CreateDate { get; set; } 152 | 153 | // res.users 154 | [JsonProperty("write_uid")] 155 | public long? WriteUid { get; set; } 156 | 157 | [JsonProperty("write_date")] 158 | public DateTime? WriteDate { get; set; } 159 | 160 | [JsonProperty("__last_update")] 161 | public DateTime? LastUpdate { get; set; } 162 | } 163 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Request/OdooFilterOfT.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using PortaCapena.OdooJsonRpcClient.Converters; 5 | using PortaCapena.OdooJsonRpcClient.Models; 6 | 7 | namespace PortaCapena.OdooJsonRpcClient.Request 8 | { 9 | public class OdooFilter : OdooFilter where T : IOdooModel 10 | { 11 | public static OdooFilter Create() 12 | { 13 | return new OdooFilter(); 14 | } 15 | 16 | public OdooFilter Or() 17 | { 18 | base.Or(); 19 | return this; 20 | } 21 | 22 | public OdooFilter Not() 23 | { 24 | base.Not(); 25 | return this; 26 | } 27 | 28 | public OdooFilter And() 29 | { 30 | base.And(); 31 | return this; 32 | } 33 | 34 | 35 | public OdooFilter EqualTo(Expression> expression, object value) 36 | { 37 | EqualTo(OdooExpresionMapper.GetOdooPropertyName(expression), value); 38 | return this; 39 | } 40 | 41 | public OdooFilter ILike(Expression> expression, object value) 42 | { 43 | ILike(OdooExpresionMapper.GetOdooPropertyName(expression), value); 44 | return this; 45 | } 46 | 47 | public OdooFilter Like(Expression> expression, object value) 48 | { 49 | Like(OdooExpresionMapper.GetOdooPropertyName(expression), value); 50 | return this; 51 | } 52 | 53 | public OdooFilter NotLike(Expression> expression, object value) 54 | { 55 | NotLike(OdooExpresionMapper.GetOdooPropertyName(expression), value); 56 | return this; 57 | } 58 | 59 | public OdooFilter NotEqual(Expression> expression, object value) 60 | { 61 | NotEqual(OdooExpresionMapper.GetOdooPropertyName(expression), value); 62 | return this; 63 | } 64 | 65 | public OdooFilter GreaterThan(Expression> expression, object value) 66 | { 67 | GreaterThan(OdooExpresionMapper.GetOdooPropertyName(expression), value); 68 | return this; 69 | } 70 | 71 | public OdooFilter GreaterThanOrEqual(Expression> expression, object value) 72 | { 73 | GreaterThanOrEqual(OdooExpresionMapper.GetOdooPropertyName(expression), value); 74 | return this; 75 | } 76 | 77 | public OdooFilter LessThan(Expression> expression, object value) 78 | { 79 | LessThan(OdooExpresionMapper.GetOdooPropertyName(expression), value); 80 | return this; 81 | } 82 | 83 | public OdooFilter LessThanOrEqual(Expression> expression, object value) 84 | { 85 | LessThanOrEqual(OdooExpresionMapper.GetOdooPropertyName(expression), value); 86 | return this; 87 | } 88 | 89 | public OdooFilter Between(Expression> expression, object value1, object value2) 90 | { 91 | Between(OdooExpresionMapper.GetOdooPropertyName(expression), value1, value2); 92 | return this; 93 | } 94 | 95 | public OdooFilter In(Expression> expression, OdooFilterValue value) 96 | { 97 | In(OdooExpresionMapper.GetOdooPropertyName(expression), value); 98 | return this; 99 | } 100 | 101 | public OdooFilter In(Expression> expression, object[] values) 102 | { 103 | In(OdooExpresionMapper.GetOdooPropertyName(expression), values); 104 | return this; 105 | } 106 | 107 | public OdooFilter In(Expression> expression, List values) 108 | { 109 | In(OdooExpresionMapper.GetOdooPropertyName(expression), values); 110 | return this; 111 | } 112 | 113 | public OdooFilter In(Expression> expression, long[] values) 114 | { 115 | In(OdooExpresionMapper.GetOdooPropertyName(expression), values); 116 | return this; 117 | } 118 | 119 | public OdooFilter NotIn(Expression> expression, OdooFilterValue value) 120 | { 121 | NotIn(OdooExpresionMapper.GetOdooPropertyName(expression), value); 122 | return this; 123 | } 124 | 125 | public OdooFilter NotIn(Expression> expression, object[] values) 126 | { 127 | NotIn(OdooExpresionMapper.GetOdooPropertyName(expression), values); 128 | return this; 129 | } 130 | 131 | public OdooFilter NotIn(Expression> expression, List values) 132 | { 133 | NotIn(OdooExpresionMapper.GetOdooPropertyName(expression), values); 134 | return this; 135 | } 136 | 137 | public OdooFilter NotIn(Expression> expression, long[] values) 138 | { 139 | NotIn(OdooExpresionMapper.GetOdooPropertyName(expression), values); 140 | return this; 141 | } 142 | 143 | public OdooFilter ChildOf(Expression> expression, string values) 144 | { 145 | ChildOf(OdooExpresionMapper.GetOdooPropertyName(expression), values); 146 | return this; 147 | } 148 | 149 | public OdooFilter NotBeNull(Expression> expression) 150 | { 151 | NotBeNull(OdooExpresionMapper.GetOdooPropertyName(expression)); 152 | return this; 153 | } 154 | public OdooFilter BeNull(Expression> expression) 155 | { 156 | BeNull(OdooExpresionMapper.GetOdooPropertyName(expression)); 157 | return this; 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Tests/OdooDictionaryModelTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using FluentAssertions; 4 | using PortaCapena.OdooJsonRpcClient.Models; 5 | using PortaCapena.OdooJsonRpcClient.Shared.Models; 6 | using Xunit; 7 | 8 | namespace PortaCapena.OdooJsonRpcClient.Tests 9 | { 10 | public class OdooDictionaryModelTests 11 | { 12 | [Fact] 13 | public void Can_create_simple_dictionary() 14 | { 15 | var model = OdooDictionaryModel.Create(() => new PurchaseOrderOdooModel() 16 | { 17 | CompanyId = 1, 18 | PartnerId = 2, 19 | CurrencyId = 3, 20 | XStudioPickupAddress = "pickupAddress", 21 | State = StatusPurchaseOrderOdooEnum.PurchaseOrder 22 | }); 23 | 24 | model.TableName.Should().NotBeEmpty(); 25 | model.Should().NotBeEmpty(); 26 | model.Count.Should().Be(5); 27 | 28 | model.First().Key.Should().Be("company_id"); 29 | model.First().Value.Should().Be(1); 30 | 31 | model.Skip(1).First().Key.Should().Be("partner_id"); 32 | model.Skip(1).First().Value.Should().Be(2); 33 | 34 | model.Skip(2).First().Key.Should().Be("currency_id"); 35 | model.Skip(2).First().Value.Should().Be(3); 36 | 37 | model.Skip(3).First().Key.Should().Be("x_studio_pickup_address"); 38 | model.Skip(3).First().Value.Should().Be("pickupAddress"); 39 | 40 | model.Skip(4).First().Key.Should().Be("state"); 41 | model.Skip(4).First().Value.Should().Be(StatusPurchaseOrderOdooEnum.PurchaseOrder); 42 | } 43 | 44 | [Fact] 45 | public void Can_create_dictionary_with_create_instance() 46 | { 47 | var model = OdooDictionaryModel.Create(() => new ProductProductOdooModel() 48 | { 49 | Name = "test name", 50 | }); 51 | model.Add(x => x.CreateDate, new DateTime()); 52 | 53 | model.TableName.Should().NotBeEmpty(); 54 | model.Should().NotBeEmpty(); 55 | model.Count.Should().Be(2); 56 | 57 | model.First().Key.Should().Be("name"); 58 | model.First().Value.Should().Be("test name"); 59 | 60 | model.Skip(1).First().Key.Should().Be("create_date"); 61 | model.Skip(1).First().Value.Should().Be(new DateTime()); 62 | } 63 | 64 | [Fact] 65 | public void Can_create_dictionary_with_call_method() 66 | { 67 | var model = OdooDictionaryModel.Create(() => new PurchaseOrderLineOdooModel() 68 | { 69 | Name = TestString(), 70 | }); 71 | model.Add(x => x.DisplayName, TestString()); 72 | 73 | model.TableName.Should().NotBeEmpty(); 74 | model.Should().NotBeEmpty(); 75 | model.Count.Should().Be(2); 76 | 77 | model.First().Key.Should().Be("name"); 78 | model.First().Value.Should().BeOfType(); 79 | 80 | model.Skip(1).First().Key.Should().Be("display_name"); 81 | model.Skip(1).First().Value.Should().BeOfType(); 82 | } 83 | 84 | [Fact] 85 | public void Can_create_dictionary_with_call_method_with_params() 86 | { 87 | var model = OdooDictionaryModel.Create(() => new PurchaseOrderLineOdooModel() 88 | { 89 | Name = TestString("123"), 90 | }); 91 | model.Add(x => x.DisplayName, TestString("456")); 92 | 93 | model.TableName.Should().NotBeEmpty(); 94 | model.Should().NotBeEmpty(); 95 | model.Count.Should().Be(2); 96 | 97 | model.First().Key.Should().Be("name"); 98 | model.First().Value.Should().BeOfType(); 99 | model.First().Value.Should().Be("123test"); 100 | 101 | model.Skip(1).First().Key.Should().Be("display_name"); 102 | model.Skip(1).First().Value.Should().BeOfType(); 103 | model.Skip(1).First().Value.Should().Be("456test"); 104 | } 105 | 106 | [Fact] 107 | public void Can_create_dictionary_with_call_method_with_enum_params() 108 | { 109 | var model = OdooDictionaryModel.Create(() => new PurchaseOrderLineOdooModel() 110 | { 111 | Name = TestString("123"), 112 | }); 113 | model.Add(x => x.State, StatusPurchaseOrderOdooEnum.PurchaseOrder); 114 | 115 | model.TableName.Should().NotBeEmpty(); 116 | model.Should().NotBeEmpty(); 117 | model.Count.Should().Be(2); 118 | 119 | model.First().Key.Should().Be("name"); 120 | model.First().Value.Should().BeOfType(); 121 | model.First().Value.Should().Be("123test"); 122 | 123 | model.Skip(1).First().Key.Should().Be("state"); 124 | model.Skip(1).First().Value.Should().BeOfType(); 125 | model.Skip(1).First().Value.Should().Be(StatusPurchaseOrderOdooEnum.PurchaseOrder); 126 | } 127 | 128 | [Fact] 129 | public void Can_create_dictionary_with_array() 130 | { 131 | var model = OdooDictionaryModel.Create(() => new ProductProductOdooModel() 132 | { 133 | ActivityIds = new long[] { 1, 2, 3 } 134 | }); 135 | model.Add(x => x.MessageFollowerIds, new long[] { 4, 5, 6 }); 136 | 137 | model.TableName.Should().NotBeEmpty(); 138 | model.Should().NotBeEmpty(); 139 | model.Count.Should().Be(2); 140 | 141 | model.First().Key.Should().Be("activity_ids"); 142 | model.First().Value.Should().BeOfType().And.NotBeNull(); 143 | model.First().Value.Should().BeEquivalentTo(new long[] { 1, 2, 3 }); 144 | 145 | model.Skip(1).First().Key.Should().Be("message_follower_ids"); 146 | model.Skip(1).First().Value.Should().BeOfType().And.NotBeNull(); 147 | model.Skip(1).First().Value.Should().BeEquivalentTo(new long[] { 4, 5, 6 }); 148 | } 149 | 150 | 151 | private string TestString() => new Guid().ToString(); 152 | private string TestString(string x) => x + "test"; 153 | } 154 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Shared/Models/AccountTaxOdooModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Converters; 5 | using PortaCapena.OdooJsonRpcClient.Attributes; 6 | using PortaCapena.OdooJsonRpcClient.Converters; 7 | using PortaCapena.OdooJsonRpcClient.Models; 8 | 9 | namespace PortaCapena.OdooJsonRpcClient.Shared.Models 10 | { 11 | [OdooTableName("account.tax")] 12 | [JsonConverter(typeof(OdooModelConverter))] 13 | public class AccountTaxOdooModel : IOdooModel 14 | { 15 | 16 | // required 17 | [JsonProperty("type_tax_use")] 18 | public TaxTypeAccountTaxOdooEnum TypeTaxUse { get; set; } 19 | 20 | [JsonProperty("tax_scope")] 21 | public TaxScopeAccountTaxOdooEnum? TaxScope { get; set; } 22 | 23 | // required 24 | [JsonProperty("amount_type")] 25 | public TaxComputationAccountTaxOdooEnum AmountType { get; set; } 26 | 27 | [JsonProperty("active")] 28 | public bool? Active { get; set; } 29 | 30 | // res.company 31 | // required 32 | [JsonProperty("company_id")] 33 | public long CompanyId { get; set; } 34 | 35 | // account.tax 36 | [JsonProperty("children_tax_ids")] 37 | public long[] ChildrenTaxIds { get; set; } 38 | 39 | // required 40 | [JsonProperty("sequence")] 41 | public int Sequence { get; set; } 42 | 43 | // required 44 | [JsonProperty("amount")] 45 | public double Amount { get; set; } 46 | 47 | [JsonProperty("price_include")] 48 | public bool? PriceInclude { get; set; } 49 | 50 | [JsonProperty("include_base_amount")] 51 | public bool? IncludeBaseAmount { get; set; } 52 | 53 | [JsonProperty("analytic")] 54 | public bool? Analytic { get; set; } 55 | 56 | // account.tax.group 57 | // required 58 | [JsonProperty("tax_group_id")] 59 | public long TaxGroupId { get; set; } 60 | 61 | [JsonProperty("hide_tax_exigibility")] 62 | public bool? HideTaxExigibility { get; set; } 63 | 64 | [JsonProperty("tax_exigibility")] 65 | public TaxDueAccountTaxOdooEnum? TaxExigibility { get; set; } 66 | 67 | // account.account 68 | [JsonProperty("cash_basis_transition_account_id")] 69 | public long? CashBasisTransitionAccountId { get; set; } 70 | 71 | // account.tax.repartition.line 72 | [JsonProperty("invoice_repartition_line_ids")] 73 | public long[] InvoiceRepartitionLineIds { get; set; } 74 | 75 | // account.tax.repartition.line 76 | [JsonProperty("refund_repartition_line_ids")] 77 | public long[] RefundRepartitionLineIds { get; set; } 78 | 79 | // res.country 80 | [JsonProperty("tax_fiscal_country_id")] 81 | public long? TaxFiscalCountryId { get; set; } 82 | 83 | [JsonProperty("country_code")] 84 | public string CountryCode { get; set; } 85 | 86 | // required 87 | [JsonProperty("name")] 88 | public string Name { get; set; } 89 | 90 | [JsonProperty("description")] 91 | public string Description { get; set; } 92 | 93 | [JsonProperty("id")] 94 | public long Id { get; set; } 95 | 96 | [JsonProperty("display_name")] 97 | public string DisplayName { get; set; } 98 | 99 | // res.users 100 | [JsonProperty("create_uid")] 101 | public long? CreateUid { get; set; } 102 | 103 | [JsonProperty("create_date")] 104 | public DateTime? CreateDate { get; set; } 105 | 106 | // res.users 107 | [JsonProperty("write_uid")] 108 | public long? WriteUid { get; set; } 109 | 110 | [JsonProperty("write_date")] 111 | public DateTime? WriteDate { get; set; } 112 | 113 | [JsonProperty("__last_update")] 114 | public DateTime? LastUpdate { get; set; } 115 | } 116 | 117 | 118 | // Determines where the tax is selectable. Note : 'None' means a tax can't be used by itself, however it can still be used in a group. 'adjustment' is used to perform tax adjustment. 119 | [JsonConverter(typeof(StringEnumConverter))] 120 | public enum TaxTypeAccountTaxOdooEnum 121 | { 122 | [EnumMember(Value = "sale")] 123 | Sales = 1, 124 | 125 | [EnumMember(Value = "purchase")] 126 | Purchases = 2, 127 | 128 | [EnumMember(Value = "none")] 129 | None = 3, 130 | } 131 | 132 | 133 | // Restrict the use of taxes to a type of product. 134 | [JsonConverter(typeof(StringEnumConverter))] 135 | public enum TaxScopeAccountTaxOdooEnum 136 | { 137 | [EnumMember(Value = "service")] 138 | Services = 1, 139 | 140 | [EnumMember(Value = "consu")] 141 | Goods = 2, 142 | } 143 | 144 | 145 | // 146 | // - Group of Taxes: The tax is a set of sub taxes. 147 | // - Fixed: The tax amount stays the same whatever the price. 148 | // - Percentage of Price: The tax amount is a % of the price: 149 | // e.g 100 * (1 + 10%) = 110 (not price included) 150 | // e.g 110 / (1 + 10%) = 100 (price included) 151 | // - Percentage of Price Tax Included: The tax amount is a division of the price: 152 | // e.g 180 / (1 - 10%) = 200 (not price included) 153 | // e.g 200 * (1 - 10%) = 180 (price included) 154 | // 155 | [JsonConverter(typeof(StringEnumConverter))] 156 | public enum TaxComputationAccountTaxOdooEnum 157 | { 158 | [EnumMember(Value = "group")] 159 | GroupOfTaxes = 1, 160 | 161 | [EnumMember(Value = "fixed")] 162 | Fixed = 2, 163 | 164 | [EnumMember(Value = "percent")] 165 | PercentageOfPrice = 3, 166 | 167 | [EnumMember(Value = "division")] 168 | PercentageOfPriceTaxIncluded = 4, 169 | } 170 | 171 | 172 | // Based on Invoice: the tax is due as soon as the invoice is validated. 173 | // Based on Payment: the tax is due as soon as the payment of the invoice is received. 174 | [JsonConverter(typeof(StringEnumConverter))] 175 | public enum TaxDueAccountTaxOdooEnum 176 | { 177 | [EnumMember(Value = "on_invoice")] 178 | BasedOnInvoice = 1, 179 | 180 | [EnumMember(Value = "on_payment")] 181 | BasedOnPayment = 2, 182 | } 183 | 184 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Shared/Models/AccountAccountOdooModel .cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Converters; 5 | using PortaCapena.OdooJsonRpcClient.Attributes; 6 | using PortaCapena.OdooJsonRpcClient.Converters; 7 | using PortaCapena.OdooJsonRpcClient.Models; 8 | 9 | namespace PortaCapena.OdooJsonRpcClient.Shared.Models 10 | { 11 | [OdooTableName("account.account")] 12 | [JsonConverter(typeof(OdooModelConverter))] 13 | public class AccountAccountOdooModel : IOdooModel 14 | { 15 | 16 | // res.currency 17 | [JsonProperty("currency_id")] 18 | public long? CurrencyId { get; set; } 19 | 20 | // required 21 | [JsonProperty("code")] 22 | public string Code { get; set; } 23 | 24 | [JsonProperty("deprecated")] 25 | public bool? Deprecated { get; set; } 26 | 27 | [JsonProperty("used")] 28 | public bool? Used { get; set; } 29 | 30 | // account.account.type 31 | // required 32 | [JsonProperty("user_type_id")] 33 | public long UserTypeId { get; set; } 34 | 35 | [JsonProperty("internal_type")] 36 | public InternalTypeAccountAccountOdooEnum? InternalType { get; set; } 37 | 38 | [JsonProperty("internal_group")] 39 | public InternalGroupAccountAccountOdooEnum? InternalGroup { get; set; } 40 | 41 | [JsonProperty("reconcile")] 42 | public bool? Reconcile { get; set; } 43 | 44 | // account.tax 45 | [JsonProperty("tax_ids")] 46 | public long[] TaxIds { get; set; } 47 | 48 | [JsonProperty("note")] 49 | public string Note { get; set; } 50 | 51 | // res.company 52 | // required 53 | [JsonProperty("company_id")] 54 | public long CompanyId { get; set; } 55 | 56 | // account.account.tag 57 | [JsonProperty("tag_ids")] 58 | public long[] TagIds { get; set; } 59 | 60 | // account.group 61 | [JsonProperty("group_id")] 62 | public long? GroupId { get; set; } 63 | 64 | // account.root 65 | [JsonProperty("root_id")] 66 | public long? RootId { get; set; } 67 | 68 | // account.journal 69 | [JsonProperty("allowed_journal_ids")] 70 | public long[] AllowedJournalIds { get; set; } 71 | 72 | [JsonProperty("opening_debit")] 73 | public decimal? OpeningDebit { get; set; } 74 | 75 | [JsonProperty("opening_credit")] 76 | public decimal? OpeningCredit { get; set; } 77 | 78 | [JsonProperty("opening_balance")] 79 | public decimal? OpeningBalance { get; set; } 80 | 81 | [JsonProperty("is_off_balance")] 82 | public bool? IsOffBalance { get; set; } 83 | 84 | // required 85 | [JsonProperty("name")] 86 | public string Name { get; set; } 87 | 88 | // res.currency 89 | [JsonProperty("exclude_provision_currency_ids")] 90 | public long[] ExcludeProvisionCurrencyIds { get; set; } 91 | 92 | // account.asset 93 | [JsonProperty("asset_model")] 94 | public long? AssetModel { get; set; } 95 | 96 | // required 97 | [JsonProperty("create_asset")] 98 | public CreateAssetAccountAccountOdooEnum CreateAsset { get; set; } 99 | 100 | [JsonProperty("can_create_asset")] 101 | public bool? CanCreateAsset { get; set; } 102 | 103 | [JsonProperty("form_view_ref")] 104 | public string FormViewRef { get; set; } 105 | 106 | [JsonProperty("asset_type")] 107 | public AssetTypeAccountAccountOdooEnum? AssetType { get; set; } 108 | 109 | [JsonProperty("multiple_assets_per_line")] 110 | public bool? MultipleAssetsPerLine { get; set; } 111 | 112 | [JsonProperty("id")] 113 | public long Id { get; set; } 114 | 115 | [JsonProperty("display_name")] 116 | public string DisplayName { get; set; } 117 | 118 | // res.users 119 | [JsonProperty("create_uid")] 120 | public long? CreateUid { get; set; } 121 | 122 | [JsonProperty("create_date")] 123 | public DateTime? CreateDate { get; set; } 124 | 125 | // res.users 126 | [JsonProperty("write_uid")] 127 | public long? WriteUid { get; set; } 128 | 129 | [JsonProperty("write_date")] 130 | public DateTime? WriteDate { get; set; } 131 | 132 | [JsonProperty("__last_update")] 133 | public DateTime? LastUpdate { get; set; } 134 | } 135 | 136 | 137 | // The 'Internal Type' is used for features available on different types of accounts: liquidity type is for cash or bank accounts, payable/receivable is for vendor/customer accounts. 138 | [JsonConverter(typeof(StringEnumConverter))] 139 | public enum InternalTypeAccountAccountOdooEnum 140 | { 141 | [EnumMember(Value = "other")] 142 | Regular = 1, 143 | 144 | [EnumMember(Value = "receivable")] 145 | Receivable = 2, 146 | 147 | [EnumMember(Value = "payable")] 148 | Payable = 3, 149 | 150 | [EnumMember(Value = "liquidity")] 151 | Liquidity = 4, 152 | } 153 | 154 | 155 | // The 'Internal Group' is used to filter accounts based on the internal group set on the account type. 156 | [JsonConverter(typeof(StringEnumConverter))] 157 | public enum InternalGroupAccountAccountOdooEnum 158 | { 159 | [EnumMember(Value = "equity")] 160 | Equity = 1, 161 | 162 | [EnumMember(Value = "asset")] 163 | Asset = 2, 164 | 165 | [EnumMember(Value = "liability")] 166 | Liability = 3, 167 | 168 | [EnumMember(Value = "income")] 169 | Income = 4, 170 | 171 | [EnumMember(Value = "expense")] 172 | Expense = 5, 173 | 174 | [EnumMember(Value = "off_balance")] 175 | OffBalance = 6, 176 | } 177 | 178 | 179 | [JsonConverter(typeof(StringEnumConverter))] 180 | public enum CreateAssetAccountAccountOdooEnum 181 | { 182 | [EnumMember(Value = "no")] 183 | No = 1, 184 | 185 | [EnumMember(Value = "draft")] 186 | CreateInDraft = 2, 187 | 188 | [EnumMember(Value = "validate")] 189 | CreateAndValidate = 3, 190 | } 191 | 192 | 193 | [JsonConverter(typeof(StringEnumConverter))] 194 | public enum AssetTypeAccountAccountOdooEnum 195 | { 196 | [EnumMember(Value = "sale")] 197 | DeferredRevenue = 1, 198 | 199 | [EnumMember(Value = "expense")] 200 | DeferredExpense = 2, 201 | 202 | [EnumMember(Value = "purchase")] 203 | Asset = 3, 204 | } 205 | 206 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Request/OdooFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using PortaCapena.OdooJsonRpcClient.Consts; 5 | using PortaCapena.OdooJsonRpcClient.Extensions; 6 | using PortaCapena.OdooJsonRpcClient.Models; 7 | 8 | namespace PortaCapena.OdooJsonRpcClient.Request 9 | { 10 | public class OdooFilter : ArrayList 11 | { 12 | public static OdooFilter Create() 13 | { 14 | return new OdooFilter(); 15 | } 16 | 17 | public OdooFilter EqualTo(string fieldName, object value) 18 | { 19 | var field = new object[] { fieldName, "=", value }; 20 | Add(field); 21 | return this; 22 | } 23 | 24 | public OdooFilter Or() 25 | { 26 | Add("|"); 27 | return this; 28 | } 29 | 30 | public OdooFilter Not() 31 | { 32 | Add("!"); 33 | return this; 34 | } 35 | 36 | public OdooFilter And() 37 | { 38 | Add("&"); 39 | return this; 40 | } 41 | 42 | public OdooFilter ILike(string fieldName, object value) 43 | { 44 | var field = new object[] { fieldName, OdooOperator.CaseInsensitiveLike.Description(), value }; 45 | Add(field); 46 | return this; 47 | } 48 | 49 | public OdooFilter Like(string fieldName, object value) 50 | { 51 | var field = new object[] { fieldName, OdooOperator.Like.Description(), value }; 52 | Add(field); 53 | return this; 54 | } 55 | 56 | public OdooFilter NotLike(string fieldName, object value) 57 | { 58 | var field = new object[] { fieldName, OdooOperator.NotLike.Description(), value }; 59 | Add(field); 60 | return this; 61 | } 62 | 63 | public OdooFilter NotEqual(string fieldName, object value) 64 | { 65 | var field = new object[] { fieldName, OdooOperator.NotEqualsTo.Description(), value }; 66 | Add(field); 67 | return this; 68 | } 69 | 70 | public OdooFilter GreaterThan(string fieldName, object value) 71 | { 72 | var field = new object[] { fieldName, OdooOperator.GreaterThan.Description(), value }; 73 | Add(field); 74 | return this; 75 | } 76 | 77 | public OdooFilter GreaterThanOrEqual(string fieldName, object value) 78 | { 79 | var field = new object[] { fieldName, OdooOperator.GreaterThanOrEqualTo.Description(), value }; 80 | Add(field); 81 | return this; 82 | } 83 | 84 | public OdooFilter LessThan(string fieldName, object value) 85 | { 86 | var field = new object[] { fieldName, OdooOperator.LessThan.Description(), value }; 87 | Add(field); 88 | return this; 89 | } 90 | 91 | public OdooFilter LessThanOrEqual(string fieldName, object value) 92 | { 93 | var field = new object[] { fieldName, OdooOperator.LessThanOrEqualTo.Description(), value }; 94 | Add(field); 95 | return this; 96 | } 97 | 98 | public OdooFilter Between(string fieldName, object value1, object value2) 99 | { 100 | var field = new object[] { fieldName, "between", value1, "and", value2 }; 101 | Add(field); 102 | return this; 103 | } 104 | 105 | public OdooFilter In(string fieldName, OdooFilterValue value) 106 | { 107 | var field = new object[] { fieldName, OdooOperator.In.Description(), value.ToArray() }; 108 | Add(field); 109 | return this; 110 | } 111 | 112 | public OdooFilter In(string fieldName, object[] values) 113 | { 114 | var field = new object[] { fieldName, OdooOperator.In.Description(), values }; 115 | Add(field); 116 | return this; 117 | } 118 | 119 | public OdooFilter In(string fieldName, List values) 120 | { 121 | var field = new object[] { fieldName, OdooOperator.In.Description(), values.ToArray() }; 122 | Add(field); 123 | return this; 124 | } 125 | 126 | public OdooFilter In(string fieldName, long[] values) 127 | { 128 | var field = new object[] { fieldName, OdooOperator.In.Description(), values }; 129 | Add(field); 130 | return this; 131 | } 132 | 133 | public OdooFilter NotIn(string fieldName, OdooFilterValue value) 134 | { 135 | var field = new object[] { fieldName, OdooOperator.NotIn.Description(), value.ToArray() }; 136 | Add(field); 137 | return this; 138 | } 139 | 140 | public OdooFilter NotIn(string fieldName, object[] values) 141 | { 142 | var field = new object[] { fieldName, OdooOperator.NotIn.Description(), values }; 143 | Add(field); 144 | return this; 145 | } 146 | 147 | public OdooFilter NotIn(string fieldName, List values) 148 | { 149 | var field = new object[] { fieldName, OdooOperator.NotIn.Description(), values.ToArray() }; 150 | Add(field); 151 | return this; 152 | } 153 | 154 | public OdooFilter NotIn(string fieldName, long[] values) 155 | { 156 | var field = new object[] { fieldName, OdooOperator.NotIn.Description(), values }; 157 | Add(field); 158 | return this; 159 | } 160 | 161 | public OdooFilter ChildOf(string fieldName, string values) 162 | { 163 | var field = new object[] { fieldName, OdooOperator.NotEqualsTo.Description(), "child_of", values }; 164 | Add(field); 165 | return this; 166 | } 167 | 168 | public OdooFilter Build() 169 | { 170 | return this; 171 | } 172 | 173 | public OdooFilter NotBeNull(string fieldName) 174 | { 175 | var field = new object[] { fieldName, OdooOperator.NotEqualsTo.Description(), false }; 176 | Add(field); 177 | return this; 178 | } 179 | public OdooFilter BeNull(string fieldName) 180 | { 181 | var field = new object[] { fieldName, OdooOperator.EqualsTo.Description(), false }; 182 | Add(field); 183 | return this; 184 | } 185 | } 186 | 187 | public class OdooFilterValue : ArrayList 188 | { 189 | public OdooFilterValue AddValue(object value) 190 | { 191 | Add(value); 192 | return this; 193 | } 194 | } 195 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Shared/Models/PurchaseOrderLineOdooModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Converters; 5 | using PortaCapena.OdooJsonRpcClient.Attributes; 6 | using PortaCapena.OdooJsonRpcClient.Converters; 7 | using PortaCapena.OdooJsonRpcClient.Models; 8 | 9 | namespace PortaCapena.OdooJsonRpcClient.Shared.Models 10 | { 11 | [OdooTableName("purchase.order.line")] 12 | [JsonConverter(typeof(OdooModelConverter))] 13 | public class PurchaseOrderLineOdooModel : IOdooModel 14 | { 15 | 16 | // required 17 | [JsonProperty("name")] 18 | public string Name { get; set; } 19 | 20 | [JsonProperty("sequence")] 21 | public int? Sequence { get; set; } 22 | 23 | // required 24 | [JsonProperty("product_qty")] 25 | public double ProductQty { get; set; } 26 | 27 | [JsonProperty("product_uom_qty")] 28 | public double? ProductUomQty { get; set; } 29 | 30 | [JsonProperty("date_planned")] 31 | public DateTime? DatePlanned { get; set; } 32 | 33 | // account.tax 34 | [JsonProperty("taxes_id")] 35 | public long[] TaxesId { get; set; } 36 | 37 | // uom.uom 38 | [JsonProperty("product_uom")] 39 | public long? ProductUom { get; set; } 40 | 41 | // uom.category 42 | [JsonProperty("product_uom_category_id")] 43 | public long? ProductUomCategoryId { get; set; } 44 | 45 | // product.product 46 | [JsonProperty("product_id")] 47 | public long? ProductId { get; set; } 48 | 49 | [JsonProperty("product_type")] 50 | public ProductTypePurchaseOrderLineOdooEnum? ProductType { get; set; } 51 | 52 | // required 53 | [JsonProperty("price_unit")] 54 | public double PriceUnit { get; set; } 55 | 56 | [JsonProperty("price_subtotal")] 57 | public decimal? PriceSubtotal { get; set; } 58 | 59 | [JsonProperty("price_total")] 60 | public decimal? PriceTotal { get; set; } 61 | 62 | [JsonProperty("price_tax")] 63 | public double? PriceTax { get; set; } 64 | 65 | // purchase.order 66 | // required 67 | [JsonProperty("order_id")] 68 | public long OrderId { get; set; } 69 | 70 | // account.analytic.account 71 | [JsonProperty("account_analytic_id")] 72 | public long? AccountAnalyticId { get; set; } 73 | 74 | // account.analytic.tag 75 | [JsonProperty("analytic_tag_ids")] 76 | public long[] AnalyticTagIds { get; set; } 77 | 78 | // res.company 79 | [JsonProperty("company_id")] 80 | public long? CompanyId { get; set; } 81 | 82 | [JsonProperty("state")] 83 | public StatusPurchaseOrderLineOdooEnum? State { get; set; } 84 | 85 | // account.move.line 86 | [JsonProperty("invoice_lines")] 87 | public long[] InvoiceLines { get; set; } 88 | 89 | [JsonProperty("qty_invoiced")] 90 | public double? QtyInvoiced { get; set; } 91 | 92 | [JsonProperty("qty_received_method")] 93 | public ReceivedQtyMethodPurchaseOrderLineOdooEnum? QtyReceivedMethod { get; set; } 94 | 95 | [JsonProperty("qty_received")] 96 | public double? QtyReceived { get; set; } 97 | 98 | [JsonProperty("qty_received_manual")] 99 | public double? QtyReceivedManual { get; set; } 100 | 101 | [JsonProperty("qty_to_invoice")] 102 | public double? QtyToInvoice { get; set; } 103 | 104 | // res.partner 105 | [JsonProperty("partner_id")] 106 | public long? PartnerId { get; set; } 107 | 108 | // res.currency 109 | [JsonProperty("currency_id")] 110 | public long? CurrencyId { get; set; } 111 | 112 | [JsonProperty("date_order")] 113 | public DateTime? DateOrder { get; set; } 114 | 115 | [JsonProperty("display_type")] 116 | public DisplayTypePurchaseOrderLineOdooEnum? DisplayType { get; set; } 117 | 118 | // sale.order 119 | [JsonProperty("sale_order_id")] 120 | public long? SaleOrderId { get; set; } 121 | 122 | // sale.order.line 123 | [JsonProperty("sale_line_id")] 124 | public long? SaleLineId { get; set; } 125 | 126 | [JsonProperty("id")] 127 | public long Id { get; set; } 128 | 129 | [JsonProperty("display_name")] 130 | public string DisplayName { get; set; } 131 | 132 | // res.users 133 | [JsonProperty("create_uid")] 134 | public long? CreateUid { get; set; } 135 | 136 | [JsonProperty("create_date")] 137 | public DateTime? CreateDate { get; set; } 138 | 139 | // res.users 140 | [JsonProperty("write_uid")] 141 | public long? WriteUid { get; set; } 142 | 143 | [JsonProperty("write_date")] 144 | public DateTime? WriteDate { get; set; } 145 | 146 | [JsonProperty("__last_update")] 147 | public DateTime? LastUpdate { get; set; } 148 | } 149 | 150 | 151 | // A storable product is a product for which you manage stock. The Inventory app has to be installed. 152 | // A consumable product is a product for which stock is not managed. 153 | // A service is a non-material product you provide. 154 | [JsonConverter(typeof(StringEnumConverter))] 155 | public enum ProductTypePurchaseOrderLineOdooEnum 156 | { 157 | [EnumMember(Value = "consu")] 158 | Consumable = 1, 159 | 160 | [EnumMember(Value = "service")] 161 | Service = 2, 162 | } 163 | 164 | 165 | [JsonConverter(typeof(StringEnumConverter))] 166 | public enum StatusPurchaseOrderLineOdooEnum 167 | { 168 | [EnumMember(Value = "draft")] 169 | RFQ = 1, 170 | 171 | [EnumMember(Value = "sent")] 172 | RFQSent = 2, 173 | 174 | [EnumMember(Value = "to approve")] 175 | ToApprove = 3, 176 | 177 | [EnumMember(Value = "purchase")] 178 | PurchaseOrder = 4, 179 | 180 | [EnumMember(Value = "done")] 181 | Locked = 5, 182 | 183 | [EnumMember(Value = "cancel")] 184 | Cancelled = 6, 185 | } 186 | 187 | 188 | // According to product configuration, the received quantity can be automatically computed by mechanism : 189 | // - Manual: the quantity is set manually on the line 190 | // - Stock Moves: the quantity comes from confirmed pickings 191 | // 192 | [JsonConverter(typeof(StringEnumConverter))] 193 | public enum ReceivedQtyMethodPurchaseOrderLineOdooEnum 194 | { 195 | [EnumMember(Value = "manual")] 196 | Manual = 1, 197 | } 198 | 199 | 200 | // Technical field for UX purpose. 201 | [JsonConverter(typeof(StringEnumConverter))] 202 | public enum DisplayTypePurchaseOrderLineOdooEnum 203 | { 204 | [EnumMember(Value = "line_section")] 205 | Section = 1, 206 | 207 | [EnumMember(Value = "line_note")] 208 | Note = 2, 209 | } 210 | 211 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Models/OdooDictionaryModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using Newtonsoft.Json; 7 | using PortaCapena.OdooJsonRpcClient.Attributes; 8 | using PortaCapena.OdooJsonRpcClient.Converters; 9 | 10 | namespace PortaCapena.OdooJsonRpcClient.Models 11 | { 12 | public class OdooDictionaryModel : Dictionary 13 | { 14 | public string TableName { get; internal set; } 15 | 16 | public OdooDictionaryModel() { } 17 | 18 | public OdooDictionaryModel(string tableName) 19 | { 20 | TableName = tableName; 21 | } 22 | 23 | public static OdooDictionaryModel Create() 24 | { 25 | return new OdooDictionaryModel(); 26 | } 27 | 28 | public static OdooDictionaryModel Create(string tableName) 29 | { 30 | return new OdooDictionaryModel(tableName); 31 | } 32 | 33 | public static OdooDictionaryModel Create() where T : IOdooAtributtesModel, new() 34 | { 35 | return new OdooDictionaryModel(); 36 | } 37 | 38 | public static OdooDictionaryModel Create(Expression> expression) where T : IOdooAtributtesModel, new() 39 | { 40 | return new OdooDictionaryModel().Add(expression); 41 | } 42 | 43 | public static OdooDictionaryModel Create(Expression> expression, object value) where T : IOdooAtributtesModel, new() 44 | { 45 | return new OdooDictionaryModel().Add(expression, value); 46 | } 47 | 48 | //TODO: Rename to set / addOrUpdate ? 49 | 50 | public OdooDictionaryModel Add(Expression> expression, object value) where T : IOdooAtributtesModel 51 | { 52 | if (TableName != null && TryGetOdooTableName(expression, out var tableName)) 53 | TableName = tableName; 54 | this[OdooExpresionMapper.GetOdooPropertyName(expression)] = value; 55 | return this; 56 | } 57 | 58 | public OdooDictionaryModel Add(Expression> expression) where T : IOdooAtributtesModel, new() 59 | { 60 | if (TableName == null && TryGetOdooTableName(expression, out var tableName)) 61 | TableName = tableName; 62 | 63 | AddFromExpresion(expression); 64 | 65 | return this; 66 | } 67 | 68 | protected void AddFromExpresion(Expression> expression) where T : IOdooAtributtesModel, new() 69 | { 70 | if (!(expression.Body is MemberInitExpression body)) throw new ArgumentException("Invalid Func"); 71 | 72 | foreach (var memberExpression in body.Bindings) 73 | { 74 | if (memberExpression is MemberAssignment memberExp) 75 | { 76 | var property = (PropertyInfo)memberExpression.Member; 77 | var attribute = property.GetCustomAttributes(); 78 | 79 | var odooName = attribute.FirstOrDefault()?.PropertyName; 80 | 81 | if (odooName != null) 82 | { 83 | switch (memberExp.Expression) 84 | { 85 | case ConstantExpression constantExpression: 86 | { 87 | var value = constantExpression.Value; 88 | this[odooName] = value; 89 | continue; 90 | } 91 | case MemberExpression memberExpr: 92 | { 93 | var value = Expression.Lambda(memberExpr).Compile().DynamicInvoke(); 94 | this[odooName] = value; 95 | continue; 96 | } 97 | case UnaryExpression unaryExpression: 98 | { 99 | var value = Expression.Lambda(unaryExpression).Compile().DynamicInvoke(); 100 | this[odooName] = value; 101 | continue; 102 | } 103 | case MethodCallExpression methodCallExpression: 104 | { 105 | var value = Expression.Lambda(methodCallExpression).Compile().DynamicInvoke(); 106 | this[odooName] = value; 107 | continue; 108 | } 109 | case NewExpression memberInitExpression: 110 | { 111 | var value = Expression.Lambda(memberInitExpression).Compile().DynamicInvoke(); 112 | this[odooName] = value; 113 | continue; 114 | } 115 | case NewArrayExpression newArrayExpression: 116 | { 117 | var value = Expression.Lambda(newArrayExpression).Compile().DynamicInvoke(); 118 | this[odooName] = value; 119 | continue; 120 | } 121 | } 122 | } 123 | } 124 | throw new ArgumentException("Invalid Func"); 125 | } 126 | } 127 | 128 | 129 | protected static bool TryGetOdooTableName(Expression> expression, out string result) 130 | { 131 | result = null; 132 | var tableNameAttribute = expression.ReturnType.GetCustomAttributes(typeof(OdooTableNameAttribute), true).FirstOrDefault() as OdooTableNameAttribute; 133 | if (tableNameAttribute == null) return false; 134 | 135 | result = tableNameAttribute.Name; 136 | return true; 137 | } 138 | 139 | protected static bool TryGetOdooTableName(Expression> expression, out string result) 140 | { 141 | result = null; 142 | var tableNameAttribute = expression.ReturnType.GetCustomAttributes(typeof(OdooTableNameAttribute), true).FirstOrDefault() as OdooTableNameAttribute; 143 | if (tableNameAttribute == null) return false; 144 | 145 | result = tableNameAttribute.Name; 146 | return true; 147 | } 148 | 149 | protected static bool TryGetOdooTableName(Expression> expression, out string result) 150 | { 151 | result = null; 152 | var tableNameAttribute = expression.ReturnType.GetCustomAttributes(typeof(OdooTableNameAttribute), true).FirstOrDefault() as OdooTableNameAttribute; 153 | if (tableNameAttribute == null) return false; 154 | 155 | result = tableNameAttribute.Name; 156 | return true; 157 | } 158 | } 159 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | TestResults/ 39 | 40 | # NUNIT 41 | *.VisualState.xml 42 | TestResult.xml 43 | 44 | # Build Results of an ATL Project 45 | [Dd]ebugPS/ 46 | [Rr]eleasePS/ 47 | dlldata.c 48 | 49 | # Benchmark Results 50 | BenchmarkDotNet.Artifacts/ 51 | 52 | # .NET Core 53 | project.lock.json 54 | project.fragment.lock.json 55 | artifacts/ 56 | **/Properties/launchSettings.json 57 | 58 | # StyleCop 59 | StyleCopReport.xml 60 | 61 | # Files built by Visual Studio 62 | *_i.c 63 | *_p.c 64 | *_i.h 65 | *.ilk 66 | *.meta 67 | *.obj 68 | *.iobj 69 | *.pch 70 | *.pdb 71 | *.ipdb 72 | *.pgc 73 | *.pgd 74 | *.rsp 75 | *.sbr 76 | *.tlb 77 | *.tli 78 | *.tlh 79 | *.tmp 80 | *.tmp_proj 81 | *.log 82 | *.vspscc 83 | *.vssscc 84 | .builds 85 | *.pidb 86 | *.svclog 87 | *.scc 88 | 89 | # Chutzpah Test files 90 | _Chutzpah* 91 | 92 | # Visual C++ cache files 93 | ipch/ 94 | *.aps 95 | *.ncb 96 | *.opendb 97 | *.opensdf 98 | *.sdf 99 | *.cachefile 100 | *.VC.db 101 | *.VC.VC.opendb 102 | 103 | # Visual Studio profiler 104 | *.psess 105 | *.vsp 106 | *.vspx 107 | *.sap 108 | 109 | # Visual Studio Trace Files 110 | *.e2e 111 | 112 | # TFS 2012 Local Workspace 113 | $tf/ 114 | 115 | # Guidance Automation Toolkit 116 | *.gpState 117 | 118 | # ReSharper is a .NET coding add-in 119 | _ReSharper*/ 120 | *.[Rr]e[Ss]harper 121 | *.DotSettings.user 122 | 123 | # JustCode is a .NET coding add-in 124 | .JustCode 125 | 126 | # TeamCity is a build add-in 127 | _TeamCity* 128 | 129 | # DotCover is a Code Coverage Tool 130 | *.dotCover 131 | 132 | # AxoCover is a Code Coverage Tool 133 | .axoCover/* 134 | !.axoCover/settings.json 135 | 136 | # Visual Studio code coverage results 137 | *.coverage 138 | *.coveragexml 139 | 140 | # NCrunch 141 | _NCrunch_* 142 | .*crunch*.local.xml 143 | nCrunchTemp_* 144 | 145 | # MightyMoose 146 | *.mm.* 147 | AutoTest.Net/ 148 | 149 | # Web workbench (sass) 150 | .sass-cache/ 151 | 152 | # Installshield output folder 153 | [Ee]xpress/ 154 | 155 | # DocProject is a documentation generator add-in 156 | DocProject/buildhelp/ 157 | DocProject/Help/*.HxT 158 | DocProject/Help/*.HxC 159 | DocProject/Help/*.hhc 160 | DocProject/Help/*.hhk 161 | DocProject/Help/*.hhp 162 | DocProject/Help/Html2 163 | DocProject/Help/html 164 | 165 | # Click-Once directory 166 | publish/ 167 | 168 | # Publish Web Output 169 | *.[Pp]ublish.xml 170 | *.azurePubxml 171 | # Note: Comment the next line if you want to checkin your web deploy settings, 172 | # but database connection strings (with potential passwords) will be unencrypted 173 | *.pubxml 174 | *.publishproj 175 | 176 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 177 | # checkin your Azure Web App publish settings, but sensitive information contained 178 | # in these scripts will be unencrypted 179 | PublishScripts/ 180 | 181 | # NuGet Packages 182 | *.nupkg 183 | # The packages folder can be ignored because of Package Restore 184 | **/[Pp]ackages/* 185 | # except build/, which is used as an MSBuild target. 186 | !**/[Pp]ackages/build/ 187 | # Uncomment if necessary however generally it will be regenerated when needed 188 | #!**/[Pp]ackages/repositories.config 189 | # NuGet v3's project.json files produces more ignorable files 190 | *.nuget.props 191 | *.nuget.targets 192 | 193 | # Microsoft Azure Build Output 194 | csx/ 195 | *.build.csdef 196 | 197 | # Microsoft Azure Emulator 198 | ecf/ 199 | rcf/ 200 | 201 | # Windows Store app package directories and files 202 | AppPackages/ 203 | BundleArtifacts/ 204 | Package.StoreAssociation.xml 205 | _pkginfo.txt 206 | *.appx 207 | 208 | # Visual Studio cache files 209 | # files ending in .cache can be ignored 210 | *.[Cc]ache 211 | # but keep track of directories ending in .cache 212 | !*.[Cc]ache/ 213 | 214 | # Others 215 | ClientBin/ 216 | ~$* 217 | *~ 218 | *.dbmdl 219 | *.dbproj.schemaview 220 | *.jfm 221 | *.pfx 222 | *.publishsettings 223 | orleans.codegen.cs 224 | 225 | # Including strong name files can present a security risk 226 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 227 | #*.snk 228 | 229 | # Since there are multiple workflows, uncomment next line to ignore bower_components 230 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 231 | #bower_components/ 232 | 233 | # RIA/Silverlight projects 234 | Generated_Code/ 235 | 236 | # Backup & report files from converting an old project file 237 | # to a newer Visual Studio version. Backup files are not needed, 238 | # because we have git ;-) 239 | _UpgradeReport_Files/ 240 | Backup*/ 241 | UpgradeLog*.XML 242 | UpgradeLog*.htm 243 | ServiceFabricBackup/ 244 | *.rptproj.bak 245 | 246 | # SQL Server files 247 | *.mdf 248 | *.ldf 249 | *.ndf 250 | 251 | # Business Intelligence projects 252 | *.rdl.data 253 | *.bim.layout 254 | *.bim_*.settings 255 | *.rptproj.rsuser 256 | 257 | # Microsoft Fakes 258 | FakesAssemblies/ 259 | 260 | # GhostDoc plugin setting file 261 | *.GhostDoc.xml 262 | 263 | # Node.js Tools for Visual Studio 264 | .ntvs_analysis.dat 265 | node_modules/ 266 | 267 | # Visual Studio 6 build log 268 | *.plg 269 | 270 | # Visual Studio 6 workspace options file 271 | *.opt 272 | 273 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 274 | *.vbw 275 | 276 | # Visual Studio LightSwitch build output 277 | **/*.HTMLClient/GeneratedArtifacts 278 | **/*.DesktopClient/GeneratedArtifacts 279 | **/*.DesktopClient/ModelManifest.xml 280 | **/*.Server/GeneratedArtifacts 281 | **/*.Server/ModelManifest.xml 282 | _Pvt_Extensions 283 | 284 | # Paket dependency manager 285 | .paket/paket.exe 286 | paket-files/ 287 | 288 | # FAKE - F# Make 289 | .fake/ 290 | 291 | # JetBrains Rider 292 | .idea/ 293 | *.sln.iml 294 | 295 | # CodeRush 296 | .cr/ 297 | 298 | # Python Tools for Visual Studio (PTVS) 299 | __pycache__/ 300 | *.pyc 301 | 302 | # Cake - Uncomment if you are using it 303 | # tools/** 304 | # !tools/packages.config 305 | 306 | # Tabs Studio 307 | *.tss 308 | 309 | # Telerik's JustMock configuration file 310 | *.jmconfig 311 | 312 | # BizTalk build output 313 | *.btp.cs 314 | *.btm.cs 315 | *.odx.cs 316 | *.xsd.cs 317 | 318 | # OpenCover UI analysis results 319 | OpenCover/ 320 | 321 | # Azure Stream Analytics local run output 322 | ASALocalRun/ 323 | 324 | # MSBuild Binary and Structured Log 325 | *.binlog 326 | 327 | # NVidia Nsight GPU debugger configuration file 328 | *.nvuser 329 | 330 | # MFractors (Xamarin productivity tool) working folder 331 | .mfractor/ 332 | 333 | # Unignrore tachinoq nuget 334 | !*/TachioniqNuget/* 335 | 336 | # Unignrore tachinoq nuget 337 | !Api/packages/PortaCapena.OdooXmlRpcClient.1.0.0.nupkg 338 | -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Request/OdooRequestModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Newtonsoft.Json; 5 | using PortaCapena.OdooJsonRpcClient.Consts; 6 | using PortaCapena.OdooJsonRpcClient.Models; 7 | 8 | namespace PortaCapena.OdooJsonRpcClient.Request 9 | { 10 | public class OdooRequestModel 11 | { 12 | [JsonProperty("id")] 13 | public int Id { get; } 14 | 15 | [JsonProperty("jsonrpc")] 16 | public string Jsonrpc { get; } 17 | 18 | [JsonProperty("method")] 19 | public string Method { get; } 20 | 21 | [JsonProperty("params")] 22 | public OdooRequestParams Params { get; } 23 | 24 | 25 | public OdooRequestModel(OdooRequestParams requestParams) : this("2.0", "call", requestParams) 26 | { 27 | } 28 | public OdooRequestModel(string jsonrpcVersion, string method, OdooRequestParams requestParams) : this(new Random().Next(0, 1000000000), jsonrpcVersion, method, requestParams) 29 | { 30 | } 31 | public OdooRequestModel(int id, string jsonrpcVersion, string method, OdooRequestParams requestParams) 32 | { 33 | this.Id = id; 34 | this.Jsonrpc = jsonrpcVersion; 35 | this.Method = method; 36 | this.Params = requestParams; 37 | } 38 | 39 | public static OdooRequestModel Version(OdooConfig config) 40 | { 41 | var param = new OdooRequestParams(config.ApiUrlJson, "common", "version"); 42 | return new OdooRequestModel(param); 43 | } 44 | public static OdooRequestModel Login(OdooConfig config) 45 | { 46 | var param = new OdooRequestParams(config.ApiUrlJson, "common", "login", config.DbName, config.UserName, config.Password); 47 | return new OdooRequestModel(param); 48 | } 49 | public static OdooRequestModel ModelFields(OdooConfig config, int uid, string tableName) 50 | { 51 | var param = new OdooRequestParams(config.ApiUrlJson, "object", "execute", config.DbName, uid, config.Password, tableName, OdooOperation.FieldsGet); 52 | return new OdooRequestModel(param); 53 | } 54 | 55 | public static OdooRequestModel Search(OdooConfig config, int uid, string tableName, OdooQuery query = null, OdooContext context = null) 56 | { 57 | var param = new OdooRequestParams(config.ApiUrlJson, "object", "execute_kw", config.DbName, uid, config.Password, tableName, OdooOperation.Search, GetRequestFilters(query), MapQuery(context, query)); 58 | return new OdooRequestModel(param); 59 | } 60 | public static OdooRequestModel SearchRead(OdooConfig config, int uid, string tableName, OdooQuery query = null, OdooContext context = null) 61 | { 62 | var param = new OdooRequestParams(config.ApiUrlJson, "object", "execute_kw", config.DbName, uid, config.Password, tableName, OdooOperation.SearchRead, GetRequestFilters(query), MapQuery(context, query)); 63 | return new OdooRequestModel(param); 64 | } 65 | public static OdooRequestModel Read(OdooConfig config, int uid, string tableName, long[] ids, OdooQuery query = null, OdooContext context = null) 66 | { 67 | var param = new OdooRequestParams(config.ApiUrlJson, "object", "execute_kw", config.DbName, uid, config.Password, tableName, OdooOperation.Read, ids, MapQuery(context, query)); 68 | return new OdooRequestModel(param); 69 | } 70 | 71 | public static OdooRequestModel SearchCount(OdooConfig config, int uid, string tableName, OdooQuery query = null, OdooContext context = null) 72 | { 73 | var param = new OdooRequestParams(config.ApiUrlJson, "object", "execute_kw", config.DbName, uid, config.Password, tableName, OdooOperation.SearchCount, GetRequestFilters(query), MapQuery(context, query)); 74 | return new OdooRequestModel(param); 75 | } 76 | 77 | public static OdooRequestModel Create(OdooConfig config, int uid, string tableName, object model, OdooContext context = null) 78 | { 79 | var param = new OdooRequestParams(config.ApiUrlJson, "object", "execute_kw", config.DbName, uid, config.Password, tableName, OdooOperation.Create, new[] { model }, MapQuery(context)); 80 | return new OdooRequestModel(param); 81 | } 82 | public static OdooRequestModel Update(OdooConfig config, int uid, string tableName, long[] ids, object model, OdooContext context = null) 83 | { 84 | var param = new OdooRequestParams(config.ApiUrlJson, "object", "execute_kw", config.DbName, uid, config.Password, tableName, OdooOperation.Update, new []{ids, model}, MapQuery(context)); 85 | return new OdooRequestModel(param); 86 | } 87 | public static OdooRequestModel Delete(OdooConfig config, int uid, string tableName, long[] ids, OdooContext context = null) 88 | { 89 | var param = new OdooRequestParams(config.ApiUrlJson, "object", "execute_kw", config.DbName, uid, config.Password, tableName, OdooOperation.Delete, ids, MapQuery(context)); 90 | return new OdooRequestModel(param); 91 | } 92 | 93 | public static OdooRequestModel Metadata(OdooConfig config, int uid, string tableName, long[] ids, OdooContext context = null) 94 | { 95 | var param = new OdooRequestParams(config.ApiUrlJson, "object", "execute_kw", config.DbName, uid, config.Password, tableName, OdooOperation.GetMetadata, new object[] { ids }, MapQuery(context)); 96 | return new OdooRequestModel(param); 97 | } 98 | public static OdooRequestModel Action(OdooConfig config, int uid, string tableName, string action, object model, OdooContext context = null) 99 | { 100 | var param = new OdooRequestParams(config.ApiUrlJson, "object", "execute_kw", config.DbName, uid, config.Password, tableName, action, new[] { model }, MapQuery(context)); 101 | return new OdooRequestModel(param); 102 | } 103 | public static OdooRequestModel ActionCollection(OdooConfig config, int uid, string tableName, string action, object model, OdooContext context = null) 104 | { 105 | var param = new OdooRequestParams(config.ApiUrlJson, "object", "execute_kw", config.DbName, uid, config.Password, tableName, action, model, MapQuery(context)); 106 | return new OdooRequestModel(param); 107 | } 108 | 109 | protected static Dictionary MapQuery(OdooContext context, OdooQuery query = null) 110 | { 111 | if (query == null && (context == null || !context.Any())) return null; 112 | 113 | var result = new Dictionary(); 114 | 115 | if (context != null && context.Any()) 116 | result["context"] = context; 117 | 118 | if (query == null) return result; 119 | 120 | if (query.Limit.HasValue) 121 | result["limit"] = query.Limit; 122 | 123 | if (!string.IsNullOrWhiteSpace(query.Order)) 124 | result["order"] = query.Order; 125 | 126 | if (query.Offset.HasValue) 127 | result["offset"] = query.Offset; 128 | 129 | if (query.ReturnFields.Any()) 130 | result["fields"] = query.ReturnFields.ToArray(); 131 | 132 | if (!result.Any()) return null; 133 | 134 | return result; 135 | } 136 | 137 | protected static object[] GetRequestFilters(OdooQuery query = null) 138 | { 139 | return query != null && query.Filters.Count > 0 ? new object[] { query.Filters.ToArray() } : new object[] { new object[] { } }; 140 | } 141 | } 142 | } 143 | 144 | -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Tests/OdooQueryOfTTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Linq; 4 | using FluentAssertions; 5 | using Newtonsoft.Json; 6 | using PortaCapena.OdooJsonRpcClient.Consts; 7 | using PortaCapena.OdooJsonRpcClient.Request; 8 | using PortaCapena.OdooJsonRpcClient.Shared.Models; 9 | using Xunit; 10 | 11 | namespace PortaCapena.OdooJsonRpcClient.Tests 12 | { 13 | public class OdooQueryOfTTests 14 | { 15 | [Fact] 16 | public void Can_create_builder_with_static_method() 17 | { 18 | var filters = OdooQuery.Create(); 19 | 20 | filters.Should().NotBeNull(); 21 | filters.Filters.Count.Should().Be(0); 22 | filters.ReturnFields.Count.Should().Be(0); 23 | filters.Limit.Should().BeNull(); 24 | filters.Offset.Should().BeNull(); 25 | } 26 | 27 | 28 | [Fact] 29 | public void When_use_select_shoud_return_filds_with_odoo_prop_names() 30 | { 31 | var filters = OdooQuery.Create() 32 | .Select(x => new 33 | { 34 | x.Name, 35 | x.Description 36 | }); 37 | 38 | filters.ReturnFields.Count.Should().Be(2); 39 | filters.ReturnFields.First().Should().Be("name"); 40 | filters.ReturnFields.ElementAt(1).Should().Be("description"); 41 | 42 | filters.Filters.Count.Should().Be(0); 43 | filters.Limit.Should().BeNull(); 44 | filters.Offset.Should().BeNull(); 45 | } 46 | 47 | [Fact] 48 | public void When_use_pridicate_where_shoud_return_correct_filters_model() 49 | { 50 | var filters = OdooQuery.Create() 51 | .Where(x => x.Id, OdooOperator.EqualsTo, 10); 52 | 53 | filters.Filters.Count.Should().Be(1); 54 | var json = JsonConvert.SerializeObject(filters.Filters); 55 | 56 | json.Should().Be("[[\"id\",\"=\",10]]"); 57 | 58 | filters.ReturnFields.Count.Should().Be(0); 59 | filters.Limit.Should().BeNull(); 60 | filters.Offset.Should().BeNull(); 61 | } 62 | 63 | 64 | [Fact] 65 | public void When_use_pridicate_where_with_two_times_shoud_return_correct_filters_model() 66 | { 67 | var filters = OdooQuery.Create() 68 | .Where(x => x.WriteDate, OdooOperator.GreaterThanOrEqualTo, new DateTime(2020, 12, 2)) 69 | .Where(x => x.Name, OdooOperator.EqualsTo, "Bioboxen 610l") 70 | ; 71 | 72 | filters.Filters.Count.Should().Be(2); 73 | var json = JsonConvert.SerializeObject(filters.Filters); 74 | 75 | json.Should().Be("[[\"write_date\",\">=\",\"2020-12-02T00:00:00\"],[\"name\",\"=\",\"Bioboxen 610l\"]]"); 76 | 77 | filters.ReturnFields.Count.Should().Be(0); 78 | filters.Limit.Should().BeNull(); 79 | filters.Offset.Should().BeNull(); 80 | } 81 | 82 | 83 | [Fact] 84 | public void When_use_where_with_OdooFilter_param_shoud_return_correct_filters_model() 85 | { 86 | 87 | var filters = OdooQuery.Create() 88 | .Where(OdooFilter.Create().GreaterThanOrEqual("write_date", new DateTime(2020, 12, 2))) 89 | .Where(OdooFilter.Create().EqualTo("name", "Bioboxen 610l")); 90 | 91 | filters.Filters.Count.Should().Be(2); 92 | var json = JsonConvert.SerializeObject(filters.Filters); 93 | 94 | json.Should().Be("[[\"write_date\",\">=\",\"2020-12-02T00:00:00\"],[\"name\",\"=\",\"Bioboxen 610l\"]]"); 95 | 96 | filters.ReturnFields.Count.Should().Be(0); 97 | filters.Limit.Should().BeNull(); 98 | filters.Offset.Should().BeNull(); 99 | } 100 | 101 | 102 | [Fact] 103 | public void When_use_where_with_complex_OdooFilter_param_shoud_return_correct_filters_model() 104 | { 105 | var filters = OdooQuery.Create() 106 | .Where(OdooFilter.Create() 107 | .GreaterThanOrEqual("write_date", new DateTime(2020, 12, 2)) 108 | .And() 109 | .EqualTo("name", "Bioboxen 610l")); 110 | 111 | var json = JsonConvert.SerializeObject(filters.Filters); 112 | json.Should().Be("[[\"write_date\",\">=\",\"2020-12-02T00:00:00\"],\"&\",[\"name\",\"=\",\"Bioboxen 610l\"]]"); 113 | 114 | filters.ReturnFields.Count.Should().Be(0); 115 | filters.Limit.Should().BeNull(); 116 | filters.Offset.Should().BeNull(); 117 | } 118 | 119 | 120 | [Fact] 121 | public void When_use_select_with_anymous_model_shoud_return_correct_filters_model() 122 | { 123 | var filters = OdooQuery.Create() 124 | .Select(x => new 125 | { 126 | x.Name, 127 | x.Description, 128 | x.WriteDate 129 | }); 130 | 131 | filters.Limit.Should().BeNull(); 132 | filters.Offset.Should().BeNull(); 133 | filters.Filters.Count.Should().Be(0); 134 | 135 | filters.ReturnFields.Count.Should().Be(3); 136 | 137 | filters.ReturnFields.First().Should().Be("name"); 138 | filters.ReturnFields.Skip(1).First().Should().Be("description"); 139 | filters.ReturnFields.Skip(2).First().Should().Be("write_date"); 140 | } 141 | 142 | [Fact] 143 | public void When_use_pridicate_where_with_one_forgin_key_shoud_return_correct_filters_model() 144 | { 145 | var filters = OdooQuery.Create() 146 | .Where(x => x.CompanyId, x => x.CountryCode, OdooOperator.EqualsTo, "BE"); 147 | 148 | filters.Filters.Count.Should().Be(1); 149 | filters.Filters[0].Should().NotBeNull(); 150 | 151 | var arr = filters.Filters[0] as ArrayList; 152 | arr[0].Should().Be("company_id.country_code"); 153 | arr[1].Should().Be("="); 154 | arr[2].Should().Be("BE"); 155 | 156 | var json = JsonConvert.SerializeObject(filters.Filters); 157 | json.Should().Be("[[\"company_id.country_code\",\"=\",\"BE\"]]"); 158 | 159 | filters.ReturnFields.Count.Should().Be(0); 160 | filters.Limit.Should().BeNull(); 161 | filters.Offset.Should().BeNull(); 162 | } 163 | 164 | [Fact] 165 | public void When_use_pridicate_where_with_two_forgin_key_shoud_return_correct_filters_model() 166 | { 167 | var filters = OdooQuery.Create() 168 | .Where(x => x.PropertyAccountExpenseId, x => x.AccountSaleTaxId, x => x.CountryCode, OdooOperator.EqualsTo, "BE"); 169 | 170 | filters.Filters.Count.Should().Be(1); 171 | filters.Filters[0].Should().NotBeNull(); 172 | 173 | var arr = filters.Filters[0] as ArrayList; 174 | arr[0].Should().Be("property_account_expense_id.account_sale_tax_id.country_code"); 175 | arr[1].Should().Be("="); 176 | arr[2].Should().Be("BE"); 177 | 178 | var json = JsonConvert.SerializeObject(filters.Filters); 179 | json.Should().Be("[[\"property_account_expense_id.account_sale_tax_id.country_code\",\"=\",\"BE\"]]"); 180 | 181 | filters.ReturnFields.Count.Should().Be(0); 182 | filters.Limit.Should().BeNull(); 183 | filters.Offset.Should().BeNull(); 184 | } 185 | } 186 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Request/OdooQueryBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using PortaCapena.OdooJsonRpcClient.Consts; 7 | using PortaCapena.OdooJsonRpcClient.Extensions; 8 | using PortaCapena.OdooJsonRpcClient.Models; 9 | using PortaCapena.OdooJsonRpcClient.Result; 10 | 11 | namespace PortaCapena.OdooJsonRpcClient.Request 12 | { 13 | public class OdooQueryBuilder where T : IOdooModel, new() 14 | { 15 | private readonly OdooClient _odooClient; 16 | private readonly OdooQuery _query; 17 | 18 | private OdooContext _odooContext; 19 | 20 | internal OdooQueryBuilder(OdooClient odooClient) : base() 21 | { 22 | _odooClient = odooClient; 23 | _query = new OdooQuery(); 24 | } 25 | 26 | #region Select 27 | 28 | public OdooQueryBuilder Select(Func selector) 29 | { 30 | _query.Select(selector); 31 | return this; 32 | } 33 | 34 | public OdooQueryBuilder Select(params string[] fields) 35 | { 36 | _query.Select(fields); 37 | return this; 38 | } 39 | 40 | public OdooQueryBuilder SelectSimplifiedModel() 41 | { 42 | _query.SelectSimplifiedModel(); 43 | return this; 44 | } 45 | 46 | #endregion 47 | 48 | #region Where 49 | public OdooQueryBuilder Where(OdooFilter filter) 50 | { 51 | _query.Where(filter); 52 | return this; 53 | } 54 | public OdooQueryBuilder Where(Expression> expression, OdooOperator odooOperator, object value) 55 | { 56 | _query.Where(expression, odooOperator, value); 57 | return this; 58 | } 59 | public OdooQueryBuilder Where(Expression> expression, Expression> expressionForeignKeyLevel1, OdooOperator odooOperator, object value) where TForeignKeyLevel1 : IOdooModel, new() 60 | { 61 | _query.Where(expression, expressionForeignKeyLevel1, odooOperator, value); 62 | return this; 63 | } 64 | public OdooQueryBuilder Where(Expression> expression, Expression> expressionForeignKeyLevel1, OdooOperator odooOperator, object value) where TForeignKeyLevel1 : IOdooModel, new() 65 | { 66 | _query.Where(expression, expressionForeignKeyLevel1, odooOperator, value); 67 | return this; 68 | } 69 | 70 | public OdooQueryBuilder Where(Expression> expression, Expression> expressionForeignKeyLevel1, Expression> expressionForeignKeyLevel2, OdooOperator odooOperator, object value) where TForeignKeyLevel1 : IOdooModel, new() where TForeignKeyLevel2 : IOdooModel, new() 71 | { 72 | _query.Where(expression, expressionForeignKeyLevel1, expressionForeignKeyLevel2, odooOperator, value); 73 | return this; 74 | } 75 | public OdooQueryBuilder Where(Expression> expression, Expression> expressionForeignKeyLevel1, Expression> expressionForeignKeyLevel2, OdooOperator odooOperator, object value) where TForeignKeyLevel1 : IOdooModel, new() where TForeignKeyLevel2 : IOdooModel, new() 76 | { 77 | _query.Where(expression, expressionForeignKeyLevel1, expressionForeignKeyLevel2, odooOperator, value); 78 | return this; 79 | } 80 | public OdooQueryBuilder Where(Expression> expression, Expression> expressionForeignKeyLevel1, Expression> expressionForeignKeyLevel2, OdooOperator odooOperator, object value) where TForeignKeyLevel1 : IOdooModel, new() where TForeignKeyLevel2 : IOdooModel, new() 81 | { 82 | _query.Where(expression, expressionForeignKeyLevel1, expressionForeignKeyLevel2, odooOperator, value); 83 | return this; 84 | } 85 | public OdooQueryBuilder Where(Expression> expression, Expression> expressionForeignKeyLevel1, Expression> expressionForeignKeyLevel2, OdooOperator odooOperator, object value) where TForeignKeyLevel1 : IOdooModel, new() where TForeignKeyLevel2 : IOdooModel, new() 86 | { 87 | _query.Where(expression, expressionForeignKeyLevel1, expressionForeignKeyLevel2, odooOperator, value); 88 | return this; 89 | } 90 | 91 | public OdooQueryBuilder ById(long id) 92 | { 93 | _query.ById(id); 94 | return this; 95 | } 96 | public OdooQueryBuilder ByIds(params long[] ids) 97 | { 98 | _query.ByIds(ids); 99 | return this; 100 | } 101 | 102 | public OdooQueryBuilder WithContext(OdooContext context) 103 | { 104 | _odooContext = new OdooContext(context); 105 | return this; 106 | } 107 | public OdooQueryBuilder WithContext(string key, object value) 108 | { 109 | if (_odooContext == null) _odooContext = new OdooContext(); 110 | _odooContext[key] = value; 111 | return this; 112 | } 113 | 114 | #endregion 115 | 116 | #region Skip 117 | 118 | public OdooQueryBuilder Take(int limit) 119 | { 120 | _query.Take(limit); 121 | return this; 122 | } 123 | public OdooQueryBuilder Skip(int offset) 124 | { 125 | _query.Skip(offset); 126 | return this; 127 | } 128 | 129 | #endregion 130 | 131 | #region Order 132 | 133 | public OdooQueryBuilder OrderBy(Expression> expression) 134 | { 135 | _query.OrderBy(expression); 136 | return this; 137 | } 138 | public OdooQueryBuilder OrderByDescending(Expression> expression) 139 | { 140 | _query.OrderByDescending(expression); 141 | return this; 142 | } 143 | public OdooQueryBuilder ThenOrderBy(Expression> expression) 144 | { 145 | _query.ThenOrderBy(expression); 146 | return this; 147 | } 148 | public OdooQueryBuilder ThenOrderByDescending(Expression> expression) 149 | { 150 | _query.ThenOrderByDescending(expression); 151 | return this; 152 | } 153 | 154 | #endregion 155 | 156 | 157 | public async Task> ToListAsync(CancellationToken cancellationToken = default) 158 | { 159 | return await _odooClient.GetAsync(_query, _odooContext, cancellationToken); 160 | } 161 | 162 | public async Task> FirstAsync(CancellationToken cancellationToken = default) 163 | { 164 | Take(1); 165 | var result = await ToListAsync(cancellationToken); 166 | return result.Succeed ? result.ToResult(result.Value.First()) : OdooResult.FailedResult(result); 167 | } 168 | 169 | public async Task> FirstOrDefaultAsync(CancellationToken cancellationToken = default) 170 | { 171 | Take(1); 172 | var result = await ToListAsync(cancellationToken); 173 | return result.Succeed ? result.ToResult(result.Value.FirstOrDefault()) : OdooResult.FailedResult(result); 174 | } 175 | 176 | public async Task> SingleAsync(CancellationToken cancellationToken = default) 177 | { 178 | Take(2); 179 | var result = await ToListAsync(cancellationToken); 180 | return result.Succeed ? result.ToResult(result.Value.Single()) : OdooResult.FailedResult(result); 181 | } 182 | 183 | public async Task> CountAsync(CancellationToken cancellationToken = default) 184 | { 185 | return await _odooClient.GetCountAsync(_query, _odooContext, cancellationToken); 186 | } 187 | public async Task> AnyAsync(CancellationToken cancellationToken = default) 188 | { 189 | Take(1); 190 | var result = await ToListAsync(cancellationToken); 191 | return result.Succeed ? result.ToResult(result.Value.Any()) : OdooResult.FailedResult(result); 192 | } 193 | } 194 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient/Request/OdooQueryOfT.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using PortaCapena.OdooJsonRpcClient.Consts; 7 | using PortaCapena.OdooJsonRpcClient.Converters; 8 | using PortaCapena.OdooJsonRpcClient.Extensions; 9 | using PortaCapena.OdooJsonRpcClient.Models; 10 | using PortaCapena.OdooJsonRpcClient.Utils; 11 | 12 | namespace PortaCapena.OdooJsonRpcClient.Request 13 | { 14 | public class OdooQuery : OdooQuery where T : IOdooModel, new() 15 | { 16 | public OdooQuery() : base() 17 | { 18 | base.Filters = new OdooFilter(); 19 | } 20 | 21 | public new OdooFilter Filters => base.Filters as OdooFilter; 22 | 23 | public static OdooQuery Create() 24 | { 25 | return new OdooQuery(); 26 | } 27 | 28 | 29 | #region Select 30 | 31 | public OdooQuery Select(Func selector) 32 | { 33 | var model = selector.Invoke(new T()); 34 | PropertyInfo[] modelProps = model.GetType().GetProperties(); 35 | 36 | return Select(modelProps.Select(x => OdooExtensions.GetOdooPropertyName(x.Name)).ToArray()); 37 | } 38 | public OdooQuery Select(params string[] fields) 39 | { 40 | foreach (var f in fields) 41 | ReturnFields.Add(f); 42 | 43 | return this; 44 | } 45 | 46 | public OdooQuery SelectSimplifiedModel() 47 | { 48 | PropertyInfo[] modelProps = typeof(T).GetProperties(); 49 | return Select(modelProps.Select(x => OdooExtensions.GetOdooPropertyName(x.Name)).ToArray()); 50 | } 51 | 52 | #endregion 53 | 54 | #region Where 55 | public OdooQuery Where(OdooFilter filter) 56 | { 57 | Filters.AddRange(filter); 58 | return this; 59 | } 60 | public OdooQuery Where(Expression> expression, OdooOperator odooOperator, object value) 61 | { 62 | Filters.Add(OdooFilterMapper.ToOdooExpresion(OdooExpresionMapper.GetPropertyName(expression), odooOperator, value)); 63 | return this; 64 | } 65 | 66 | public OdooQuery Where(Expression> expression, Expression> expressionForeignKeyLevel1, OdooOperator odooOperator, object value) where TForeignKeyLevel1 : IOdooModel, new() 67 | { 68 | var fields = OdooExtensions.GetOdooPropertyName(OdooExpresionMapper.GetPropertyName(expression)) + '.' + 69 | OdooExtensions.GetOdooPropertyName(OdooExpresionMapper.GetPropertyName(expressionForeignKeyLevel1)); 70 | 71 | Filters.Add(new OdooFilter { fields, odooOperator.Description(), value }); 72 | return this; 73 | } 74 | public OdooQuery Where(Expression> expression, Expression> expressionForeignKeyLevel1, OdooOperator odooOperator, object value) where TForeignKeyLevel1 : IOdooModel, new() 75 | { 76 | var fields = OdooExtensions.GetOdooPropertyName(OdooExpresionMapper.GetPropertyName(expression)) + '.' + 77 | OdooExtensions.GetOdooPropertyName(OdooExpresionMapper.GetPropertyName(expressionForeignKeyLevel1)); 78 | 79 | Filters.Add(new OdooFilter { fields, odooOperator.Description(), value }); 80 | return this; 81 | } 82 | 83 | public OdooQuery Where(Expression> expression, Expression> expressionForeignKeyLevel1, Expression> expressionForeignKeyLevel2, OdooOperator odooOperator, object value) where TForeignKeyLevel1 : IOdooModel, new() where TForeignKeyLevel2 : IOdooModel, new() 84 | { 85 | var fields = OdooExtensions.GetOdooPropertyName(OdooExpresionMapper.GetPropertyName(expression)) + '.' + 86 | OdooExtensions.GetOdooPropertyName(OdooExpresionMapper.GetPropertyName(expressionForeignKeyLevel1)) + '.' + 87 | OdooExtensions.GetOdooPropertyName(OdooExpresionMapper.GetPropertyName(expressionForeignKeyLevel2)); 88 | 89 | Filters.Add(new OdooFilter { fields, odooOperator.Description(), value }); 90 | return this; 91 | } 92 | public OdooQuery Where(Expression> expression, Expression> expressionForeignKeyLevel1, Expression> expressionForeignKeyLevel2, OdooOperator odooOperator, object value) where TForeignKeyLevel1 : IOdooModel, new() where TForeignKeyLevel2 : IOdooModel, new() 93 | { 94 | var fields = OdooExtensions.GetOdooPropertyName(OdooExpresionMapper.GetPropertyName(expression)) + '.' + 95 | OdooExtensions.GetOdooPropertyName(OdooExpresionMapper.GetPropertyName(expressionForeignKeyLevel1)) + '.' + 96 | OdooExtensions.GetOdooPropertyName(OdooExpresionMapper.GetPropertyName(expressionForeignKeyLevel2)); 97 | 98 | Filters.Add(new OdooFilter { fields, odooOperator.Description(), value }); 99 | return this; 100 | } 101 | public OdooQuery Where(Expression> expression, Expression> expressionForeignKeyLevel1, Expression> expressionForeignKeyLevel2, OdooOperator odooOperator, object value) where TForeignKeyLevel1 : IOdooModel, new() where TForeignKeyLevel2 : IOdooModel, new() 102 | { 103 | var fields = OdooExtensions.GetOdooPropertyName(OdooExpresionMapper.GetPropertyName(expression)) + '.' + 104 | OdooExtensions.GetOdooPropertyName(OdooExpresionMapper.GetPropertyName(expressionForeignKeyLevel1)) + '.' + 105 | OdooExtensions.GetOdooPropertyName(OdooExpresionMapper.GetPropertyName(expressionForeignKeyLevel2)); 106 | 107 | Filters.Add(new OdooFilter { fields, odooOperator.Description(), value }); 108 | return this; 109 | } 110 | public OdooQuery Where(Expression> expression, Expression> expressionForeignKeyLevel1, Expression> expressionForeignKeyLevel2, OdooOperator odooOperator, object value) where TForeignKeyLevel1 : IOdooModel, new() where TForeignKeyLevel2 : IOdooModel, new() 111 | { 112 | var fields = OdooExtensions.GetOdooPropertyName(OdooExpresionMapper.GetPropertyName(expression)) + '.' + 113 | OdooExtensions.GetOdooPropertyName(OdooExpresionMapper.GetPropertyName(expressionForeignKeyLevel1)) + '.' + 114 | OdooExtensions.GetOdooPropertyName(OdooExpresionMapper.GetPropertyName(expressionForeignKeyLevel2)); 115 | 116 | Filters.Add(new OdooFilter { fields, odooOperator.Description(), value }); 117 | return this; 118 | } 119 | 120 | 121 | public OdooQuery ById(long id) 122 | { 123 | Filters.EqualTo(x => x.Id, id); 124 | return this; 125 | } 126 | public OdooQuery ByIds(params long[] ids) 127 | { 128 | if (ids.Any()) Filters.In(x => x.Id, ids); 129 | return this; 130 | } 131 | 132 | #endregion 133 | 134 | #region Skip 135 | 136 | public OdooQuery Take(int limit) 137 | { 138 | this.Limit = limit; 139 | return this; 140 | } 141 | public OdooQuery Skip(int offset) 142 | { 143 | Offset = offset; 144 | return this; 145 | } 146 | 147 | #endregion 148 | 149 | #region Order 150 | 151 | public OdooQuery OrderBy(Expression> expression) 152 | { 153 | var odooPropertyName = OdooExpresionMapper.GetOdooPropertyName(expression); 154 | this.Order = $"{odooPropertyName} ASC"; 155 | return this; 156 | } 157 | public OdooQuery OrderByDescending(Expression> expression) 158 | { 159 | var odooPropertyName = OdooExpresionMapper.GetOdooPropertyName(expression); 160 | this.Order = $"{odooPropertyName} DESC"; 161 | return this; 162 | } 163 | public OdooQuery ThenOrderBy(Expression> expression) 164 | { 165 | var odooPropertyName = OdooExpresionMapper.GetOdooPropertyName(expression); 166 | this.Order += $", {odooPropertyName} ASC"; 167 | return this; 168 | } 169 | public OdooQuery ThenOrderByDescending(Expression> expression) 170 | { 171 | var odooPropertyName = OdooExpresionMapper.GetOdooPropertyName(expression); 172 | this.Order += $", {odooPropertyName} DESC"; 173 | return this; 174 | } 175 | 176 | #endregion 177 | 178 | } 179 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Shared/Models/SaleOrderLineOdooDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using PortaCapena.OdooJsonRpcClient.Attributes; 4 | using PortaCapena.OdooJsonRpcClient.Converters; 5 | using PortaCapena.OdooJsonRpcClient.Models; 6 | 7 | namespace PortaCapena.OdooJsonRpcClient.Shared.Models 8 | { 9 | [OdooTableName("sale.order.line")] 10 | [JsonConverter(typeof(OdooModelConverter))] 11 | public class SaleOrderLineOdooDto : IOdooModel 12 | { 13 | 14 | // sale.order 15 | [JsonProperty("order_id")] 16 | public long OrderId { get; set; } 17 | 18 | [JsonProperty("name")] 19 | public string Name { get; set; } 20 | 21 | [JsonProperty("sequence")] 22 | public int? Sequence { get; set; } 23 | 24 | // account.move.line 25 | [JsonProperty("invoice_lines")] 26 | public long[] InvoiceLines { get; set; } 27 | 28 | [JsonProperty("invoice_status")] 29 | public string InvoiceStatus { get; set; } 30 | 31 | [JsonProperty("price_unit")] 32 | public double PriceUnit { get; set; } 33 | 34 | [JsonProperty("price_subtotal")] 35 | public decimal? PriceSubtotal { get; set; } 36 | 37 | [JsonProperty("price_tax")] 38 | public double? PriceTax { get; set; } 39 | 40 | [JsonProperty("price_total")] 41 | public decimal? PriceTotal { get; set; } 42 | 43 | [JsonProperty("price_reduce")] 44 | public double? PriceReduce { get; set; } 45 | 46 | // account.tax 47 | [JsonProperty("tax_id")] 48 | public long[] TaxId { get; set; } 49 | 50 | [JsonProperty("price_reduce_taxinc")] 51 | public decimal? PriceReduceTaxinc { get; set; } 52 | 53 | [JsonProperty("price_reduce_taxexcl")] 54 | public decimal? PriceReduceTaxexcl { get; set; } 55 | 56 | [JsonProperty("discount")] 57 | public double? Discount { get; set; } 58 | 59 | // product.product 60 | [JsonProperty("product_id")] 61 | public long? ProductId { get; set; } 62 | 63 | // product.template 64 | [JsonProperty("product_template_id")] 65 | public long? ProductTemplateId { get; set; } 66 | 67 | [JsonProperty("product_updatable")] 68 | public bool? ProductUpdatable { get; set; } 69 | 70 | [JsonProperty("product_uom_qty")] 71 | public double ProductUomQty { get; set; } 72 | 73 | // uom.uom 74 | [JsonProperty("product_uom")] 75 | public long? ProductUom { get; set; } 76 | 77 | // uom.category 78 | [JsonProperty("product_uom_category_id")] 79 | public long? ProductUomCategoryId { get; set; } 80 | 81 | [JsonProperty("product_uom_readonly")] 82 | public bool? ProductUomReadonly { get; set; } 83 | 84 | // product.attribute.custom.value 85 | [JsonProperty("product_custom_attribute_value_ids")] 86 | public long[] ProductCustomAttributeValueIds { get; set; } 87 | 88 | // product.template.attribute.value 89 | [JsonProperty("product_no_variant_attribute_value_ids")] 90 | public long[] ProductNoVariantAttributeValueIds { get; set; } 91 | 92 | [JsonProperty("qty_delivered")] 93 | public double? QtyDelivered { get; set; } 94 | 95 | [JsonProperty("qty_delivered_manual")] 96 | public double? QtyDeliveredManual { get; set; } 97 | 98 | [JsonProperty("qty_to_invoice")] 99 | public double? QtyToInvoice { get; set; } 100 | 101 | [JsonProperty("qty_invoiced")] 102 | public double? QtyInvoiced { get; set; } 103 | 104 | [JsonProperty("untaxed_amount_invoiced")] 105 | public decimal? UntaxedAmountInvoiced { get; set; } 106 | 107 | [JsonProperty("untaxed_amount_to_invoice")] 108 | public decimal? UntaxedAmountToInvoice { get; set; } 109 | 110 | // res.users 111 | [JsonProperty("salesman_id")] 112 | public long? SalesmanId { get; set; } 113 | 114 | // res.currency 115 | [JsonProperty("currency_id")] 116 | public long? CurrencyId { get; set; } 117 | 118 | // res.company 119 | [JsonProperty("company_id")] 120 | public long? CompanyId { get; set; } 121 | 122 | // res.partner 123 | [JsonProperty("order_partner_id")] 124 | public long? OrderPartnerId { get; set; } 125 | 126 | // account.analytic.tag 127 | [JsonProperty("analytic_tag_ids")] 128 | public long[] AnalyticTagIds { get; set; } 129 | 130 | // account.analytic.line 131 | [JsonProperty("analytic_line_ids")] 132 | public long[] AnalyticLineIds { get; set; } 133 | 134 | [JsonProperty("is_expense")] 135 | public bool? IsExpense { get; set; } 136 | 137 | [JsonProperty("is_downpayment")] 138 | public bool? IsDownpayment { get; set; } 139 | 140 | [JsonProperty("state")] 141 | public string State { get; set; } 142 | 143 | [JsonProperty("customer_lead")] 144 | public double CustomerLead { get; set; } 145 | 146 | [JsonProperty("display_type")] 147 | public string DisplayType { get; set; } 148 | 149 | // sale.order.option 150 | [JsonProperty("sale_order_option_ids")] 151 | public long[] SaleOrderOptionIds { get; set; } 152 | 153 | // purchase.order.line 154 | [JsonProperty("purchase_line_ids")] 155 | public long[] PurchaseLineIds { get; set; } 156 | 157 | [JsonProperty("purchase_line_count")] 158 | public int? PurchaseLineCount { get; set; } 159 | 160 | [JsonProperty("is_rental")] 161 | public bool? IsRental { get; set; } 162 | 163 | [JsonProperty("qty_returned")] 164 | public double? QtyReturned { get; set; } 165 | 166 | [JsonProperty("pickup_date")] 167 | public DateTime? PickupDate { get; set; } 168 | 169 | [JsonProperty("return_date")] 170 | public DateTime? ReturnDate { get; set; } 171 | 172 | [JsonProperty("reservation_begin")] 173 | public DateTime? ReservationBegin { get; set; } 174 | 175 | [JsonProperty("is_late")] 176 | public bool? IsLate { get; set; } 177 | 178 | [JsonProperty("is_product_rentable")] 179 | public bool? IsProductRentable { get; set; } 180 | 181 | [JsonProperty("rental_updatable")] 182 | public bool? RentalUpdatable { get; set; } 183 | 184 | [JsonProperty("qty_delivered_method")] 185 | public string QtyDeliveredMethod { get; set; } 186 | 187 | // product.packaging 188 | [JsonProperty("product_packaging")] 189 | public long? ProductPackaging { get; set; } 190 | 191 | // stock.location.route 192 | [JsonProperty("route_id")] 193 | public long? RouteId { get; set; } 194 | 195 | // stock.move 196 | [JsonProperty("move_ids")] 197 | public long[] MoveIds { get; set; } 198 | 199 | [JsonProperty("product_type")] 200 | public string ProductType { get; set; } 201 | 202 | [JsonProperty("virtual_available_at_date")] 203 | public double? VirtualAvailableAtDate { get; set; } 204 | 205 | [JsonProperty("scheduled_date")] 206 | public DateTime? ScheduledDate { get; set; } 207 | 208 | [JsonProperty("forecast_expected_date")] 209 | public DateTime? ForecastExpectedDate { get; set; } 210 | 211 | [JsonProperty("free_qty_today")] 212 | public double? FreeQtyToday { get; set; } 213 | 214 | [JsonProperty("qty_available_today")] 215 | public double? QtyAvailableToday { get; set; } 216 | 217 | // stock.warehouse 218 | [JsonProperty("warehouse_id")] 219 | public long? WarehouseId { get; set; } 220 | 221 | [JsonProperty("qty_to_deliver")] 222 | public double? QtyToDeliver { get; set; } 223 | 224 | [JsonProperty("is_mto")] 225 | public bool? IsMto { get; set; } 226 | 227 | [JsonProperty("display_qty_widget")] 228 | public bool? DisplayQtyWidget { get; set; } 229 | 230 | [JsonProperty("tracking")] 231 | public string Tracking { get; set; } 232 | 233 | // stock.production.lot 234 | [JsonProperty("reserved_lot_ids")] 235 | public long[] ReservedLotIds { get; set; } 236 | 237 | // stock.production.lot 238 | [JsonProperty("pickedup_lot_ids")] 239 | public long[] PickedupLotIds { get; set; } 240 | 241 | // stock.production.lot 242 | [JsonProperty("returned_lot_ids")] 243 | public long[] ReturnedLotIds { get; set; } 244 | 245 | // stock.production.lot 246 | [JsonProperty("unavailable_lot_ids")] 247 | public long[] UnavailableLotIds { get; set; } 248 | 249 | // sale.subscription 250 | [JsonProperty("subscription_id")] 251 | public long? SubscriptionId { get; set; } 252 | 253 | [JsonProperty("id")] 254 | public long Id { get; set; } 255 | 256 | [JsonProperty("display_name")] 257 | public string DisplayName { get; set; } 258 | 259 | // res.users 260 | [JsonProperty("create_uid")] 261 | public long? CreateUid { get; set; } 262 | 263 | [JsonProperty("create_date")] 264 | public DateTime? CreateDate { get; set; } 265 | 266 | // res.users 267 | [JsonProperty("write_uid")] 268 | public long? WriteUid { get; set; } 269 | 270 | [JsonProperty("write_date")] 271 | public DateTime? WriteDate { get; set; } 272 | 273 | [JsonProperty("__last_update")] 274 | public DateTime? LastUpdate { get; set; } 275 | } 276 | 277 | } -------------------------------------------------------------------------------- /PortaCapena.OdooJsonRpcClient.Shared/Models/PurchaseOrderOdooModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Converters; 5 | using PortaCapena.OdooJsonRpcClient.Attributes; 6 | using PortaCapena.OdooJsonRpcClient.Converters; 7 | using PortaCapena.OdooJsonRpcClient.Models; 8 | 9 | namespace PortaCapena.OdooJsonRpcClient.Shared.Models 10 | { 11 | [OdooTableName("purchase.order")] 12 | [JsonConverter(typeof(OdooModelConverter))] 13 | public class PurchaseOrderOdooModel : IOdooModel 14 | { 15 | 16 | // required 17 | [JsonProperty("name")] 18 | public string Name { get; set; } 19 | 20 | [JsonProperty("priority")] 21 | public PriorityPurchaseOrderOdooEnum? Priority { get; set; } 22 | 23 | [JsonProperty("origin")] 24 | public string Origin { get; set; } 25 | 26 | [JsonProperty("partner_ref")] 27 | public string PartnerRef { get; set; } 28 | 29 | // required 30 | [JsonProperty("date_order")] 31 | public DateTime DateOrder { get; set; } 32 | 33 | [JsonProperty("date_approve")] 34 | public DateTime? DateApprove { get; set; } 35 | 36 | // res.partner 37 | // required 38 | [JsonProperty("partner_id")] 39 | public long PartnerId { get; set; } 40 | 41 | // res.partner 42 | [JsonProperty("dest_address_id")] 43 | public long? DestAddressId { get; set; } 44 | 45 | // res.currency 46 | // required 47 | [JsonProperty("currency_id")] 48 | public long CurrencyId { get; set; } 49 | 50 | [JsonProperty("state")] 51 | public StatusPurchaseOrderOdooEnum? State { get; set; } 52 | 53 | // purchase.order.line 54 | [JsonProperty("order_line")] 55 | public long[] OrderLine { get; set; } 56 | 57 | [JsonProperty("notes")] 58 | public string Notes { get; set; } 59 | 60 | [JsonProperty("invoice_count")] 61 | public int? InvoiceCount { get; set; } 62 | 63 | // account.move 64 | [JsonProperty("invoice_ids")] 65 | public long[] InvoiceIds { get; set; } 66 | 67 | [JsonProperty("invoice_status")] 68 | public BillingStatusPurchaseOrderOdooEnum? InvoiceStatus { get; set; } 69 | 70 | [JsonProperty("date_planned")] 71 | public DateTime? DatePlanned { get; set; } 72 | 73 | [JsonProperty("date_calendar_start")] 74 | public DateTime? DateCalendarStart { get; set; } 75 | 76 | [JsonProperty("amount_untaxed")] 77 | public decimal? AmountUntaxed { get; set; } 78 | 79 | [JsonProperty("amount_tax")] 80 | public decimal? AmountTax { get; set; } 81 | 82 | [JsonProperty("amount_total")] 83 | public decimal? AmountTotal { get; set; } 84 | 85 | // account.fiscal.position 86 | [JsonProperty("fiscal_position_id")] 87 | public long? FiscalPositionId { get; set; } 88 | 89 | // account.payment.term 90 | [JsonProperty("payment_term_id")] 91 | public long? PaymentTermId { get; set; } 92 | 93 | // account.incoterms 94 | [JsonProperty("incoterm_id")] 95 | public long? IncotermId { get; set; } 96 | 97 | // product.product 98 | [JsonProperty("product_id")] 99 | public long? ProductId { get; set; } 100 | 101 | // res.users 102 | [JsonProperty("user_id")] 103 | public long? UserId { get; set; } 104 | 105 | // res.company 106 | // required 107 | [JsonProperty("company_id")] 108 | public long CompanyId { get; set; } 109 | 110 | [JsonProperty("currency_rate")] 111 | public double? CurrencyRate { get; set; } 112 | 113 | [JsonProperty("mail_reminder_confirmed")] 114 | public bool? MailReminderConfirmed { get; set; } 115 | 116 | [JsonProperty("mail_reception_confirmed")] 117 | public bool? MailReceptionConfirmed { get; set; } 118 | 119 | [JsonProperty("receipt_reminder_email")] 120 | public bool? ReceiptReminderEmail { get; set; } 121 | 122 | [JsonProperty("reminder_date_before_receipt")] 123 | public int? ReminderDateBeforeReceipt { get; set; } 124 | 125 | [JsonProperty("sale_order_count")] 126 | public int? SaleOrderCount { get; set; } 127 | 128 | // mail.activity 129 | [JsonProperty("activity_ids")] 130 | public long[] ActivityIds { get; set; } 131 | 132 | [JsonProperty("activity_state")] 133 | public ActivityStatePurchaseOrderOdooEnum? ActivityState { get; set; } 134 | 135 | // res.users 136 | [JsonProperty("activity_user_id")] 137 | public long? ActivityUserId { get; set; } 138 | 139 | // mail.activity.type 140 | [JsonProperty("activity_type_id")] 141 | public long? ActivityTypeId { get; set; } 142 | 143 | [JsonProperty("activity_type_icon")] 144 | public string ActivityTypeIcon { get; set; } 145 | 146 | [JsonProperty("activity_date_deadline")] 147 | public DateTime? ActivityDateDeadline { get; set; } 148 | 149 | [JsonProperty("activity_summary")] 150 | public string ActivitySummary { get; set; } 151 | 152 | [JsonProperty("activity_exception_decoration")] 153 | public ActivityExceptionDecorationPurchaseOrderOdooEnum? ActivityExceptionDecoration { get; set; } 154 | 155 | [JsonProperty("activity_exception_icon")] 156 | public string ActivityExceptionIcon { get; set; } 157 | 158 | [JsonProperty("message_is_follower")] 159 | public bool? MessageIsFollower { get; set; } 160 | 161 | // mail.followers 162 | [JsonProperty("message_follower_ids")] 163 | public long[] MessageFollowerIds { get; set; } 164 | 165 | // res.partner 166 | [JsonProperty("message_partner_ids")] 167 | public long[] MessagePartnerIds { get; set; } 168 | 169 | // mail.channel 170 | [JsonProperty("message_channel_ids")] 171 | public long[] MessageChannelIds { get; set; } 172 | 173 | // mail.message 174 | [JsonProperty("message_ids")] 175 | public long[] MessageIds { get; set; } 176 | 177 | [JsonProperty("message_unread")] 178 | public bool? MessageUnread { get; set; } 179 | 180 | [JsonProperty("message_unread_counter")] 181 | public int? MessageUnreadCounter { get; set; } 182 | 183 | [JsonProperty("message_needaction")] 184 | public bool? MessageNeedaction { get; set; } 185 | 186 | [JsonProperty("message_needaction_counter")] 187 | public int? MessageNeedactionCounter { get; set; } 188 | 189 | [JsonProperty("message_has_error")] 190 | public bool? MessageHasError { get; set; } 191 | 192 | [JsonProperty("message_has_error_counter")] 193 | public int? MessageHasErrorCounter { get; set; } 194 | 195 | [JsonProperty("message_attachment_count")] 196 | public int? MessageAttachmentCount { get; set; } 197 | 198 | // ir.attachment 199 | [JsonProperty("message_main_attachment_id")] 200 | public long? MessageMainAttachmentId { get; set; } 201 | 202 | // mail.message 203 | [JsonProperty("website_message_ids")] 204 | public long[] WebsiteMessageIds { get; set; } 205 | 206 | [JsonProperty("message_has_sms_error")] 207 | public bool? MessageHasSmsError { get; set; } 208 | 209 | [JsonProperty("access_url")] 210 | public string AccessUrl { get; set; } 211 | 212 | [JsonProperty("access_token")] 213 | public string AccessToken { get; set; } 214 | 215 | [JsonProperty("access_warning")] 216 | public string AccessWarning { get; set; } 217 | 218 | [JsonProperty("id")] 219 | public long Id { get; set; } 220 | 221 | [JsonProperty("display_name")] 222 | public string DisplayName { get; set; } 223 | 224 | // res.users 225 | [JsonProperty("create_uid")] 226 | public long? CreateUid { get; set; } 227 | 228 | [JsonProperty("create_date")] 229 | public DateTime? CreateDate { get; set; } 230 | 231 | // res.users 232 | [JsonProperty("write_uid")] 233 | public long? WriteUid { get; set; } 234 | 235 | [JsonProperty("write_date")] 236 | public DateTime? WriteDate { get; set; } 237 | 238 | [JsonProperty("__last_update")] 239 | public DateTime? LastUpdate { get; set; } 240 | 241 | [JsonProperty("x_studio_pickup_date")] 242 | public DateTime? XStudioPickupDate { get; set; } 243 | 244 | [JsonProperty("x_studio_sent_to_exact")] 245 | public bool? XStudioSentToExact { get; set; } 246 | 247 | [JsonProperty("x_studio_pickup_address")] 248 | public string XStudioPickupAddress { get; set; } 249 | } 250 | 251 | 252 | [JsonConverter(typeof(StringEnumConverter))] 253 | public enum PriorityPurchaseOrderOdooEnum 254 | { 255 | [EnumMember(Value = "0")] 256 | Normal = 1, 257 | 258 | [EnumMember(Value = "1")] 259 | Urgent = 2, 260 | } 261 | 262 | 263 | [JsonConverter(typeof(StringEnumConverter))] 264 | public enum StatusPurchaseOrderOdooEnum 265 | { 266 | [EnumMember(Value = "draft")] 267 | RFQ = 1, 268 | 269 | [EnumMember(Value = "sent")] 270 | RFQSent = 2, 271 | 272 | [EnumMember(Value = "to approve")] 273 | ToApprove = 3, 274 | 275 | [EnumMember(Value = "purchase")] 276 | PurchaseOrder = 4, 277 | 278 | [EnumMember(Value = "done")] 279 | Locked = 5, 280 | 281 | [EnumMember(Value = "cancel")] 282 | Cancelled = 6, 283 | } 284 | 285 | 286 | [JsonConverter(typeof(StringEnumConverter))] 287 | public enum BillingStatusPurchaseOrderOdooEnum 288 | { 289 | [EnumMember(Value = "no")] 290 | NothingToBill = 1, 291 | 292 | [EnumMember(Value = "to invoice")] 293 | WaitingBills = 2, 294 | 295 | [EnumMember(Value = "invoiced")] 296 | FullyBilled = 3, 297 | } 298 | 299 | 300 | // Status based on activities 301 | // Overdue: Due date is already passed 302 | // Today: Activity date is today 303 | // Planned: Future activities. 304 | [JsonConverter(typeof(StringEnumConverter))] 305 | public enum ActivityStatePurchaseOrderOdooEnum 306 | { 307 | [EnumMember(Value = "overdue")] 308 | Overdue = 1, 309 | 310 | [EnumMember(Value = "today")] 311 | Today = 2, 312 | 313 | [EnumMember(Value = "planned")] 314 | Planned = 3, 315 | } 316 | 317 | 318 | // Type of the exception activity on record. 319 | [JsonConverter(typeof(StringEnumConverter))] 320 | public enum ActivityExceptionDecorationPurchaseOrderOdooEnum 321 | { 322 | [EnumMember(Value = "warning")] 323 | Alert = 1, 324 | 325 | [EnumMember(Value = "danger")] 326 | Error = 2, 327 | } 328 | 329 | 330 | } --------------------------------------------------------------------------------