From e55821737fab2ad9003495aebb49bc1cc74eee19 Mon Sep 17 00:00:00 2001 From: dibakor Date: Thu, 25 Jun 2026 12:41:18 +0600 Subject: [PATCH] Add FOC Api --- .../IntegrationMaterialFreeGood.cs | 23 +++ .../IntegrationMaterialFreeGoodsRequest.cs | 76 +++++++ .../IntegrationMaterialFreeGoodsResponse.cs | 30 +++ .../Integrations/IIntegrationService.cs | 4 + .../Integrations/IntegrationService.cs | 195 ++++++++++++++++++ .../IntegretionApi/IntegrationController.cs | 45 +++- 6 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 Api/OnlineSalesAutoCrop.CoreAPI.Models/Objects/Integrations/IntegrationMaterialFreeGood.cs create mode 100644 Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Integrations/IntegrationMaterialFreeGoodsRequest.cs create mode 100644 Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/Integrations/IntegrationMaterialFreeGoodsResponse.cs diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Objects/Integrations/IntegrationMaterialFreeGood.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Objects/Integrations/IntegrationMaterialFreeGood.cs new file mode 100644 index 0000000..7508327 --- /dev/null +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Objects/Integrations/IntegrationMaterialFreeGood.cs @@ -0,0 +1,23 @@ +using System; + +namespace OnlineSalesAutoCrop.CoreAPI.Models.Objects.Integrations; + +public class IntegrationMaterialFreeGood +{ + public int FreeGoodsId { get; set; } // required for Update + public string SalesOrg { get; set; } + public string DistributionChannel { get; set; } + public int? CustomerId { get; set; } // nullable + public string CustomerPriceGroup { get; set; } + public int MaterialId{ get; set; } + public decimal MinOrderQuantity { get; set; } + public decimal ForQuantity { get; set; } + public string UnitOfMeasure { get; set; } + public decimal FreeQuantity { get; set; } + public string FocUnitOfMeasure { get; set; } + public DateTime ValidFrom { get; set; } + public DateTime ValidTo { get; set; } + public string? Status { get; set; } // NULL = Active, X = Inactive + public DateTime CreatedDate { get; set; } + public DateTime ModifiedDate { get; set; } +} diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Integrations/IntegrationMaterialFreeGoodsRequest.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Integrations/IntegrationMaterialFreeGoodsRequest.cs new file mode 100644 index 0000000..0319130 --- /dev/null +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Integrations/IntegrationMaterialFreeGoodsRequest.cs @@ -0,0 +1,76 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations; + +public class IntegrationMaterialFreeGoodsRequest +{ + + [Required(ErrorMessage = "SalesOrg is required.")] + [StringLength(4, MinimumLength = 1, ErrorMessage = "SalesOrg must be between 1 and 4 characters.")] + public string SalesOrg { get; set; } + + [Required(ErrorMessage = "DistributionChannel is required.")] + [StringLength(2, MinimumLength = 1, ErrorMessage = "DistributionChannel must be between 1 and 2 characters.")] + public string DistributionChannel { get; set; } + + [StringLength(13, MinimumLength = 1, ErrorMessage = "CustomerNumber must be between 1 and 13 characters.")] + public string? CustomerNumber { get; set; } // nullable + + [Required(ErrorMessage = "CustomerPriceGroup is required.")] + [StringLength(2, MinimumLength = 1, ErrorMessage = "CustomerPriceGroup must be between 1 and 2 characters.")] + public string CustomerPriceGroup { get; set; } + + [Required(ErrorMessage = "MaterialCode is required.")] + [StringLength(10, MinimumLength = 1, ErrorMessage = "MaterialCode must be between 1 and 10 characters.")] + public string MaterialCode { get; set; } + + [Required(ErrorMessage = "MinOrderQuantity is required.")] + [Range(0.001, 9999999999999.999, ErrorMessage = "MinOrderQuantity must be greater than 0.")] + public decimal MinOrderQuantity { get; set; } + + [Required(ErrorMessage = "ForQuantity is required.")] + [Range(0.001, 9999999999999.999, ErrorMessage = "ForQuantity must be greater than 0.")] + public decimal ForQuantity { get; set; } + + [Required(ErrorMessage = "UnitOfMeasure is required.")] + [StringLength(3, MinimumLength = 1, ErrorMessage = "UnitOfMeasure must be between 1 and 3 characters.")] + public string UnitOfMeasure { get; set; } + + [Required(ErrorMessage = "FreeQuantity is required.")] + [Range(0.001, 9999999999999.999, ErrorMessage = "FreeQuantity must be greater than 0.")] + public decimal FreeQuantity { get; set; } + + [Required(ErrorMessage = "FocUnitOfMeasure is required.")] + [StringLength(3, MinimumLength = 1, ErrorMessage = "FocUnitOfMeasure must be between 1 and 3 characters.")] + public string FocUnitOfMeasure { get; set; } + + [Required(ErrorMessage = "ValidFrom is required.")] + public DateOnly ValidFrom { get; set; } + + [Required(ErrorMessage = "ValidTo is required.")] + public DateOnly ValidTo { get; set; } + + [StringLength(1, MinimumLength = 1, ErrorMessage = "Status must be 1 character.")] + public string? Status { get; set; } // NULL = Active, X = Inactive +} + + +public class GetMaterialFreeGoodsByFilterRequest +{ + [Required(ErrorMessage = "SalesOrg is required.")] + [StringLength(4, MinimumLength = 1, ErrorMessage = "SalesOrg must be between 1 and 4 characters.")] + public string SalesOrg { get; set; } + + [Required(ErrorMessage = "DistributionChannel is required.")] + [StringLength(2, MinimumLength = 1, ErrorMessage = "DistributionChannel must be between 1 and 2 characters.")] + public string DistributionChannel { get; set; } + + [Required(ErrorMessage = "CustomerPriceGroup is required.")] + [StringLength(2, MinimumLength = 1, ErrorMessage = "CustomerPriceGroup must be between 1 and 2 characters.")] + public string CustomerPriceGroup { get; set; } + + [Required(ErrorMessage = "MaterialCode is required.")] + [StringLength(10, MinimumLength = 1, ErrorMessage = "MaterialCode must be between 1 and 10 characters.")] + public string MaterialCode { get; set; } +} \ No newline at end of file diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/Integrations/IntegrationMaterialFreeGoodsResponse.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/Integrations/IntegrationMaterialFreeGoodsResponse.cs new file mode 100644 index 0000000..f20b797 --- /dev/null +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/Integrations/IntegrationMaterialFreeGoodsResponse.cs @@ -0,0 +1,30 @@ +using System; + +namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations; + +public class IntegrationMaterialFreeGoodsResponse : ResponseBase +{ + public int FreeGoodsId { get; set; } // required for Update + public string SalesOrg { get; set; } + public string DistributionChannel { get; set; } + public string? CustomerNumber { get; set; } // nullable + public string CustomerPriceGroup { get; set; } + public string MaterialCode { get; set; } + public decimal MinOrderQuantity { get; set; } + public decimal ForQuantity { get; set; } + public string UnitOfMeasure { get; set; } + public decimal FreeQuantity { get; set; } + public string FocUnitOfMeasure { get; set; } + public DateTime ValidFrom { get; set; } + public DateTime ValidTo { get; set; } + public string? Status { get; set; } +} + +public class MaterialFreeGoodsByReqResponse : ResponseBase +{ + public string SalesOrg { get; set; } + public string DistributionChannel { get; set; } + public string CustomerPriceGroup { get; set; } + public string MaterialCode { get; set; } + public bool IsUpdated { get; set; } +} \ No newline at end of file diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/Integrations/IIntegrationService.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/Integrations/IIntegrationService.cs index ffea944..6e397bf 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/Integrations/IIntegrationService.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/Integrations/IIntegrationService.cs @@ -23,4 +23,8 @@ public interface IIntegrationService //Material Price Task UpsertMaterialPriceAsync(IntegrationMaterialPriceRequest request); Task GetMaterialPriceByCodeAsync( GetMaterialPriceByCodeRequest request); + + //Material Free Goods + Task UpsertMaterialFreeGoodsAsync(IntegrationMaterialFreeGoodsRequest request); + Task GetMaterialFreeGoodseByFilterAsync(GetMaterialFreeGoodsByFilterRequest request); } diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Integrations/IntegrationService.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Integrations/IntegrationService.cs index 56eb493..f4671cd 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Integrations/IntegrationService.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Integrations/IntegrationService.cs @@ -228,6 +228,7 @@ public class IntegrationService : IIntegrationService return response; } + public async Task UpsertMaterialPriceAsync(IntegrationMaterialPriceRequest request) { MaterialIntegrationPriceReqResponse response = new MaterialIntegrationPriceReqResponse(); @@ -296,6 +297,77 @@ public class IntegrationService : IIntegrationService } + public async Task UpsertMaterialFreeGoodsAsync(IntegrationMaterialFreeGoodsRequest request) + { + MaterialFreeGoodsByReqResponse response = new MaterialFreeGoodsByReqResponse(); + try + { + using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true); + try + { + IntegrationMaterialFreeGoodsResponse meterialFreeGoods = await GetMaterialFreeGoodsByFilterAsync(tc, + new GetMaterialFreeGoodsByFilterRequest() { SalesOrg = request.SalesOrg, DistributionChannel = request.DistributionChannel, + CustomerPriceGroup = request.CustomerPriceGroup,MaterialCode = request.MaterialCode }); + + if (meterialFreeGoods != null && !string.IsNullOrWhiteSpace(meterialFreeGoods.MaterialCode)) + { + UpdateMaterialFreeGoods(tc, request); + response.IsUpdated = true; + } + else + { + CreateMaterialFreeGoods(tc, request); + response.IsUpdated = false; + } + + response.SalesOrg = request.SalesOrg; + response.DistributionChannel = request.DistributionChannel; + response.CustomerPriceGroup = request.CustomerPriceGroup; + response.MaterialCode = request.MaterialCode; + tc.End(); + } + catch (Exception ie) + { + tc?.HandleError(); + + throw DBCustomError.GenerateCustomError(ie); + } + } + catch (Exception ex) + { + throw; + } + + return response; + } + + public async Task GetMaterialFreeGoodseByFilterAsync(GetMaterialFreeGoodsByFilterRequest request) + { + IntegrationMaterialFreeGoodsResponse response = new(); + try + { + using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode); + try + { + response = await GetMaterialFreeGoodsByFilterAsync(tc, request); + tc.End(); + } + catch (Exception ie) + { + tc?.HandleError(); + + throw DBCustomError.GenerateCustomError(ie); + } + } + catch (Exception ex) + { + throw; + } + + return response; + } + + #region Customer Private Functions private async Task GetCustomerByCompanyCodeAsync(TransactionContext tc, CustomerByCompanyCodeRequest request) @@ -948,4 +1020,127 @@ public class IntegrationService : IIntegrationService } #endregion + + #region Material Free Goods Private Functions + + private async Task GetMaterialFreeGoodsByFilterAsync( + TransactionContext tc, GetMaterialFreeGoodsByFilterRequest request) + { + IntegrationMaterialFreeGoodsResponse response = new IntegrationMaterialFreeGoodsResponse(); + + try + { + SqlParameter[] p = + [ + SqlHelperExtension.CreateInParam("@MaterialCode", SqlDbType.NVarChar, request.MaterialCode), + SqlHelperExtension.CreateInParam("@DistributionChannel", SqlDbType.NVarChar, request.DistributionChannel), + SqlHelperExtension.CreateInParam("@CustomerPriceGroup", SqlDbType.NVarChar, request.CustomerPriceGroup), + SqlHelperExtension.CreateInParam("@SalesOrg", SqlDbType.NVarChar, request.SalesOrg) + ]; + + using (IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetAllMaterialFreeGoodsForUpdate", parameterValues: p)) + { + while (dr.Read()) + { + response= new IntegrationMaterialFreeGoodsResponse() + { + FreeGoodsId = dr.GetInt32(0), + SalesOrg = dr.GetString(1), + DistributionChannel = dr.GetString(2), + CustomerNumber = dr.IsDBNull(3) ? null : dr.GetString(3), + CustomerPriceGroup = dr.GetString(4), + MaterialCode = dr.GetString(5), + MinOrderQuantity = dr.GetDecimal(6), + ForQuantity = dr.GetDecimal(7), + UnitOfMeasure = dr.GetString(8), + FreeQuantity = dr.GetDecimal(9), + FocUnitOfMeasure = dr.GetString(10), + ValidFrom = dr.GetDateTime(11), + ValidTo = dr.GetDateTime(12), + Status = dr.IsDBNull(13) ? null : dr.GetString(13) + }; + } + dr.Close(); + } + } + catch (Exception ex) + { + throw DBCustomError.GenerateCustomError(ex); + } + + return response; + } + + private int CreateMaterialFreeGoods(TransactionContext tc, IntegrationMaterialFreeGoodsRequest request) + { + int newFreeGoodsId = 0; + + try + { + SqlParameter[] p = + [ + SqlHelperExtension.CreateInParam("@SalesOrg", SqlDbType.NVarChar, request.SalesOrg), + SqlHelperExtension.CreateInParam("@DistributionChannel", SqlDbType.NVarChar, request.DistributionChannel), + SqlHelperExtension.CreateInParam("@CustomerNumber", SqlDbType.NVarChar, (object?)request.CustomerNumber ?? DBNull.Value), + SqlHelperExtension.CreateInParam("@CustomerPriceGroup", SqlDbType.NVarChar, request.CustomerPriceGroup), + SqlHelperExtension.CreateInParam("@MaterialCode", SqlDbType.NVarChar, request.MaterialCode), + SqlHelperExtension.CreateInParam("@MinOrderQuantity", SqlDbType.Decimal, request.MinOrderQuantity), + SqlHelperExtension.CreateInParam("@ForQuantity", SqlDbType.Decimal, request.ForQuantity), + SqlHelperExtension.CreateInParam("@UnitOfMeasure", SqlDbType.NVarChar, request.UnitOfMeasure), + SqlHelperExtension.CreateInParam("@FreeQuantity", SqlDbType.Decimal, request.FreeQuantity), + SqlHelperExtension.CreateInParam("@FocUnitOfMeasure", SqlDbType.NVarChar, request.FocUnitOfMeasure), + SqlHelperExtension.CreateInParam("@ValidFrom", SqlDbType.Date, request.ValidFrom), + SqlHelperExtension.CreateInParam("@ValidTo", SqlDbType.Date, request.ValidTo), + SqlHelperExtension.CreateInParam("@Status", SqlDbType.NVarChar, (object?)request.Status ?? DBNull.Value), + SqlHelperExtension.CreateOutParam("@NewFreeGoodsId", SqlDbType.Int) // index 14 + ]; + + _ = tc.ExecuteNonQuerySp(spName: "dbo.InsertIntegrationMaterialFreeGoods", parameterValues: p); + + newFreeGoodsId = (int)p[14].Value; + } + catch (Exception) + { + throw; + } + + return newFreeGoodsId; + } + + private bool UpdateMaterialFreeGoods(TransactionContext tc, IntegrationMaterialFreeGoodsRequest request) + { + bool returnValue = false; + + try + { + SqlParameter[] p = + [ + SqlHelperExtension.CreateInParam("@SalesOrg", SqlDbType.NVarChar, request.SalesOrg), + SqlHelperExtension.CreateInParam("@DistributionChannel", SqlDbType.NVarChar, request.DistributionChannel), + SqlHelperExtension.CreateInParam("@CustomerNumber", SqlDbType.NVarChar, string.IsNullOrWhiteSpace(request.CustomerNumber)? null : request.CustomerNumber ), + SqlHelperExtension.CreateInParam("@CustomerPriceGroup", SqlDbType.NVarChar, request.CustomerPriceGroup), + SqlHelperExtension.CreateInParam("@MaterialCode", SqlDbType.NVarChar, request.MaterialCode), + SqlHelperExtension.CreateInParam("@MinOrderQuantity", SqlDbType.Decimal, request.MinOrderQuantity), + SqlHelperExtension.CreateInParam("@ForQuantity", SqlDbType.Decimal, request.ForQuantity), + SqlHelperExtension.CreateInParam("@UnitOfMeasure", SqlDbType.NVarChar, request.UnitOfMeasure), + SqlHelperExtension.CreateInParam("@FreeQuantity", SqlDbType.Decimal, request.FreeQuantity), + SqlHelperExtension.CreateInParam("@FocUnitOfMeasure", SqlDbType.NVarChar, request.FocUnitOfMeasure), + SqlHelperExtension.CreateInParam("@ValidFrom", SqlDbType.Date, request.ValidFrom), + SqlHelperExtension.CreateInParam("@ValidTo", SqlDbType.Date, request.ValidTo), + SqlHelperExtension.CreateInParam("@Status", SqlDbType.NVarChar, (object?)request.Status ?? DBNull.Value) + ]; + + _ = tc.ExecuteNonQuerySp(spName: "dbo.UpdateIntegrationMaterialFreeGoods", parameterValues: p); + + returnValue = true; + } + catch (Exception) + { + throw; + } + + return returnValue; + } + + #endregion } diff --git a/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/IntegretionApi/IntegrationController.cs b/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/IntegretionApi/IntegrationController.cs index 21184c4..e818d46 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/IntegretionApi/IntegrationController.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/IntegretionApi/IntegrationController.cs @@ -151,7 +151,7 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers.IntegretionApi } /// - /// Insert or Update Material for SAP + /// Insert or Update Material Prices for SAP /// /// /// @@ -189,5 +189,48 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers.IntegretionApi return StatusCode(StatusCodes.Status500InternalServerError, response); } } + + /// + /// Insert or Update Material Prices for SAP + /// + /// + /// + /// + /// if created return 201 or if updated return 200 true + [HttpPost("MaterialFreeGoods")] + [IgnoreAntiforgeryToken] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(MaterialFreeGoodsByReqResponse))] + [ProducesResponseType(StatusCodes.Status201Created, Type = typeof(MaterialFreeGoodsByReqResponse))] + public async Task UpsertMaterialFreeGoods(IntegrationMaterialFreeGoodsRequest request) + { + MaterialFreeGoodsByReqResponse response = new MaterialFreeGoodsByReqResponse(); + try + { + response = await _integrationService.UpsertMaterialFreeGoodsAsync(request); + if (!response.IsUpdated) + { + response.ReturnMessage.Add($"Material Free Goods Created Successfully for SalesOrg :{request.SalesOrg} " + + $"And CustomerPriceGroup : {request.CustomerPriceGroup} And MaterialCode : {request.MaterialCode} And DistributorChannel : {request.DistributionChannel}"); + response.ReturnStatus = StatusCodes.Status201Created; + return StatusCode(StatusCodes.Status201Created, response); + } + else + { + response.ReturnMessage.Add($"Material Free Goods Updated Successfully for SalesOrg :{request.SalesOrg} " + + $"And CustomerPriceGroup : {request.CustomerPriceGroup} And MaterialCode : {request.MaterialCode} And DistributorChannel : {request.DistributionChannel}"); + response.ReturnStatus = StatusCodes.Status200OK; + return StatusCode(StatusCodes.Status200OK, response); + } + } + catch (Exception ex) + { + string msg = $"Exception Occur in Material Free Goods Operation SalesOrg :{request.SalesOrg} " + + $"And CustomerPriceGroup : {request.CustomerPriceGroup} And MaterialCode : {request.MaterialCode} And DistributorChannel : {request.DistributionChannel}" ; + _logger.LogError(exception: ex, msg); + response.ReturnStatus = StatusCodes.Status500InternalServerError; + response.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message); + return StatusCode(StatusCodes.Status500InternalServerError, response); + } + } } } \ No newline at end of file