├── 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