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 config, IMemoryCache cache) { _httpClient = httpClient; _config = config.Value; _cache = cache; } public async Task 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(); 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 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(); 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 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(); 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(); } /// /// Updates an existing record in Odoo. /// /// The Odoo model to update. /// The ID of the record to update. /// The fields to update. /// True if the update was successful, otherwise false. public async Task UpdateAsync(string model, int recordId, Dictionary 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(); if (result == null || !result.RootElement.TryGetProperty("result", out var successElement)) { throw new Exception("Failed to retrieve update status from response."); } return successElement.GetBoolean(); } /// /// Performs a search_read operation on the specified Odoo model. /// /// The Odoo model to search in. /// The filter criteria and fields to retrieve. /// A list of results from the search_read operation. /// Thrown when the request fails or response is invalid. public async Task> 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; 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(); 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(record.GetRawText())).ToList(); } /// /// Performs a search_read operation on the specified Odoo model. /// /// The Odoo model to search in. /// The filter criteria and fields to retrieve. /// A list of results from the search_read operation. /// Thrown when the request fails or response is invalid. public async Task> 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; 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(); 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(record.GetRawText())).ToList(); } public async Task 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(); 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(); 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); } }