practicekea_backend/microservices/_layers/common/OdooService.cs

423 lines
14 KiB
C#
Raw Permalink Normal View History

2025-10-22 13:05:31 +00:00
using Azure;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using OnlineAssessment.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
public class OdooService
{
protected readonly HttpClient _httpClient;
protected readonly OdooConfig _config;
protected readonly IMemoryCache _cache;
public OdooService(HttpClient httpClient, IOptions<OdooConfig> config, IMemoryCache cache)
{
_httpClient = httpClient;
_config = config.Value;
_cache = cache;
}
public async Task<string> AuthenticateAsync()
{
const string cacheKey = "OdooSession";
if (_cache.TryGetValue(cacheKey, out string cachedSessionId))
{
return cachedSessionId;
}
var payload = new
{
jsonrpc = "2.0",
@params = new
{
db = _config.Db,
login = _config.Username,
password = _config.Password
}
};
var response = await _httpClient.PostAsJsonAsync($"{_config.BaseUrl}/web/session/authenticate", payload);
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"Authentication failed with status code: {response.StatusCode}");
}
var result = await response.Content.ReadFromJsonAsync<JsonDocument>();
var sessionId = ExtractSessionId(response);
if (string.IsNullOrEmpty(sessionId))
{
throw new Exception("Session ID not found in authentication response.");
}
CacheSessionId(sessionId);
return sessionId;
}
public async Task<int> CreateAsync(string model, object fields)
{
var sessionId = await AuthenticateAsync();
_httpClient.DefaultRequestHeaders.Add("Cookie", $"session_id={sessionId}");
var payload = new
{
jsonrpc = "2.0",
method = "call",
@params = new
{
model = model,
method = "create",
args = new[] { fields },
kwargs = new { }
}
};
var response = await _httpClient.PostAsJsonAsync($"{_config.BaseUrl}/web/dataset/call_kw/{model}/create", payload);
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"Failed to create record with status code: {response.StatusCode}");
}
var responseContent1 = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Response Content: {responseContent1}");
var result = await response.Content.ReadFromJsonAsync<JsonDocument>();
if (result == null || !result.RootElement.TryGetProperty("result", out var idElement))
{
throw new Exception("Failed to retrieve ID from response.");
}
return idElement.GetInt32();
}
public async Task<bool> DeleteAsync(string model, int recordId)
{
var sessionId = await AuthenticateAsync();
_httpClient.DefaultRequestHeaders.Add("Cookie", $"session_id={sessionId}");
var payload = new
{
jsonrpc = "2.0",
method = "call",
@params = new
{
model = model,
method = "unlink", // Use the 'unlink' method to delete the record
args = new[] { recordId },
kwargs = new { }
}
};
var response = await _httpClient.PostAsJsonAsync($"{_config.BaseUrl}/web/dataset/call_kw", payload);
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"Failed to delete record with status code: {response.StatusCode}");
}
var responseContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Response Content: {responseContent}");
var result = await response.Content.ReadFromJsonAsync<JsonDocument>();
if (result == null || !result.RootElement.TryGetProperty("result", out var resultElement))
{
throw new Exception("Failed to retrieve result from the response.");
}
// If result is 1, it means the operation (deletion) was successful
return resultElement.GetBoolean();
}
/// <summary>
/// Updates an existing record in Odoo.
/// </summary>
/// <param name="model">The Odoo model to update.</param>
/// <param name="recordId">The ID of the record to update.</param>
/// <param name="updateData">The fields to update.</param>
/// <returns>True if the update was successful, otherwise false.</returns>
public async Task<bool> UpdateAsync(string model, int recordId, Dictionary<string, object> updateData)
{
if (string.IsNullOrWhiteSpace(model))
{
throw new ArgumentException("Model name cannot be null or empty.", nameof(model));
}
if (recordId <= 0)
{
throw new ArgumentException("Invalid record ID.", nameof(recordId));
}
if (updateData == null || updateData.Count == 0)
{
throw new ArgumentNullException(nameof(updateData), "Update data cannot be null or empty.");
}
var sessionId = await AuthenticateAsync();
_httpClient.DefaultRequestHeaders.Add("Cookie", $"session_id={sessionId}");
var payload = new
{
jsonrpc = "2.0",
method = "call",
@params = new
{
model = model,
method = "write",
args = new object[] { new int[] { recordId }, updateData },
kwargs = new { }
}
};
var response = await _httpClient.PostAsJsonAsync($"{_config.BaseUrl}/web/dataset/call_kw/{model}/write", payload);
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"Failed to update record with status code: {response.StatusCode}");
}
var result = await response.Content.ReadFromJsonAsync<JsonDocument>();
if (result == null || !result.RootElement.TryGetProperty("result", out var successElement))
{
throw new Exception("Failed to retrieve update status from response.");
}
return successElement.GetBoolean();
}
/// <summary>
/// Performs a search_read operation on the specified Odoo model.
/// </summary>
/// <param name="model">The Odoo model to search in.</param>
/// <param name="filterData">The filter criteria and fields to retrieve.</param>
/// <returns>A list of results from the search_read operation.</returns>
/// <exception cref="Exception">Thrown when the request fails or response is invalid.</exception>
public async Task<List<object>> SearchAsync(string model, object filterData, string[] fields)
{
if (string.IsNullOrWhiteSpace(model))
throw new ArgumentException("Model name cannot be null or empty.", nameof(model));
var sessionId = await AuthenticateAsync();
_httpClient.DefaultRequestHeaders.Add("Cookie", $"session_id={sessionId}");
// Extract `domain` from `filterData`
var domainFilter = filterData.GetType().GetProperty("domain")?.GetValue(filterData) as List<object>;
var payload = new
{
jsonrpc = "2.0",
method = "call",
@params = new
{
model,
method = "search_read",
args = new object[] { domainFilter },
kwargs = new { fields }
}
};
var response = await _httpClient.PostAsJsonAsync($"{_config.BaseUrl}/web/dataset/call_kw", payload);
if (!response.IsSuccessStatusCode)
throw new HttpRequestException($"Failed to search '{model}' with status code: {response.StatusCode}");
var result = await response.Content.ReadFromJsonAsync<JsonDocument>();
if (result == null || !result.RootElement.TryGetProperty("result", out var records))
throw new Exception($"Failed to retrieve records from search response for '{model}'.");
return records.EnumerateArray().Select(record => JsonSerializer.Deserialize<object>(record.GetRawText())).ToList();
}
/// <summary>
/// Performs a search_read operation on the specified Odoo model.
/// </summary>
/// <param name="model">The Odoo model to search in.</param>
/// <param name="filterData">The filter criteria and fields to retrieve.</param>
/// <returns>A list of results from the search_read operation.</returns>
/// <exception cref="Exception">Thrown when the request fails or response is invalid.</exception>
public async Task<List<object>> SearchReadAttendanceAsync(string model, object filterData)
{
if (string.IsNullOrWhiteSpace(model))
{
throw new ArgumentException("Model name cannot be null or empty.", nameof(model));
}
if (filterData == null)
{
throw new ArgumentNullException(nameof(filterData), "Filter data cannot be null.");
}
var sessionId = await AuthenticateAsync();
_httpClient.DefaultRequestHeaders.Add("Cookie", $"session_id={sessionId}");
// Extract `domain` from `filterData`
var domainFilter = filterData.GetType().GetProperty("domain")?.GetValue(filterData) as List<object>;
var payload = new
{
jsonrpc = "2.0",
method = "call",
@params = new
{
model,
method = "search_read",
args = new object[] { domainFilter },
kwargs = new { fields = new[] { "id", "employee_id", "check_in", "check_out" } }
}
};
var response = await _httpClient.PostAsJsonAsync($"{_config.BaseUrl}/web/dataset/call_kw", payload);
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"Failed to perform search_read on model '{model}' with status code: {response.StatusCode}");
}
var responseContent1 = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Response Content: {responseContent1}");
var result = await response.Content.ReadFromJsonAsync<JsonDocument>();
if (result == null || !result.RootElement.TryGetProperty("result", out var records))
{
throw new Exception("Failed to retrieve results from search_read response.");
}
return records.EnumerateArray().Select(record => JsonSerializer.Deserialize<object>(record.GetRawText())).ToList();
}
public async Task<bool> ValidateSubscriptionAsync(string instituteId)
{
try
{
var sessionId = await AuthenticateAsync();
_httpClient.DefaultRequestHeaders.Add("Cookie", $"session_id={sessionId}");
var domain = new[]
{
new object[] { "company_registry", "=", instituteId }
};
var companySearchPayload = new
{
jsonrpc = "2.0",
method = "call",
@params = new
{
model = "res.partner",
method = "search_read",
args = new object[] { domain },
kwargs = new { fields = new[] { "id" } }
}
};
var companyResponse = await _httpClient.PostAsJsonAsync($"{_config.BaseUrl}/web/dataset/call_kw", companySearchPayload);
if (!companyResponse.IsSuccessStatusCode)
{
return false;
}
var companyData = await companyResponse.Content.ReadFromJsonAsync<JsonDocument>();
if (companyData == null || !companyData.RootElement.TryGetProperty("result", out var resultIdList))
{
return false;
}
var firstRecord = resultIdList.EnumerateArray().FirstOrDefault();
if (!(firstRecord.ValueKind == JsonValueKind.Object) || !firstRecord.TryGetProperty("id", out var companyIdElement))
{
return false;
}
var companyId = companyIdElement.GetInt32();
var domain2 = new[]
{
new object[] { "partner_id", "=", companyId }
};
var subscriptionSearchPayload = new
{
jsonrpc = "2.0",
method = "call",
@params = new
{
model = "sale.subscription.report",
method = "search_read",
args = new object[] { domain2 },
kwargs = new { fields = new[] { "subscription_state" } }
}
};
var subscriptionResponse = await _httpClient.PostAsJsonAsync($"{_config.BaseUrl}/web/dataset/call_kw", subscriptionSearchPayload);
if (!subscriptionResponse.IsSuccessStatusCode)
{
return false;
}
var subscriptionData = await subscriptionResponse.Content.ReadFromJsonAsync<JsonDocument>();
if (subscriptionData == null || !subscriptionData.RootElement.TryGetProperty("result", out var resultStateList))
{
return false;
}
firstRecord = resultStateList.EnumerateArray().FirstOrDefault();
if (!(firstRecord.ValueKind == JsonValueKind.Object) || !firstRecord.TryGetProperty("subscription_state", out var subscriptionElement))
{
return false;
}
var subscriptionState = subscriptionElement.GetString();
return subscriptionState == "3_progress";
}
catch (Exception ex)
{
Console.WriteLine($"Error validating subscription: {ex.Message}");
return false;
}
}
private string ExtractSessionId(HttpResponseMessage response)
{
if (response.Headers.TryGetValues("Set-Cookie", out var cookies))
{
var sessionCookie = cookies.FirstOrDefault(c => c.Contains("session_id"));
return sessionCookie?.Split(';').FirstOrDefault()?.Split('=')[1];
}
return null;
}
private void CacheSessionId(string sessionId)
{
var cacheEntryOptions = new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30),
Size = 1
};
_cache.Set("OdooSession", sessionId, cacheEntryOptions);
}
}