implement intgreation customer ledger api when get customer details by a specific code
This commit is contained in:
parent
56bbcdfa04
commit
3ae6fa0882
|
|
@ -266,11 +266,13 @@ namespace OnlineSalesAutoCrop.CoreAPI.Models.Global
|
|||
/// </summary>
|
||||
[Required]
|
||||
public ApiSettings API { get; set; }
|
||||
|
||||
public AutoCropSAPApi AutoCropSAPApi { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Swagger Setting
|
||||
/// </summary>
|
||||
[Required]
|
||||
/// <summary>
|
||||
/// Swagger Setting
|
||||
/// </summary>
|
||||
[Required]
|
||||
public Swagger Swagger { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -346,10 +348,20 @@ namespace OnlineSalesAutoCrop.CoreAPI.Models.Global
|
|||
public string Url { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class ApiLicense
|
||||
/// <summary>
|
||||
/// AutoCrop Integration API Details
|
||||
/// </summary>
|
||||
public class AutoCropSAPApi
|
||||
{
|
||||
public string BaseUrl { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class ApiLicense
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Url { get; set; }
|
||||
|
|
|
|||
|
|
@ -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<T>
|
||||
{
|
||||
[JsonPropertyName("d")]
|
||||
public SapODataResult<T> D { get; set; } = default!;
|
||||
}
|
||||
|
||||
public sealed class SapODataResult<T>
|
||||
{
|
||||
[JsonPropertyName("results")]
|
||||
public List<T> 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<DateTime>
|
||||
{
|
||||
// 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})/");
|
||||
}
|
||||
}
|
||||
|
|
@ -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<CustomerLedgerDto?> GetCustomerLedgerAsync(string customerNumber, string companyCode,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<IntegrationMaterialStockHttpResponse?> GetMaterialStockAsync(string plantCode, string materialCode,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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<IntegrationHttpService> _logger;
|
||||
|
||||
public IntegrationHttpService(HttpClient httpClient, ILogger<IntegrationHttpService> logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<CustomerLedgerDto> 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<CustomerLedgerDto>>(cancellationToken: cancellationToken);
|
||||
|
||||
return payload?.D.Results.FirstOrDefault();
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IntegrationMaterialStockHttpResponse> 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<IntegrationMaterialStockHttpResponse>(cancellationToken: cancellationToken);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<AppSettings> options, IUserService userService)
|
||||
public MobileMasterDataService(IOptions<AppSettings> options, IUserService userService, IIntegrationHttpService httpService)
|
||||
{
|
||||
_settings = options.Value;
|
||||
_userService = userService;
|
||||
_httpService = httpService;
|
||||
}
|
||||
|
||||
public async Task<MarketHeirarchyByEmpResponse> GetMarketHeirarchiesByEmpAsync(GetMarketHeirarchyByEmpRequest request)
|
||||
|
|
@ -236,6 +240,7 @@ public class MobileMasterDataService : IMobileMasterDataService
|
|||
|
||||
public async Task<PagedResult<GetCustomerResponse>> GetCustomersAsync(GetCustomersRequest request)
|
||||
{
|
||||
string companyCode = string.Empty;
|
||||
PagedResult<GetCustomerResponse> 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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -171,6 +171,8 @@
|
|||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="10.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="10.8.0" />
|
||||
<PackageReference Include="OpenAI" Version="2.10.0" />
|
||||
<PackageReference Include="RabbitMQ.Client" Version="7.2.1" />
|
||||
<PackageReference Include="ReportViewerCore.NETCore" Version="15.1.33" />
|
||||
|
|
|
|||
|
|
@ -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<IIntegrationHttpService, IntegrationHttpService>((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
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user