using Ease.NetCore.DataAccess; using Ease.NetCore.DataAccess.SQL; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Options; using OnlineSalesAutoCrop.CoreAPI.Models; using OnlineSalesAutoCrop.CoreAPI.Models.Global; using OnlineSalesAutoCrop.CoreAPI.Models.Requests.MobileApp; using OnlineSalesAutoCrop.CoreAPI.Models.Responses; 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; using System.Threading.Tasks; namespace OnlineSalesAutoCrop.CoreAPI.Services.Services.MobileApp; public class OrderService : IOrderService { private readonly AppSettings _settings; private readonly IUserService _userService; private readonly IMobileMasterDataService _masterService; private readonly IIntegrationHttpService _httpService; public OrderService(IOptions options, IUserService userService, IMobileMasterDataService masterService, IIntegrationHttpService httpService) { _settings = options.Value; _userService = userService; _masterService = masterService; _httpService = httpService; } public async Task SaveOrderAsync(SaveOrderRequest request, int userId) { SaveOrderResponse response = new(); try { using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true); try { // 1. Role - fetched using the claims-resolved UserId UserRoleDto? userRole = await GetUserRoleAsync(tc, userId); if (userRole is null) throw new Exception($"User id '{userId}' was not found."); if (!IsPermissiontoModify(request.OrderStatus ,userRole.RoleId )) { throw new Exception("You are not permitted to submit of modify this order"); } var existingOrder = await GetExistingOrderIdAsync(tc, request.UUID); request.OrderId = (existingOrder != null && existingOrder.OrderId > 0) ? existingOrder.OrderId : 0; if (existingOrder is not null && existingOrder.OrderId > 0 && (existingOrder.OrderStatus == OrderStatusEnum.Approved || existingOrder.OrderStatus == OrderStatusEnum.CancelledByApprover || existingOrder.OrderStatus == OrderStatusEnum.CancelledByValidator)) { throw new Exception($"Order with UUID '{request.UUID}' has already been {existingOrder.OrderStatus.ToString()} and cannot be modified."); } foreach (var item in request.Items) { item.ItemId = 0; item.OrderId = request.OrderId; } //var materialStock = await _httpService.GetMaterialStockAsync(request.PlantCode); //if(materialStock is null || materialStock?.Count==0) //{ // throw new Exception($"Material Stocks not found against this Plant Code-{request.PlantCode}"); //} //foreach (var item in request.Items) //{ // decimal currentStock = materialStock.FirstOrDefault(x => x.MaterialCode == item.MaterialCode)?.AvailableStock ?? 0; // if(currentStock 0 ; // 3. Reference data - SPs only fetch, every pass/fail call happens here CustomerLookupDto? customer = await GetCustomerAsync(tc, request.CustomerCode); if (customer is null) throw new Exception($"Customer code '{request.CustomerCode}' does not exist."); request.CustomerId = customer.CustomerId; PaymentTermLookupDto? paymentTerm = await GetPaymentTermAsync(tc, request.PaymentTermCode, request.SalesOrgCode); if (paymentTerm is null) throw new Exception($"Payment term code '{request.PaymentTermCode}' does not exist for sales org '{request.SalesOrgCode}'."); DiscountLookupDto? discount = await GetDiscountAsync(tc, request.DiscountCode, request.SalesOrgCode); if (discount is null) throw new Exception($"Discount code '{request.DiscountCode}' does not exist."); bool focCodeExists = await CheckFocCodeExistsAsync(tc, request.FocCode, request.SalesOrgCode); if (!focCodeExists) throw new Exception($"FOC code '{request.FocCode}' does not exist."); if (request.Items is null || request.Items.Count == 0) throw new Exception("Order must contain at least one item."); Dictionary materialsByCode = new(StringComparer.OrdinalIgnoreCase); if (request.Items is { Count: > 0 }) { List distinctCodes = request.Items .Select(i => i.MaterialCode) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); materialsByCode = await GetMaterialsAsync(tc, distinctCodes); foreach (string code in distinctCodes) { if (!materialsByCode.ContainsKey(code)) throw new Exception($"Material code '{code}' does not exist."); } } var focItem = request.Items.FirstOrDefault(x => x.IsFocItem == true); var dicountItem = request.Items.FirstOrDefault(x => x.DiscountAmount > 0); List promotions = new List(); request.FocValue = 0; if(dicountItem!= null) { promotions.Add(new SaveOrderPromotionRequest() { OrderId = isUpdate ? existingOrder.OrderId : 0, MaterialId = materialsByCode[dicountItem.MaterialCode].MaterialId, PromotionCode = request.DiscountCode, DiscountPercentage = dicountItem.DiscountPercentage, DiscountValue = dicountItem.DiscountAmount, ForQuantity = dicountItem.PromoQuantity, OrderFocQty = 0, ValidatedFocQty = 0, ApprovedFocQty = 0, PromotionType = 1, }); } if(focItem != null) { promotions.Add(new SaveOrderPromotionRequest() { OrderId = isUpdate ? existingOrder.OrderId : 0, MaterialId = materialsByCode[focItem.MaterialCode].MaterialId, PromotionCode = request.FocCode, DiscountPercentage = 0, DiscountValue = 0, ForQuantity = focItem.PromoQuantity, OrderFocQty = (request.OrderStatus == OrderStatusEnum.Draft || request.OrderStatus == OrderStatusEnum.InOrderValidation) ? focItem.OrderQuantity : 0, ValidatedFocQty = (request.OrderStatus == OrderStatusEnum.CancelledByValidator || request.OrderStatus == OrderStatusEnum.InOrderApproval) ? focItem.OrderQuantity : 0, ApprovedFocQty = (request.OrderStatus == OrderStatusEnum.CancelledByApprover || request.OrderStatus == OrderStatusEnum.Approved) ? focItem.OrderQuantity : 0, PromotionType = 2, }); } decimal usedCreditBalance = await GetCurrentApprovedOrderAmountByCustomerAsync(tc, request.CustomerCode, request.SalesOrgCode); if (request.OrderStatus == OrderStatusEnum.Approved && customer.IsCreditCustomer && request.PaymentTermCode != "0001") { var customerLedger = await _httpService.GetCustomerLedgerAsync(request.CustomerCode, customer.CompanyCode); var creditBalance = customer.CreditLimit- (customerLedger.CurrentBalance + usedCreditBalance); if(creditBalance < request.NetValue) { throw new Exception("Credit Limit is extend for this customer"); } } // 5. Insert or update int orderId; if (isUpdate) { orderId = existingOrder.OrderId; await UpdateOrderAsync(tc, orderId, request, customer!, paymentTerm!, discount!, materialsByCode, promotions, userId,(int) userRole!.RoleId); response.IsNewOrder = !isUpdate; response.UUID = request.UUID; response.OrderId = orderId; response.OrderDate = existingOrder.OrderDate; response.OrderNo = existingOrder.OrderNo; } else { response = await InsertOrderAsync(tc, request, customer!, paymentTerm!, discount!, materialsByCode, promotions, userId,(int) userRole!.RoleId); } tc.End(); } catch (Exception ie) { tc?.HandleError(); throw DBCustomError.GenerateCustomError(ie); } } catch (Exception) { throw; } return response; } private async Task GetCurrentApprovedOrderAmountByCustomerAsync(TransactionContext tc, string customerCode, string salesOrgCode) { decimal totalApprovedAmount = 0; try { SqlParameter[] p = [ SqlHelperExtension.CreateInParam(pName: "@CustomerCode", pType: SqlDbType.NVarChar, pValue: customerCode), SqlHelperExtension.CreateInParam(pName: "@SalesOrgCode", pType: SqlDbType.NVarChar, pValue: salesOrgCode), ]; using (IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetCustomerApprovedAmount", p)) { if (dr.Read()) { totalApprovedAmount = dr.GetDecimal(0); } dr.Close(); } } catch (Exception) { throw; } return totalApprovedAmount; } private bool IsPermissiontoModify(OrderStatusEnum orderStatus, UserRoleTypeEnum roleId) { if( UserRoleTypeEnum.OrderCollector == roleId && ( OrderStatusEnum.Draft == orderStatus || OrderStatusEnum.InOrderValidation == orderStatus)) { return true; } if (UserRoleTypeEnum.OrderValidator == roleId && (OrderStatusEnum.CancelledByValidator == orderStatus || OrderStatusEnum.InOrderApproval == orderStatus)) { return true; } if (UserRoleTypeEnum.OrderApprover == roleId && (OrderStatusEnum.CancelledByApprover == orderStatus || OrderStatusEnum.Approved == orderStatus)) { return true; } if (UserRoleTypeEnum.OrderApproverWithoutValidated == roleId && (OrderStatusEnum.CancelledByApprover == orderStatus || OrderStatusEnum.Approved == orderStatus)) { return true; } return false; } private async Task GetUserRoleAsync(TransactionContext tc, int userId) { try { SqlParameter[] p = [ SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: userId) ]; using IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetUserRoleByUserId", parameterValues: p); if (dr.Read()) { return new UserRoleDto { UserId = dr.GetInt32(0), RoleId =(UserRoleTypeEnum) dr.GetInt32(1) }; } return null; } catch (Exception) { throw; } } private async Task GetExistingOrderIdAsync(TransactionContext tc, Guid uuid) { OrderBasicResponse response = null; try { SqlParameter[] p = [ SqlHelperExtension.CreateInParam(pName: "@UUID", pType: SqlDbType.UniqueIdentifier, pValue: uuid) ]; using IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetOrderHeaderByUUID", parameterValues: p); if( dr.Read()) { response = new OrderBasicResponse() { OrderId = dr.GetInt32(0), OrderNo = dr.GetString(1), OrderDate = dr.GetDateTime(2), UUID = uuid, OrderStatus = (OrderStatusEnum)dr.GetInt32(3) }; } dr.Close(); } catch (Exception) { throw; } return response; } private async Task GetCustomerAsync(TransactionContext tc, string customerCode) { try { SqlParameter[] p = [ SqlHelperExtension.CreateInParam(pName: "@CustomerCode", pType: SqlDbType.VarChar, pValue: customerCode) ]; using IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetCustomerByCode", parameterValues: p); if (dr.Read()) { return new CustomerLookupDto { CustomerId = dr.GetInt32(0), CustomerCode = dr.GetString(1), CustomerName = dr.GetString(2), IsCreditCustomer = dr.GetInt32(3) > 0, CompanyCode = dr.GetString(4), CreditLimit = dr.IsDBNull(5)? 0 : dr.GetDecimal(5) }; } return null; } catch (Exception) { throw; } } private async Task GetPaymentTermAsync(TransactionContext tc, string paymentTermCode, string salesOrgCode) { try { SqlParameter[] p = [ SqlHelperExtension.CreateInParam(pName: "@PaymentTermCode", pType: SqlDbType.VarChar, pValue: paymentTermCode), SqlHelperExtension.CreateInParam(pName: "@SalesOrgCode", pType: SqlDbType.VarChar, pValue: salesOrgCode) ]; using IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetPaymentTermByCode", parameterValues: p); if (dr.Read()) { return new PaymentTermLookupDto { PaymentTermId = dr.GetInt32(0), PaymentTermCode = dr.GetString(1) }; } return null; } catch (Exception) { throw; } } private async Task GetDiscountAsync(TransactionContext tc, string discountCode, string salesOrgCode) { try { SqlParameter[] p = [ SqlHelperExtension.CreateInParam(pName: "@DiscountCode", pType: SqlDbType.VarChar, pValue: discountCode), SqlHelperExtension.CreateInParam(pName: "@SalesOrgCode", pType: SqlDbType.VarChar, pValue: salesOrgCode) ]; using IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetDiscountByCode", parameterValues: p); if (dr.Read()) { return new DiscountLookupDto { DiscountId = dr.GetInt32(0), DiscountCode = dr.GetString(1) }; } return null; } catch (Exception) { throw; } } private async Task CheckFocCodeExistsAsync(TransactionContext tc, string focCode, string salesOrgCode) { try { SqlParameter[] p = [ SqlHelperExtension.CreateInParam(pName: "@SalesOrgCode", pType: SqlDbType.VarChar, pValue: salesOrgCode), SqlHelperExtension.CreateInParam(pName: "@FocCode", pType: SqlDbType.VarChar, pValue: focCode) ]; using IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.CheckFocCodeExists", parameterValues: p); return dr.Read(); } catch (Exception) { throw; } } private async Task> GetMaterialsAsync(TransactionContext tc, IEnumerable materialCodes) { try { Dictionary result = new(StringComparer.OrdinalIgnoreCase); DataTable codesTable = new(); codesTable.Columns.Add("Code", typeof(string)); foreach (string code in materialCodes) codesTable.Rows.Add(code); SqlParameter codesParam = SqlHelperExtension.CreateInParam(pName: "@Codes", pType: SqlDbType.Structured, pValue: codesTable); codesParam.TypeName = "dbo.CodeListTableType"; // CreateInParam has no TypeName slot, so it's set separately SqlParameter[] p = [codesParam]; using IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetMaterialsByCodes", parameterValues: p); while (dr.Read()) { MaterialLookupDto dto = new() { MaterialId = dr.GetInt32(0), MaterialCode = dr.GetString(1), MaterialName = dr.GetString(2) }; result[dto.MaterialCode] = dto; } dr.Close(); return result; } catch (Exception) { throw; } } private async Task> GetFreeGoodsRulesAsync(TransactionContext tc, string salesOrgCode, string focCode, IEnumerable materialIds, DateTime? asOfDate, int customerId) { try { List rules = new(); DataTable idsTable = new(); idsTable.Columns.Add("Id", typeof(int)); foreach (int id in materialIds) idsTable.Rows.Add(id); SqlParameter idsParam = SqlHelperExtension.CreateInParam(pName: "@MaterialIds", pType: SqlDbType.Structured, pValue: idsTable); idsParam.TypeName = "dbo.IntListTableType"; SqlParameter[] p = [ SqlHelperExtension.CreateInParam(pName: "@SalesOrgCode", pType: SqlDbType.VarChar, pValue: salesOrgCode), SqlHelperExtension.CreateInParam(pName: "@CustomerId", pType: SqlDbType.VarChar, pValue: customerId), SqlHelperExtension.CreateInParam(pName: "@FocCode", pType: SqlDbType.VarChar, pValue: focCode),idsParam, SqlHelperExtension.CreateInParam(pName: "@AsOfDate", pType: SqlDbType.DateTime, pValue: DateTime.Now) ]; using IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetFreeGoodsRules", parameterValues: p); while (dr.Read()) { rules.Add(new FreeGoodsRuleDto { FreeGoodsId = dr.GetInt32(0), MaterialId = dr.GetInt32(1), MinOrderQuantity = dr.GetDecimal(2), ForQuantity = dr.GetDecimal(3), FreeQuantity = dr.GetDecimal(4), UnitOfMeasure = dr.IsDBNull(5) ? string.Empty : dr.GetString(5), FocUnitOfMeasure = dr.IsDBNull(6) ? string.Empty : dr.GetString(6) }); } dr.Close(); return rules; } catch (Exception) { throw; } } // Once OrderQuantity >= MinOrderQuantity, the material earns FreeQuantity for every // ForQuantity units ordered (e.g. Min 10 / For 10 / Free 1 -> ordering 25 = floor(25/10)*1 = 2 free). // This is my read of "calculate based on order quantity" - swap the formula below if yours differs. private static List BuildFocPromotions(SaveOrderRequest request, Dictionary materialsByCode,List focRules) { try { List promotions = new(); Dictionary rulesByMaterialId = focRules .GroupBy(r => r.MaterialId) .ToDictionary(g => g.Key, g => g.First()); // first active rule wins if more than one overlaps foreach (SaveOrderItemRequest item in request.Items) { if (item.IsFocItem) continue; // a free item doesn't itself trigger more free goods if (!materialsByCode.TryGetValue(item.MaterialCode, out MaterialLookupDto? material)) continue; // already caught by validation - guarded here defensively if (!rulesByMaterialId.TryGetValue(material.MaterialId, out FreeGoodsRuleDto? rule)) continue; // no free-goods rule configured for this material under this FOC code if (rule.ForQuantity <= 0 || item.OrderQuantity < rule.MinOrderQuantity) continue; decimal eligibleSets = Math.Floor(item.OrderQuantity / rule.ForQuantity); decimal focQuantity = eligibleSets * rule.FreeQuantity; if (focQuantity <= 0) continue; promotions.Add(new SaveOrderPromotionRequest { MaterialId = material.MaterialId, PromotionCode = request.FocCode, DiscountPercentage = 0, DiscountValue = 0, ForQuantity = rule.ForQuantity, OrderFocQty = focQuantity, ValidatedFocQty = 0, ApprovedFocQty = 0, PromotionType = 1 // TODO: swap for your real "free goods" PromotionType constant }); } return promotions; } catch (Exception) { throw; } } private static DataTable ToOrderItemTable(IEnumerable items, Dictionary materialsByCode, int orderStatus ) { try { DataTable table = new(); table.Columns.Add("OrderId", typeof(int)); table.Columns.Add("ItemId", typeof(int)); table.Columns.Add("MaterialId", typeof(int)); table.Columns.Add("MaterialCode", typeof(string)); table.Columns.Add("MaterialName", typeof(string)); table.Columns.Add("UnitPrice", typeof(decimal)); table.Columns.Add("SalesUnit", typeof(string)); table.Columns.Add("PromoQty", typeof(decimal)); table.Columns.Add("OrderQuantity", typeof(decimal)); table.Columns.Add("ValidatedQuantity", typeof(decimal)); table.Columns.Add("ApprovedQuantity", typeof(decimal)); table.Columns.Add("DiscountPercentage", typeof(decimal)); table.Columns.Add("DiscountAmount", typeof(decimal)); table.Columns.Add("IsFocItem", typeof(bool)); table.Columns.Add("LineTotalValue", typeof(decimal)); table.Columns.Add("VatValue", typeof(decimal)); table.Columns.Add("OrderStatus", typeof(int)); foreach (SaveOrderItemRequest item in items) { MaterialLookupDto material = materialsByCode[item.MaterialCode]; table.Rows.Add( item.ItemId, item.OrderId, material.MaterialId, item.MaterialCode, item.MaterialName ?? material.MaterialName, item.UnitPrice, item.SalesUnit, item.PromoQuantity, item.OrderQuantity, item.ValidatedQuantity, item.ApprovedQuantity, item.DiscountPercentage, item.DiscountAmount, item.IsFocItem, item.LineTotalValue, item.VatValue, orderStatus); } return table; } catch (Exception) { throw; } } private static DataTable ToOrderPromotionTable(IEnumerable promotions) { try { DataTable table = new(); table.Columns.Add("ItemId", typeof(int)); table.Columns.Add("OrderId", typeof(int)); table.Columns.Add("MaterialId", typeof(int)); table.Columns.Add("PromotionCode", typeof(string)); table.Columns.Add("DiscountPercentage", typeof(decimal)); table.Columns.Add("DiscountValue", typeof(decimal)); table.Columns.Add("ForQuantity", typeof(decimal)); table.Columns.Add("OrderFocQty", typeof(decimal)); table.Columns.Add("ValidatedFocQty", typeof(decimal)); table.Columns.Add("ApprovedFocQty", typeof(decimal)); table.Columns.Add("PromotionType", typeof(int)); foreach (SaveOrderPromotionRequest promo in promotions) { table.Rows.Add( promo.ItemId, promo.OrderId, promo.MaterialId, promo.PromotionCode , promo.DiscountPercentage, promo.DiscountValue, promo.ForQuantity, promo.OrderFocQty, promo.ValidatedFocQty, promo.ApprovedFocQty, promo.PromotionType); } return table; } catch (Exception) { throw; } } private async Task InsertOrderAsync(TransactionContext tc, SaveOrderRequest request, CustomerLookupDto customer, PaymentTermLookupDto paymentTerm, DiscountLookupDto discount, Dictionary materialsByCode, List promotions, int userId, int role) { try { SqlParameter itemsParam = SqlHelperExtension.CreateInParam(pName: "@Items", pType: SqlDbType.Structured, pValue: ToOrderItemTable(request.Items, materialsByCode, (int)request.OrderStatus)); itemsParam.TypeName = "dbo.OrderItemTableType"; SqlParameter promotionsParam = SqlHelperExtension.CreateInParam(pName: "@Promotions", pType: SqlDbType.Structured, pValue: ToOrderPromotionTable(promotions)); promotionsParam.TypeName = "dbo.OrderPromotionTableType"; SqlParameter[] p = [ SqlHelperExtension.CreateInParam(pName: "@UUID", pType: SqlDbType.UniqueIdentifier, pValue: request.UUID), SqlHelperExtension.CreateInParam(pName: "@OrderNo", pType: SqlDbType.VarChar, pValue: request.OrderNo), SqlHelperExtension.CreateInParam(pName: "@OrderDate", pType: SqlDbType.DateTime, pValue: request.OrderDate ?? DateTime.Now ), SqlHelperExtension.CreateInParam(pName: "@ExpectedDeliveryDate", pType: SqlDbType.DateTime, pValue: request.ExpectedDeliveryDate ), SqlHelperExtension.CreateInParam(pName: "@SalesOrgCode", pType: SqlDbType.VarChar, pValue: request.SalesOrgCode), SqlHelperExtension.CreateInParam(pName: "@CustomerId", pType: SqlDbType.Int, pValue: customer.CustomerId), SqlHelperExtension.CreateInParam(pName: "@CustomerCode", pType: SqlDbType.VarChar, pValue: request.CustomerCode), SqlHelperExtension.CreateInParam(pName: "@CustomerName", pType: SqlDbType.VarChar, pValue: request.CustomerName), SqlHelperExtension.CreateInParam(pName: "@EmployeeId", pType: SqlDbType.Int, pValue: request.EmployeeId ), SqlHelperExtension.CreateInParam(pName: "@EmployeeCode", pType: SqlDbType.VarChar, pValue: request.EmployeeCode ), SqlHelperExtension.CreateInParam(pName: "@EmployeeName", pType: SqlDbType.VarChar, pValue: request.EmployeeCode ), SqlHelperExtension.CreateInParam(pName: "@PaymentTermCode", pType: SqlDbType.VarChar, pValue: request.PaymentTermCode), SqlHelperExtension.CreateInParam(pName: "@DiscountCode", pType: SqlDbType.VarChar, pValue: request.DiscountCode), SqlHelperExtension.CreateInParam(pName: "@FOCCode", pType: SqlDbType.VarChar, pValue: request.FocCode), SqlHelperExtension.CreateInParam(pName: "@ReferenceTxtNo", pType: SqlDbType.VarChar, pValue: request.ReferenceTxtNo ), SqlHelperExtension.CreateInParam(pName: "@GrossValue", pType: SqlDbType.Decimal, pValue: request.GrossValue), SqlHelperExtension.CreateInParam(pName: "@DiscountValue", pType: SqlDbType.Decimal, pValue: request.DiscountValue), SqlHelperExtension.CreateInParam(pName: "@TotalVatValue", pType: SqlDbType.VarChar, pValue: request.TotalVatValue), SqlHelperExtension.CreateInParam(pName: "@FOCValue", pType: SqlDbType.Decimal, pValue: request.FocCode), SqlHelperExtension.CreateInParam(pName: "@NetValue", pType: SqlDbType.Decimal, pValue: request.NetValue), SqlHelperExtension.CreateInParam(pName: "@OrderStatus", pType: SqlDbType.Int, pValue: (int)request.OrderStatus), SqlHelperExtension.CreateInParam(pName: "@SapRefferenceOrderNo", pType: SqlDbType.NVarChar, pValue: null), SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: userId), itemsParam, promotionsParam ]; using IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.InsertOrder", parameterValues: p); int newOrderId = 0; string orderNo = string.Empty; if (dr.Read()) { newOrderId = dr.GetInt32(0); orderNo = dr.GetString(1); } dr.Close(); return new SaveOrderResponse() { IsNewOrder=true, OrderDate= DateTime.Now, OrderId= newOrderId,OrderNo =orderNo,UUID = request.UUID}; } catch (Exception) { throw; } } private async Task UpdateOrderAsync(TransactionContext tc, int orderId, SaveOrderRequest request, CustomerLookupDto customer, PaymentTermLookupDto paymentTerm, DiscountLookupDto discount, Dictionary materialsByCode, List promotions, int userId, int role) { try { SqlParameter itemsParam = SqlHelperExtension.CreateInParam(pName: "@Items", pType: SqlDbType.Structured, pValue: ToOrderItemTable(request.Items, materialsByCode, (int)request.OrderStatus)); itemsParam.TypeName = "dbo.OrderItemTableType"; SqlParameter promotionsParam = SqlHelperExtension.CreateInParam(pName: "@Promotions", pType: SqlDbType.Structured, pValue: ToOrderPromotionTable(promotions)); promotionsParam.TypeName = "dbo.OrderPromotionTableType"; SqlParameter[] p = [ SqlHelperExtension.CreateInParam(pName: "@OrderId", pType: SqlDbType.Int, pValue: orderId), SqlHelperExtension.CreateInParam(pName: "@ExpectedDeliveryDate", pType: SqlDbType.DateTime, pValue: request.ExpectedDeliveryDate ), SqlHelperExtension.CreateInParam(pName: "@PaymentTermCode", pType: SqlDbType.VarChar, pValue: request.PaymentTermCode), SqlHelperExtension.CreateInParam(pName: "@DiscountCode", pType: SqlDbType.VarChar, pValue: request.DiscountCode), SqlHelperExtension.CreateInParam(pName: "@FOCCode", pType: SqlDbType.VarChar, pValue: request.FocCode), SqlHelperExtension.CreateInParam(pName: "@ReferenceTxtNo", pType: SqlDbType.VarChar, pValue: request.ReferenceTxtNo ), SqlHelperExtension.CreateInParam(pName: "@GrossValue", pType: SqlDbType.Decimal, pValue: request.GrossValue), SqlHelperExtension.CreateInParam(pName: "@DiscountValue", pType: SqlDbType.Decimal, pValue: request.DiscountValue), SqlHelperExtension.CreateInParam(pName: "@TotalVatValue", pType: SqlDbType.VarChar, pValue: request.TotalVatValue), SqlHelperExtension.CreateInParam(pName: "@FOCValue", pType: SqlDbType.Decimal, pValue: request.FocCode), SqlHelperExtension.CreateInParam(pName: "@NetValue", pType: SqlDbType.Decimal, pValue: request.NetValue), SqlHelperExtension.CreateInParam(pName: "@OrderStatus", pType: SqlDbType.Int, pValue: (int)request.OrderStatus), SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: userId), itemsParam, promotionsParam ]; using IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.UpdateOrder", parameterValues: p); dr.Close(); } catch (Exception) { throw; } } public async Task> GetSalesOrdersSummary(GetOrderSummaryRequest request, int userId) { PagedResult response = null; List orders = new(); List orderItems = new(); try { using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode); try { SqlParameter[] p = [ SqlHelperExtension.CreateInParam(pName: "@SalesOrgCode", pType: SqlDbType.VarChar, pValue:request.SalesOrgCode) , SqlHelperExtension.CreateInParam(pName: "@EmployeeCode", pType: SqlDbType.VarChar, pValue:request.EmployeeCode) , SqlHelperExtension.CreateInParam(pName: "@OrderNo", pType: SqlDbType.VarChar, pValue: request.OrderNo ), SqlHelperExtension.CreateInParam(pName: "@UUID", pType: SqlDbType.VarChar, pValue: request.UUID ), SqlHelperExtension.CreateInParam(pName: "@CustomerCode", pType: SqlDbType.VarChar, pValue: request.CustomerCode ), SqlHelperExtension.CreateInParam(pName: "@OrderStatus", pType: SqlDbType.Int, pValue: (int?)request.OrderStatus ), SqlHelperExtension.CreateInParam(pName: "@StartDate", pType: SqlDbType.DateTime, pValue: request.StartDate ), SqlHelperExtension.CreateInParam(pName: "@EndDate", pType: SqlDbType.DateTime, pValue: request.EndDate ), SqlHelperExtension.CreateInParam(pName: "@PageNumber", pType: SqlDbType.Int, pValue: request.PageNumber ), SqlHelperExtension.CreateInParam(pName: "@PageSize", pType: SqlDbType.Int, pValue: request.PageSize ) ]; using (IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetOrderSummaryPaginated", parameterValues: p)) { // --- Result set 1: order header --- while (dr.Read()) { orders.Add( new GetSalesOrderResponse { OrderId = dr.GetInt32(16), Uuid = dr.GetGuid(0), OrderNo = dr.GetString(1), OrderDate = dr.GetDateTime(2), ExpectedDeliveryDate = dr.IsDBNull(3) ? null : dr.GetDateTime(3), SalesOrgCode = dr.GetString(4), CustomerCode = dr.GetString(5), CustomerName = dr.GetString(6), ReferenceTxtNo = dr.IsDBNull(7) ? null : dr.GetString(7), EmployeeCode = dr.GetString(8), PaymentTermCode = dr.GetString(9), DiscountCode = dr.IsDBNull(10) ? string.Empty : dr.GetString(10), FocCode = dr.IsDBNull(11) ? string.Empty : dr.GetString(11), DiscountValue = dr.GetDecimal(12), GrossValue = dr.GetDecimal(13), NetValue = dr.GetDecimal(14), OrderStatus = dr.GetInt32(15), TotalVatValue = dr.IsDBNull(17) ? null: dr.GetDecimal(17), BillingNumber =dr.IsDBNull(18) ? null : dr.GetString(18), BillingDate =dr.IsDBNull(19) ? null : dr.GetDateTime(19) }); } // --- Result set 2: order items --- if (orders.Count >0 && dr.NextResult()) { while (dr.Read()) { orderItems.Add(new GetSalesOrderItemResponse { OrderId = dr.GetInt32(0), UUID = dr.GetGuid(1), MaterialCode = dr.GetString(2), MaterialName = dr.GetString(3), UnitPrice = dr.GetDecimal(4), SalesUnit = dr.GetString(5), PromoQuantity =dr.IsDBNull(6)? null : dr.GetDecimal(6), OrderQuantity = dr.IsDBNull(7)? null : dr.GetDecimal(7), ValidatedQuantity = dr.IsDBNull(8) ? null : dr.GetDecimal(8), ApprovedQuantity = dr.IsDBNull(9) ? null : dr.GetDecimal(9), DiscountPercentage = dr.IsDBNull(10) ? null : dr.GetDecimal(10), DiscountAmount = dr.IsDBNull(11) ? null : dr.GetDecimal(11), IsFocItem = dr.GetInt32(12) > 0, LineTotalValue = dr.GetDecimal(13), VatValue = dr.IsDBNull(14) ? null : dr.GetDecimal(14) }); } } dr.Close(); } tc.End(); foreach (var order in orders) { order.Items = orderItems.Where(i => i.OrderId == order.OrderId).ToList(); } response = new PagedResult { Items = orders, TotalCount = orders.Count, PageNumber = request.PageNumber, PageSize = request.PageSize }; } catch (Exception ie) { tc?.HandleError(); throw DBCustomError.GenerateCustomError(ie); } } catch (Exception ex) { throw; } return response; } public async Task> GetAvailableOrderIdsForSAPAsync(SAPOrderStatusEnum orderStaus, CancellationToken cancellationToken = default) { List response = new List(); try { using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode); try { SqlParameter[] p = [ SqlHelperExtension.CreateInParam(pName: "@OrderStatus", pType: SqlDbType.Int, pValue: (int?)orderStaus ), ]; using (IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetAvailableOrderIdsByStatus", parameterValues: p)) { while (dr.Read()) { response.Add(dr.GetInt32(0)); } dr.Close(); } tc.End(); } catch (Exception ie) { tc?.HandleError(); throw DBCustomError.GenerateCustomError(ie); } } catch (Exception ex) { throw; } return response; } public async Task GetOrderDetailsForSapByIdAsync(int orderId, CancellationToken cancellationToken = default) { GetOrderForSAPResponse response = null; try { using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode); try { SqlParameter[] p = [ SqlHelperExtension.CreateInParam(pName: "@OrderId", pType: SqlDbType.Int, pValue: orderId ), ]; using (IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetOrderDetailsById", parameterValues: p)) { while (dr.Read()) { response = new GetOrderForSAPResponse() { SalesOrderType = dr.GetString(0), SalesOrganization = dr.GetString(1), DistributionChannel = dr.GetString(2), OrganizationDivision = dr.GetString(3), SoldToParty = dr.GetString(4), PurchaseOrderByCustomer = dr.GetString(5), CustomerPurchaseOrderDate = ConvertOrderDateIntoMilisec( dr.GetDateTime(6)), PurchaseOrderByShipToParty =dr.IsDBNull(7) ? null : dr.GetString(7), CustomerPaymentTerms = dr.IsDBNull(8) ? null : dr.GetString(8), CustomerGroup = dr.GetString(9), CustomerPriceGroup = dr.GetString(10) }; } if (response is not null && dr.NextResult()) { while (dr.Read()) { response.to_Item.Add(new GetOrderItemForSAPResponse { Material = dr.GetString(0), RequestedQuantity =Convert.ToString( dr.IsDBNull(1)? 0 : dr.GetDecimal(1)) }); } } dr.Close(); } tc.End(); } catch (Exception ie) { tc?.HandleError(); throw DBCustomError.GenerateCustomError(ie); } } catch (Exception ex) { throw; } return response; } private string ConvertOrderDateIntoMilisec(DateTime orderDate) { long milliseconds = new DateTimeOffset(orderDate).ToUnixTimeMilliseconds(); string sapDate = $"/Date({milliseconds})/"; return sapDate; } public async Task UpdateSendingSapDetailsAsync(UpdateSapDetailsRequest request, CancellationToken cancellationToken = default) { bool response = false; try { using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true); try { SqlParameter[] p = [ SqlHelperExtension.CreateInParam(pName: "@OrderId", pType: SqlDbType.Int, pValue: request.OrderId ), SqlHelperExtension.CreateInParam(pName: "@OrderNo", pType: SqlDbType.NVarChar, pValue: request.SapOrderNo ), SqlHelperExtension.CreateInParam(pName: "@Status", pType: SqlDbType.Int, pValue: request.Status ), SqlHelperExtension.CreateInParam(pName: "@SentAt", pType: SqlDbType.DateTime, pValue: DateTime.Now ), SqlHelperExtension.CreateInParam(pName: "@RetryCount", pType: SqlDbType.Int, pValue: request.RetryCount ), SqlHelperExtension.CreateInParam(pName: "@LastError", pType: SqlDbType.Int, pValue: request.LastError ), SqlHelperExtension.CreateInParam(pName: "@ReturnMessage", pType: SqlDbType.Int, pValue: request.ReturnMessage ), ]; tc.ExecuteNonQuerySp("dbo.UpdateSendingSapDetails", parameterValues: p); tc.End(); } catch (Exception ie) { tc?.HandleError(); throw DBCustomError.GenerateCustomError(ie); } } catch (Exception ex) { throw; } return response; } }