diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Global/AppSettings.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Global/AppSettings.cs index 4a2a508..9d50f91 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Global/AppSettings.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Global/AppSettings.cs @@ -266,11 +266,13 @@ namespace OnlineSalesAutoCrop.CoreAPI.Models.Global /// [Required] public ApiSettings API { get; set; } + + public AutoCropSAPApi AutoCropSAPApi { get; set; } - /// - /// Swagger Setting - /// - [Required] + /// + /// Swagger Setting + /// + [Required] public Swagger Swagger { get; set; } /// @@ -346,10 +348,20 @@ namespace OnlineSalesAutoCrop.CoreAPI.Models.Global public string Url { get; set; } } - /// - /// - /// - public class ApiLicense + /// + /// AutoCrop Integration API Details + /// + public class AutoCropSAPApi + { + public string BaseUrl { get; set; } + public string UserName { get; set; } + public string Password { get; set; } + } + + /// + /// + /// + public class ApiLicense { public string Name { get; set; } public string Url { get; set; } diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/Integrations/IntegrationHttpResponse.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/Integrations/IntegrationHttpResponse.cs new file mode 100644 index 0000000..eff3d37 --- /dev/null +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/Integrations/IntegrationHttpResponse.cs @@ -0,0 +1,91 @@ + + +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.RegularExpressions; + + +namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations; + +public class IntegrationCustLedgerHttpResponse +{ + public string Company { get; set; } + public string Customer_no { get; set; } + public decimal Current_Balance { get; set; } + public decimal Overdue_Amount { get; set; } +} + +public class IntegrationMaterialStockHttpResponse +{ + public string Plant { get; set; } + public string Material { get; set; } + public decimal Available_Stock { get; set; } + public string UOM { get; set; } +} + + + +// Reusable envelope for any SAP OData V2 collection response: { "d": { "results": [...] } } +public sealed class SapODataResponse +{ + [JsonPropertyName("d")] + public SapODataResult D { get; set; } = default!; +} + +public sealed class SapODataResult +{ + [JsonPropertyName("results")] + public List Results { get; set; } = new(); +} + +public sealed class CustomerLedgerDto +{ + [JsonPropertyName("company")] + public string Company { get; set; } = string.Empty; + + [JsonPropertyName("customer_no")] + public string CustomerNo { get; set; } = string.Empty; // stays string — SAP customer numbers can carry leading zeros + + [JsonPropertyName("current_balance")] + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] + public decimal CurrentBalance { get; set; } + + [JsonPropertyName("overdue_amount")] + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] + public decimal OverdueAmount { get; set; } + + [JsonPropertyName("currency")] + public string Currency { get; set; } = string.Empty; + + [JsonPropertyName("overdue_date")] + [JsonConverter(typeof(SapODataDateConverter))] + public DateTime OverdueDate { get; set; } +} + +public sealed class SapODataDateConverter : JsonConverter +{ + // Matches "/Date(1783641600000)/" and the rarer offset form "/Date(1783641600000+0600)/" + private static readonly Regex Pattern = new(@"/Date\((-?\d+)(?:[+-]\d{4})?\)/", RegexOptions.Compiled); + + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var raw = reader.GetString(); + if (string.IsNullOrEmpty(raw)) + return default; + + var match = Pattern.Match(raw); + if (!match.Success) + throw new JsonException($"Unexpected SAP OData date format: '{raw}'"); + + var ms = long.Parse(match.Groups[1].Value); + return DateTimeOffset.FromUnixTimeMilliseconds(ms).UtcDateTime; + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + var ms = new DateTimeOffset(value, TimeSpan.Zero).ToUnixTimeMilliseconds(); + writer.WriteStringValue($"/Date({ms})/"); + } +} \ No newline at end of file diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/Integrations/IIntegrationHttpService.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/Integrations/IIntegrationHttpService.cs new file mode 100644 index 0000000..19f6939 --- /dev/null +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/Integrations/IIntegrationHttpService.cs @@ -0,0 +1,17 @@ + +using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations; +using System.Threading; +using System.Threading.Tasks; + +namespace OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Integrations; + +public interface IIntegrationHttpService +{ + Task GetCustomerLedgerAsync(string customerNumber, string companyCode, + CancellationToken cancellationToken = default); + + Task GetMaterialStockAsync(string plantCode, string materialCode, + CancellationToken cancellationToken = default); + + +} diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Integrations/IntegrationHttpService.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Integrations/IntegrationHttpService.cs new file mode 100644 index 0000000..e7dc82f --- /dev/null +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Integrations/IntegrationHttpService.cs @@ -0,0 +1,71 @@ + +using Microsoft.Extensions.Logging; +using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations; +using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Integrations; +using System; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace OnlineSalesAutoCrop.CoreAPI.Services.Services.Integrations; + +public class IntegrationHttpService : IIntegrationHttpService +{ + private readonly HttpClient _httpClient; + private readonly ILogger _logger; + + public IntegrationHttpService(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + public async Task GetCustomerLedgerAsync(string customerNumber, string companyCode, CancellationToken cancellationToken = default) + { + try + { + var requestUri = $"http/get_customer_ledger?customer={Uri.EscapeDataString(customerNumber)}&company={Uri.EscapeDataString(companyCode)}"; + + var response = await _httpClient.GetAsync(requestUri, cancellationToken); + + if (!response.IsSuccessStatusCode) + { + var body = await response.Content.ReadAsStringAsync(cancellationToken); + _logger.LogError("Customer ledger call failed: {StatusCode} — {Body}", response.StatusCode, body); + response.EnsureSuccessStatusCode(); + } + var payload= await response.Content.ReadFromJsonAsync< SapODataResponse>(cancellationToken: cancellationToken); + + return payload?.D.Results.FirstOrDefault(); + } + catch(Exception) + { + throw; + } + } + + public async Task GetMaterialStockAsync(string plantCode, string materialCode, CancellationToken cancellationToken = default) + { + try + { + var requestUri = $"http/get_available_stocks?plant={Uri.EscapeDataString(plantCode)}&material={Uri.EscapeDataString(materialCode)}"; + + var response = await _httpClient.GetAsync(requestUri, cancellationToken); + + if (!response.IsSuccessStatusCode) + { + var body = await response.Content.ReadAsStringAsync(cancellationToken); + _logger.LogError("Material Stock call failed: {StatusCode} — {Body}", response.StatusCode, body); + response.EnsureSuccessStatusCode(); + } + + return await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); + } + catch (Exception) + { + throw; + } + } +} diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/MobileApp/MobileMasterDataService.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/MobileApp/MobileMasterDataService.cs index 3f84fb1..74a7512 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/MobileApp/MobileMasterDataService.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/MobileApp/MobileMasterDataService.cs @@ -8,11 +8,13 @@ using OnlineSalesAutoCrop.CoreAPI.Models.Requests.MobileApp; using OnlineSalesAutoCrop.CoreAPI.Models.Responses; using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations; using OnlineSalesAutoCrop.CoreAPI.Models.Responses.MobileApp; +using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Integrations; using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.MobileApp; using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems; using System; using System.Collections.Generic; using System.Data; +using System.Linq; using System.Threading.Tasks; namespace OnlineSalesAutoCrop.CoreAPI.Services.Services.MobileApp; @@ -21,11 +23,13 @@ public class MobileMasterDataService : IMobileMasterDataService { private readonly AppSettings _settings; private readonly IUserService _userService; + private readonly IIntegrationHttpService _httpService; - public MobileMasterDataService(IOptions options, IUserService userService) + public MobileMasterDataService(IOptions options, IUserService userService, IIntegrationHttpService httpService) { _settings = options.Value; _userService = userService; + _httpService = httpService; } public async Task GetMarketHeirarchiesByEmpAsync(GetMarketHeirarchyByEmpRequest request) @@ -236,6 +240,7 @@ public class MobileMasterDataService : IMobileMasterDataService public async Task> GetCustomersAsync(GetCustomersRequest request) { + string companyCode = string.Empty; PagedResult response = new(); try { @@ -284,6 +289,7 @@ public class MobileMasterDataService : IMobileMasterDataService CreditBalance = dr.GetDecimal(23), CreditOverdue = dr.GetDecimal(24), }); + companyCode = dr.GetString(25); } dr.Close(); @@ -292,6 +298,18 @@ public class MobileMasterDataService : IMobileMasterDataService response.PageSize = request.PageSize; } tc.End(); + + + if (!string.IsNullOrWhiteSpace(request.CustomerCode)) + { + var customerLedger = await _httpService.GetCustomerLedgerAsync(request.CustomerCode, companyCode); + response.Items = response.Items.Select(x => + { + x.CreditBalance = customerLedger.CurrentBalance; + x.CreditOverdue = customerLedger.OverdueAmount; + return x; + }).ToList(); + } } catch (Exception ie) { diff --git a/Api/OnlineSalesAutoCrop.CoreAPI/OnlineSalesAutoCrop.CoreAPI.csproj b/Api/OnlineSalesAutoCrop.CoreAPI/OnlineSalesAutoCrop.CoreAPI.csproj index 99aee17..47984ff 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI/OnlineSalesAutoCrop.CoreAPI.csproj +++ b/Api/OnlineSalesAutoCrop.CoreAPI/OnlineSalesAutoCrop.CoreAPI.csproj @@ -171,6 +171,8 @@ + + diff --git a/Api/OnlineSalesAutoCrop.CoreAPI/Startup.cs b/Api/OnlineSalesAutoCrop.CoreAPI/Startup.cs index 472a207..2a650a6 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI/Startup.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI/Startup.cs @@ -8,7 +8,6 @@ using Microsoft.AspNetCore.CookiePolicy; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -23,18 +22,22 @@ using OnlineSalesAutoCrop.CoreAPI.Configurations; using OnlineSalesAutoCrop.CoreAPI.Models; using OnlineSalesAutoCrop.CoreAPI.Models.Global; using OnlineSalesAutoCrop.CoreAPI.Models.Responses; -using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems; +using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Integrations; +using OnlineSalesAutoCrop.CoreAPI.Services.Services.Integrations; using OnlineSalesAutoCrop.CoreAPI.SignalRHub; using Swashbuckle.AspNetCore.SwaggerGen; using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Net; using System.Reflection; using System.Text; using System.Threading.RateLimiting; using System.Threading.Tasks; +using Microsoft.AspNetCore.HttpOverrides; +using System.Net.Http.Headers; +using System.Net.Http; +using System.Net; namespace OnlineSalesAutoCrop.CoreAPI { @@ -341,9 +344,29 @@ namespace OnlineSalesAutoCrop.CoreAPI services.ConfigureBusinessServices(); - #endregion - } - } + #endregion + + + #region AutoCrop Integration Service + + services.AddHttpClient((sp, client) => + { + var opt = _appSettings.AutoCropSAPApi; + + client.BaseAddress = new Uri(opt.BaseUrl); + + var basicAuth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{opt.UserName}:{opt.Password}")); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", basicAuth); + + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + + client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json"); + }) + .AddStandardResilienceHandler(); // retry + timeout + circuit breaker, bundled + + #endregion + } + } catch { //Nothimg to do here as we are handling all exceptions in Configure method using global exception handler diff --git a/Api/OnlineSalesAutoCrop.CoreAPI/appsettings.json b/Api/OnlineSalesAutoCrop.CoreAPI/appsettings.json index 3b3c47c..cf9d690 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI/appsettings.json +++ b/Api/OnlineSalesAutoCrop.CoreAPI/appsettings.json @@ -85,7 +85,12 @@ "WaMsgSvcSid": "MG8401d33a9a3b2aea95619bda3e5757b5", "WaSenderId": "+8801326755660", "RefreshTokenDuration": "15", - "AccessTokenDuration": "60" + "AccessTokenDuration": "60", + "AutoCropSAPApi": { + "BaseUrl": "https://accl-test-pad-l06vsvh7.it-cpi004-rt.cfapps.ap11.hana.ondemand.com/", + "UserName": "sb-1635ae0e-9941-4998-962f-e23f592d29b5!b45657|it-rt-accl-test-pad-l06vsvh7!b68", + "Password": "01b3f9b9-8dee-4aed-9cb9-bbefeffc1db8$eB6J_oUJZDGuJnpzAzLUxI9lAha_hljim2cfH1niJa8=" + } }, "MenuSettings": {