public virtual async Task HandleRefundTransaction(HSOrderWorksheet worksheet, HSPayment creditCardPayment, HSPaymentTransaction creditCardPaymentTransaction, decimal totalToRefund, RMA rma) { try { CardConnectRefundRequest requestedRefund = new CardConnectRefundRequest() { currency = worksheet.Order.xp.Currency.ToString(), merchid = creditCardPaymentTransaction.xp.CardConnectResponse.merchid, retref = creditCardPaymentTransaction.xp.CardConnectResponse.retref, amount = totalToRefund.ToString("F2"), }; CardConnectRefundResponse response = await _cardConnect.Refund(requestedRefund); HSPayment newCreditCardPayment = new HSPayment() { Amount = response.amount }; await _oc.Payments.CreateTransactionAsync(OrderDirection.Incoming, rma.SourceOrderID, creditCardPayment.ID, CardConnectMapper.Map(newCreditCardPayment, response)); } catch (CreditCardRefundException ex) { throw new CatalystBaseException(new ApiError { ErrorCode = "Payment.FailedToRefund", Message = ex.ApiError.Message }); } }
private async Task <List <HSShipEstimate> > ApplyFreeShipping(HSOrderWorksheet orderWorksheet, IList <HSShipEstimate> shipEstimates) { var supplierIDs = orderWorksheet.LineItems.Select(li => li.SupplierID); var suppliers = await _oc.Suppliers.ListAsync <HSSupplier>(filters : $"ID={string.Join("|", supplierIDs)}"); var updatedEstimates = new List <HSShipEstimate>(); foreach (var estimate in shipEstimates) { // get supplier and supplier subtotal var supplierID = orderWorksheet.LineItems.First(li => li.ID == estimate.ShipEstimateItems.FirstOrDefault()?.LineItemID).SupplierID; var supplier = suppliers.Items.FirstOrDefault(s => s.ID == supplierID); var supplierLineItems = orderWorksheet.LineItems.Where(li => li.SupplierID == supplier?.ID); var supplierSubTotal = supplierLineItems.Select(li => li.LineSubtotal).Sum(); if (supplier?.xp?.FreeShippingThreshold != null && supplier.xp?.FreeShippingThreshold < supplierSubTotal) // free shipping for this supplier { foreach (var method in estimate.ShipMethods) { // free shipping on ground shipping or orders where we weren't able to calculate a shipping rate if (method.Name.Contains("GROUND") || method.ID == ShippingConstants.NoRatesID) { method.xp.FreeShippingApplied = true; method.xp.FreeShippingThreshold = supplier.xp.FreeShippingThreshold; method.Cost = 0; } } } updatedEstimates.Add(estimate); } return(updatedEstimates); }
public decimal GetPurchaseOrderTotal(HSOrderWorksheet worksheet) { return(worksheet.LineItems .Where(li => li.Product.xp.ProductType == ProductType.PurchaseOrder) .Select(li => li.LineTotal) .Sum()); }
private async Task <ShipEstimateResponse> GetRatesAsync(HSOrderWorksheet worksheet, CheckoutIntegrationConfiguration config = null) { var groupedLineItems = worksheet.LineItems.GroupBy(li => new AddressPair { ShipFrom = li.ShipFromAddress, ShipTo = li.ShippingAddress }).ToList(); var shipResponse = (await _shippingService.GetRates(groupedLineItems, _profiles)).Reserialize <HSShipEstimateResponse>(); // include all accounts at this stage so we can save on order worksheet and analyze // Certain suppliers use certain shipping accounts. This filters available rates based on those accounts. for (var i = 0; i < groupedLineItems.Count; i++) { var supplierID = groupedLineItems[i].First().SupplierID; var profile = _profiles.FirstOrDefault(supplierID); var methods = FilterMethodsBySupplierConfig(shipResponse.ShipEstimates[i].ShipMethods.Where(s => profile.CarrierAccountIDs.Contains(s.xp.CarrierAccountID)).ToList(), profile); shipResponse.ShipEstimates[i].ShipMethods = methods.Select(s => { // there is logic here to support not marking up shipping over list rate. But USPS is always list rate // so adding an override to the suppliers that use USPS var carrier = _profiles.ShippingProfiles.First(p => p.CarrierAccountIDs.Contains(s.xp?.CarrierAccountID)); s.Cost = carrier.MarkupOverride ? s.xp.OriginalCost * carrier.Markup : Math.Min((s.xp.OriginalCost * carrier.Markup), s.xp.ListRate); return(s); }).ToList(); } var buyerCurrency = worksheet.Order.xp.Currency ?? CurrencySymbol.USD; await shipResponse.ShipEstimates .CheckForEmptyRates(_settings.EasyPostSettings.NoRatesFallbackCost, _settings.EasyPostSettings.NoRatesFallbackTransitDays) .ApplyShippingLogic(worksheet, _oc, _settings.EasyPostSettings.FreeShippingTransitDays).Result .ConvertCurrency(CurrencySymbol.USD, buyerCurrency, _exchangeRates); return(shipResponse); }
public async Task <OrderSubmitResponse> HandleBuyerOrderSubmit(HSOrderWorksheet orderWorksheet) { var results = new List <ProcessResult>(); // STEP 1 var(supplierOrders, buyerOrder, activities) = await HandlingForwarding(orderWorksheet); results.Add(new ProcessResult() { Type = ProcessType.Forwarding, Activity = activities }); // step 1 failed. we don't want to attempt the integrations. return error for further action if (activities.Any(a => !a.Success)) { return(await CreateOrderSubmitResponse(results, new List <HSOrder> { orderWorksheet.Order })); } // STEP 2 (integrations) var integrations = await HandleIntegrations(supplierOrders, buyerOrder); results.AddRange(integrations); // STEP 3: return OrderSubmitResponse return(await CreateOrderSubmitResponse(results, new List <HSOrder> { orderWorksheet.Order })); }
private static void CalculateDiscountByPromoCode(string promoCode, HSOrderWorksheet order, List <OrderPromotion> promosOnOrder, Dictionary <string, decimal> totalWeightedLineItemDiscounts) { // Total discounted from this code for all line items decimal totalDiscountedByOrderCloud = promosOnOrder .Where(promo => promo.Code == promoCode) .Select(promo => promo.Amount) .Sum(); // Line items discounted with this code List <string> eligibleLineItemIDs = promosOnOrder .Where(promo => promo.Code == promoCode) .Select(promo => promo.LineItemID) .ToList(); // Subtotal of all of these line items before applying promo code decimal eligibleLineItemSubtotal = order.LineItems .Where(lineItem => eligibleLineItemIDs.Contains(lineItem.ID)) .Select(lineItem => lineItem.LineSubtotal) .Sum(); Dictionary <string, decimal> weightedLineItemDiscountsByPromoCode = new Dictionary <string, decimal>(); HandleWeightedDiscounting(eligibleLineItemIDs, weightedLineItemDiscountsByPromoCode, order, eligibleLineItemSubtotal, totalDiscountedByOrderCloud); foreach (string lineItemID in eligibleLineItemIDs) { totalWeightedLineItemDiscounts[lineItemID] += weightedLineItemDiscountsByPromoCode[lineItemID]; } }
private async Task <ProcessResult> PerformZohoTasks(HSOrderWorksheet worksheet, IList <HSOrder> supplierOrders) { var(salesAction, zohoSalesOrder) = await ProcessActivityCall( ProcessType.Accounting, "Create Zoho Sales Order", _zoho.CreateSalesOrder(worksheet)); var(poAction, zohoPurchaseOrder) = await ProcessActivityCall( ProcessType.Accounting, "Create Zoho Purchase Order", _zoho.CreateOrUpdatePurchaseOrder(zohoSalesOrder, supplierOrders.ToList())); var(shippingAction, zohoShippingOrder) = await ProcessActivityCall( ProcessType.Accounting, "Create Zoho Shipping Purchase Order", _zoho.CreateShippingPurchaseOrder(zohoSalesOrder, worksheet)); return(new ProcessResult() { Type = ProcessType.Accounting, Activity = new List <ProcessResultAction>() { salesAction, poAction, shippingAction } }); }
private async Task <List <ZohoLineItem> > ApplyShipping(HSOrderWorksheet orderWorksheet) { var list = new List <ZohoLineItem>(); if (orderWorksheet.ShipEstimateResponse == null) { return(list); } foreach (var shipment in orderWorksheet.ShipEstimateResponse.ShipEstimates) { var method = shipment.ShipMethods.FirstOrDefault(s => s.ID == shipment.SelectedShipMethodID); var z_shipping = await _zoho.Items.ListAsync(new ZohoFilter() { Key = "sku", Value = method?.ShippingSku() }); if (z_shipping.Items.Any()) { list.Add(await _zoho.Items.SaveAsync(ZohoShippingLineItemMapper.Map(z_shipping.Items.FirstOrDefault(), method))); } else { list.Add(await _zoho.Items.CreateAsync(ZohoShippingLineItemMapper.Map(method))); } } return(list); }
private async Task <HSOrderWorksheet> BuildSupplierOrderWorksheet(HSOrderWorksheet orderWorksheet, string supplierID) { var supplierOrderWorksheet = await _oc.IntegrationEvents.GetWorksheetAsync <HSOrderWorksheet>(OrderDirection.Outgoing, $"{orderWorksheet.Order.ID}-{supplierID}"); supplierOrderWorksheet.Order.BillingAddress = orderWorksheet.Order.BillingAddress; supplierOrderWorksheet.Order.FromUser = orderWorksheet.Order.FromUser; return(supplierOrderWorksheet); }
public decimal GetPurchaseOrderTotal(HSOrderWorksheet worksheet) { var shippingTotal = GetPurchaseOrderShipping(worksheet); return(worksheet.GetLineItemsByProductType(ProductType.PurchaseOrder) .Where(li => li.Product.xp.ProductType == ProductType.PurchaseOrder) .Select(li => li.LineTotal) .Sum() + shippingTotal); }
public virtual decimal GetShippingRefundIfCancellingAll(HSOrderWorksheet worksheet, RMA rma, CosmosListPage <RMA> allRMAsOnThisOrder) { // What are all the line items on this order for this supplier and their quantities? IEnumerable <HSLineItem> allLineItemsShippedFromThisSupplier = worksheet.LineItems .Where(li => li.SupplierID == rma.SupplierID && worksheet.Order.xp.PaymentMethod == "Credit Card"); Dictionary <string, int> allLineItemsDictionary = new Dictionary <string, int>(); foreach (HSLineItem li in allLineItemsShippedFromThisSupplier) { allLineItemsDictionary.Add(li.ID, li.Quantity); } // Including this RMA and previous RMAs for this supplier, get everything that has been refunded or is about to be refunded. var rmasFromThisSupplier = allRMAsOnThisOrder.Items.Where(r => r.SupplierID == rma.SupplierID); Dictionary <string, int> allCompleteRMALineItemsDictionary = new Dictionary <string, int>(); foreach (RMA existingRMA in rmasFromThisSupplier) { RMA rmaToAnalyze = existingRMA.RMANumber == rma.RMANumber ? rma : existingRMA; foreach (RMALineItem rmaLineItem in rmaToAnalyze.LineItems) { if (rmaLineItem.Status == RMALineItemStatus.Complete && rmaLineItem.RefundableViaCreditCard) { if (!allCompleteRMALineItemsDictionary.ContainsKey(rmaLineItem.ID)) { allCompleteRMALineItemsDictionary.Add(rmaLineItem.ID, rmaLineItem.QuantityProcessed); } else { allCompleteRMALineItemsDictionary[rmaLineItem.ID] += rmaLineItem.QuantityProcessed; } } } } // If these are the same, the supplier hasn't shipped anything, and shipping should be credited back to the buyer. bool shouldShippingBeCanceled = allLineItemsDictionary.OrderBy(kvp => kvp.Key) .SequenceEqual(allCompleteRMALineItemsDictionary.OrderBy(kvp => kvp.Key)); // Figure out what the buyer paid for shipping for this supplier on this order. if (shouldShippingBeCanceled) { string selectedShipMethodID = worksheet.ShipEstimateResponse.ShipEstimates .FirstOrDefault(estimate => estimate.xp.SupplierID == rma.SupplierID)?.SelectedShipMethodID; TransactionLineModel shippingLine = worksheet.OrderCalculateResponse.xp.TaxResponse.lines.FirstOrDefault(line => line.lineNumber == selectedShipMethodID); decimal shippingCostToRefund = (decimal)(shippingLine.taxableAmount + shippingLine.tax + shippingLine.exemptAmount); rma.ShippingCredited += shippingCostToRefund; return(shippingCostToRefund); } return(0M); }
public async Task PatchOrder(HSOrderWorksheet buyerOrder) { var payment = (await _oc.Payments.ListAsync(OrderDirection.Incoming, buyerOrder.Order.ID))?.Items?.FirstOrDefault(); var patchObj = new PartialOrder() { xp = new OrderXp() { HasSellerProducts = buyerOrder.LineItems.Any(li => li.SupplierID == null), PaymentMethod = payment.Type == PaymentType.CreditCard ? "Credit Card" : "Purchase Order" } }; await _oc.Orders.PatchAsync(OrderDirection.Incoming, buyerOrder.Order.ID, patchObj); }
public async Task <RMAWithLineItemStatusByQuantity> ProcessRefund(string rmaNumber, DecodedToken decodedToken) { var me = await _oc.Me.GetAsync(accessToken : decodedToken.AccessToken); RMA rma = await GetRMA(rmaNumber, decodedToken); ValidateRMA(rma, me.Supplier.ID); decimal initialAmountRefunded = rma.TotalCredited; IEnumerable <RMALineItem> rmaLineItemsToUpdate = rma.LineItems .Where(li => !li.IsRefunded && (li.Status == RMALineItemStatus.Approved || li.Status == RMALineItemStatus.PartialQtyApproved)).ToList(); HSOrderWorksheet worksheet = await _oc.IntegrationEvents.GetWorksheetAsync <HSOrderWorksheet>(OrderDirection.Incoming, rma.SourceOrderID); CosmosListPage <RMA> allRMAsOnThisOrder = await ListRMAsByOrderID(worksheet.Order.ID, decodedToken.CommerceRole, me, true); CalculateAndUpdateLineTotalRefund(rmaLineItemsToUpdate, worksheet, allRMAsOnThisOrder, rma.SupplierID); // UPDATE RMA LINE ITEM STATUSES SetRMALineItemStatusesToComplete(rmaLineItemsToUpdate); // UPDATE RMA STATUS UpdateRMAStatus(rma); await HandleRefund(rma, allRMAsOnThisOrder, worksheet, decodedToken); MarkRMALineItemsAsRefunded(rmaLineItemsToUpdate); decimal totalRefundedForThisTransaction = rma.TotalCredited - initialAmountRefunded; RMALog log = new RMALog() { Status = rma.Status, Date = DateTime.Now, AmountRefunded = totalRefundedForThisTransaction, FromUserID = me.ID }; rma.Logs.Insert(0, log); List <LineItemStatusChanges> lineItemStatusChangesList = BuildLineItemStatusChanges(rmaLineItemsToUpdate, worksheet, rma.Type, false); // SAVE RMA ItemResponse <RMA> updatedRMA = await _rmaRepo.ReplaceItemAsync(rma.id, rma); RMAWithLineItemStatusByQuantity rmaWithStatusByQuantityChanges = new RMAWithLineItemStatusByQuantity() { SupplierOrderID = $"{rma.SourceOrderID}-{rma.SupplierID}", RMA = updatedRMA.Resource, LineItemStatusChangesList = lineItemStatusChangesList }; return(rmaWithStatusByQuantityChanges); }
private static async Task ValidateShipping(HSOrderWorksheet orderWorksheet) { if (orderWorksheet.ShipEstimateResponse.HttpStatusCode != 200) { throw new Exception(orderWorksheet.ShipEstimateResponse.UnhandledErrorBody); } if (orderWorksheet.ShipEstimateResponse.ShipEstimates.Any(s => s.SelectedShipMethodID == ShippingConstants.NoRatesID)) { throw new Exception("No shipping rates could be determined - fallback shipping rate of $20 3-day was used"); } await Task.CompletedTask; }
public async Task SendOrderSubmitEmail(HSOrderWorksheet orderWorksheet) { var supplierEmailList = await GetSupplierEmails(orderWorksheet); var firstName = orderWorksheet.Order.FromUser.FirstName; var lastName = orderWorksheet.Order.FromUser.LastName; if (orderWorksheet.Order.xp.OrderType == OrderType.Standard) { var orderData = SendgridMappers.GetOrderTemplateData(orderWorksheet.Order, orderWorksheet.LineItems); var sellerTemplateData = new EmailTemplate <OrderTemplateData>() { Data = orderData, Message = OrderSubmitEmailConstants.GetOrderSubmitText(orderWorksheet.Order.ID, firstName, lastName, VerifiedUserType.admin) }; var buyerTemplateData = new EmailTemplate <OrderTemplateData>() { Data = orderData, Message = OrderSubmitEmailConstants.GetOrderSubmitText(orderWorksheet.Order.ID, firstName, lastName, VerifiedUserType.buyer) }; var sellerEmailList = await GetSellerEmails(); // send emails await SendSingleTemplateEmailMultipleRcpts(_settings?.SendgridSettings?.FromEmail, sellerEmailList, _settings?.SendgridSettings?.OrderSubmitTemplateID, sellerTemplateData); await SendSingleTemplateEmail(_settings?.SendgridSettings?.FromEmail, orderWorksheet.Order.FromUser.Email, _settings?.SendgridSettings?.OrderSubmitTemplateID, buyerTemplateData); await SendSupplierOrderSubmitEmails(orderWorksheet); } else if (orderWorksheet.Order.xp.OrderType == OrderType.Quote) { var orderData = SendgridMappers.GetQuoteOrderTemplateData(orderWorksheet.Order, orderWorksheet.LineItems); var buyerTemplateData = new EmailTemplate <QuoteOrderTemplateData>() { Data = orderData, Message = OrderSubmitEmailConstants.GetQuoteOrderSubmitText(VerifiedUserType.buyer) }; var supplierTemplateData = new EmailTemplate <QuoteOrderTemplateData>() { Data = orderData, Message = OrderSubmitEmailConstants.GetQuoteOrderSubmitText(VerifiedUserType.supplier) }; // send emails await SendSingleTemplateEmailMultipleRcpts(_settings?.SendgridSettings?.FromEmail, supplierEmailList, _settings?.SendgridSettings?.QuoteOrderSubmitTemplateID, supplierTemplateData); await SendSingleTemplateEmail(_settings?.SendgridSettings?.FromEmail, orderWorksheet.Order.FromUser.Email, _settings?.SendgridSettings?.QuoteOrderSubmitTemplateID, buyerTemplateData); } }
public async Task <RMAWithLineItemStatusByQuantity> ProcessRMA(RMA rma, DecodedToken decodedToken) { // Get the RMA from the last time it was saved. var me = await _oc.Me.GetAsync(accessToken : decodedToken.AccessToken); RMA currentRMA = await GetRMA(rma.RMANumber, decodedToken); if (currentRMA.SupplierID != me.Supplier.ID) { throw new Exception($"You do not have permission to process this RMA."); } // Should the Status and IsResolved proprerties of an RMALineItem change? rma.LineItems = UpdateRMALineItemStatusesAndCheckIfResolved(rma.LineItems); // Should status of RMA change? rma = UpdateRMAStatus(rma); // If the status on the new RMA differs from the old RMA, create an RMALog if (rma.Status != currentRMA.Status) { RMALog log = new RMALog() { Status = rma.Status, Date = DateTime.Now, FromUserID = me.ID }; rma.Logs.Insert(0, log); } HSOrderWorksheet worksheet = await _oc.IntegrationEvents.GetWorksheetAsync <HSOrderWorksheet>(OrderDirection.Incoming, rma.SourceOrderID); IEnumerable <RMALineItem> deniedLineItems = rma.LineItems .Where(li => !li.IsRefunded && li.Status == RMALineItemStatus.Denied) .Where(li => currentRMA.LineItems.FirstOrDefault(currentLi => currentLi.ID == li.ID).Status != RMALineItemStatus.Denied).ToList(); List <LineItemStatusChanges> lineItemStatusChangesList = BuildLineItemStatusChanges(deniedLineItems, worksheet, rma.Type, true); ItemResponse <RMA> updatedRMA = await _rmaRepo.ReplaceItemAsync(currentRMA.id, rma); await HandlePendingApprovalEmails(currentRMA, rma.LineItems, worksheet); RMAWithLineItemStatusByQuantity rmaWithStatusByQuantityChanges = new RMAWithLineItemStatusByQuantity() { SupplierOrderID = $"{rma.SourceOrderID}-{rma.SupplierID}", RMA = updatedRMA.Resource, LineItemStatusChangesList = lineItemStatusChangesList }; return(rmaWithStatusByQuantityChanges); }
private async Task ValidateOrderAsync(HSOrderWorksheet worksheet, OrderCloudIntegrationsCreditCardPayment payment, string userToken) { Require.That( !worksheet.Order.IsSubmitted, new ErrorCode("OrderSubmit.AlreadySubmitted", 400, "Order has already been submitted") ); var shipMethodsWithoutSelections = worksheet.ShipEstimateResponse.ShipEstimates.Where(estimate => estimate.SelectedShipMethodID == null); Require.That( shipMethodsWithoutSelections.Count() == 0, new ErrorCode("OrderSubmit.MissingShippingSelections", 400, "All shipments on an order must have a selection"), shipMethodsWithoutSelections ); var standardLines = worksheet.LineItems.Where(li => li.Product.xp.ProductType != ProductType.PurchaseOrder); Require.That( !standardLines.Any() || payment != null, new ErrorCode("OrderSubmit.MissingPayment", 400, "Order contains standard line items and must include credit card payment details"), standardLines ); var lineItemsInactive = await GetInactiveLineItems(worksheet, userToken); Require.That( !lineItemsInactive.Any(), new ErrorCode("OrderSubmit.InvalidProducts", 400, "Order contains line items for products that are inactive"), lineItemsInactive ); try { // ordercloud validates the same stuff that would be checked on order submit await _oc.Orders.ValidateAsync(OrderDirection.Incoming, worksheet.Order.ID); } catch (OrderCloudException ex) { // this error is expected and will be resolved before oc order submit call happens // in a non-seb flow this could be removed because we'd auth the payment which would mark it as accepted // before it even hits the submit endpoint var errors = ex.Errors.Where(ex => ex.ErrorCode != "Order.CannotSubmitWithUnaccceptedPayments"); if (errors.Any()) { throw new OrderCloudIntegrationException(new ApiError { ErrorCode = "OrderSubmit.OrderCloudValidationError", Message = "Failed ordercloud validation, see Data for details", Data = errors }); } } }
private async Task SendSupplierOrderSubmitEmails(HSOrderWorksheet orderWorksheet) { ListPage <HSSupplier> suppliers = null; if (orderWorksheet.Order.xp.SupplierIDs != null) { var filterString = String.Join("|", orderWorksheet.Order.xp.SupplierIDs); suppliers = await _oc.Suppliers.ListAsync <HSSupplier>(filters : $"ID={filterString}"); } foreach (var supplier in suppliers.Items) { if (supplier?.xp?.NotificationRcpts?.Count() > 0) { // get orderworksheet for supplier order and fill in some information from buyer order worksheet var supplierOrderWorksheet = await BuildSupplierOrderWorksheet(orderWorksheet, supplier.ID); var supplierTemplateData = new EmailTemplate <OrderTemplateData>() { Data = SendgridMappers.GetOrderTemplateData(supplierOrderWorksheet.Order, supplierOrderWorksheet.LineItems), Message = OrderSubmitEmailConstants.GetOrderSubmitText(orderWorksheet.Order.ID, supplierOrderWorksheet.Order.FromUser.FirstName, supplierOrderWorksheet.Order.FromUser.LastName, VerifiedUserType.supplier) }; // SEB-Specific Data ((OrderTemplateData)supplierTemplateData.Data).BillTo = new Address() { CompanyName = "SEB Vendor Portal - BES", Street1 = "8646 Eagle Creek Circle", Street2 = "Suite 107", City = "Savage", State = "MN", Zip = "55378", Phone = "877-771-9123", xp = { Email = "*****@*****.**" } }; var supplierTos = new List <EmailAddress>(); foreach (var rcpt in supplier.xp.NotificationRcpts) { supplierTos.Add(new EmailAddress(rcpt)); } ; await SendSingleTemplateEmailMultipleRcpts(_settings?.SendgridSettings?.FromEmail, supplierTos, _settings?.SendgridSettings?.OrderSubmitTemplateID, supplierTemplateData); } } }
private async Task <ZohoSalesOrder> CreateSalesOrder(HSOrderWorksheet orderWorksheet, IEnumerable <ZohoLineItem> items, ZohoContact contact) { // promotions aren't part of the order worksheet, so we have to get them from OC var promotions = await _oc.Orders.ListPromotionsAsync(OrderDirection.Incoming, orderWorksheet.Order.ID); var zOrder = await _zoho.SalesOrders.ListAsync(new ZohoFilter() { Key = "reference_number", Value = orderWorksheet.Order.ID }); if (zOrder.Items.Any()) { return(await _zoho.SalesOrders.SaveAsync(ZohoSalesOrderMapper.Map(zOrder.Items.FirstOrDefault(), orderWorksheet, items.ToList(), contact, promotions.Items))); } return(await _zoho.SalesOrders.CreateAsync(ZohoSalesOrderMapper.Map(orderWorksheet, items.ToList(), contact, promotions.Items))); }
private decimal GetPurchaseOrderShippingCost(HSOrderWorksheet orderWorksheet, string supplierID) { var supplierShipEstimate = orderWorksheet?.ShipEstimateResponse?.ShipEstimates?.FirstOrDefault(estimate => estimate?.xp?.SupplierID == supplierID); if (supplierShipEstimate == null) { return(0M); } if (orderWorksheet?.OrderCalculateResponse?.xp?.TaxResponse?.lines?.FirstOrDefault(line => line?.lineNumber == supplierShipEstimate?.SelectedShipMethodID) != null) { var shippingCost = orderWorksheet?.OrderCalculateResponse?.xp?.TaxResponse?.lines?.FirstOrDefault(line => line?.lineNumber == supplierShipEstimate?.SelectedShipMethodID)?.lineAmount; return(shippingCost != null?Math.Round((decimal)shippingCost, 2) : 0M); } return(0M); }
private async Task <List <HSLineItem> > GetInactiveLineItems(HSOrderWorksheet worksheet, string userToken) { List <HSLineItem> inactiveLineItems = new List <HSLineItem>(); foreach (HSLineItem lineItem in worksheet.LineItems) { try { await _oc.Me.GetProductAsync(lineItem.ProductID, accessToken : userToken); } catch (OrderCloudException ex) when(ex.HttpStatus == HttpStatusCode.NotFound) { inactiveLineItems.Add(lineItem); } } return(inactiveLineItems); }
private decimal GetPurchaseOrderShippingCost(HSOrderWorksheet orderWorksheet, string supplierID) { var supplierShipEstimate = orderWorksheet?.ShipEstimateResponse?.ShipEstimates?.FirstOrDefault(estimate => estimate?.xp?.SupplierID == supplierID); if (supplierShipEstimate == null) { return(0M); } if (orderWorksheet?.OrderCalculateResponse?.xp?.TaxCalculation?.OrderLevelTaxes?.FirstOrDefault(line => line?.ShipEstimateID == supplierShipEstimate?.ID) != null) { var shippingCost = orderWorksheet?.OrderCalculateResponse?.xp?.TaxCalculation?.OrderLevelTaxes?.FirstOrDefault(line => line?.ShipEstimateID == supplierShipEstimate?.ID)?.Tax; return(shippingCost != null?Math.Round((decimal)shippingCost, 2) : 0M); } return(0M); }
private async Task <ShipEstimateResponse> GetRatesAsync(HSOrderWorksheet worksheet, CheckoutIntegrationConfiguration config = null) { if (config != null && config.ExcludePOProductsFromShipping) { worksheet.LineItems = worksheet.LineItems.Where(li => li.Product.xp.ProductType != ProductType.PurchaseOrder).ToList(); } var groupedLineItems = worksheet.LineItems.GroupBy(li => new AddressPair { ShipFrom = li.ShipFromAddress, ShipTo = li.ShippingAddress }).ToList(); var shipResponse = (await _shippingService.GetRates(groupedLineItems, _profiles)).Reserialize <HSShipEstimateResponse>(); // include all accounts at this stage so we can save on order worksheet and analyze // Certain suppliers use certain shipping accounts. This filters available rates based on those accounts. for (var i = 0; i < groupedLineItems.Count; i++) { var supplierID = groupedLineItems[i].First().SupplierID; var profile = _profiles.FirstOrDefault(supplierID); var methods = FilterMethodsBySupplierConfig(shipResponse.ShipEstimates[i].ShipMethods.Where(s => profile.CarrierAccountIDs.Contains(s.xp.CarrierAccountID)).ToList(), profile); var cheapestMethods = WhereRateIsCheapestOfItsKind(methods); shipResponse.ShipEstimates[i].ShipMethods = cheapestMethods.Select(s => { // there is logic here to support not marking up shipping over list rate. But USPS is always list rate // so adding an override to the suppliers that use USPS var carrier = _profiles.ShippingProfiles.First(p => p.CarrierAccountIDs.Contains(s.xp?.CarrierAccountID)); s.Cost = carrier.MarkupOverride ? s.xp.OriginalCost * carrier.Markup : Math.Min((s.xp.OriginalCost * carrier.Markup), s.xp.ListRate); return(s); }).ToList(); } var buyerCurrency = worksheet.Order.xp.Currency ?? CurrencySymbol.USD; if (buyerCurrency != CurrencySymbol.USD) // shipper currency is USD { shipResponse.ShipEstimates = await ConvertShippingRatesCurrency(shipResponse.ShipEstimates, CurrencySymbol.USD, buyerCurrency); } shipResponse.ShipEstimates = CheckForEmptyRates(shipResponse.ShipEstimates, _settings.EasyPostSettings.NoRatesFallbackCost, _settings.EasyPostSettings.NoRatesFallbackTransitDays); shipResponse.ShipEstimates = UpdateFreeShippingRates(shipResponse.ShipEstimates, _settings.EasyPostSettings.FreeShippingTransitDays); shipResponse.ShipEstimates = await ApplyFreeShipping(worksheet, shipResponse.ShipEstimates); shipResponse.ShipEstimates = FilterSlowerRatesWithHighCost(shipResponse.ShipEstimates); shipResponse.ShipEstimates = ApplyFlatRateShipping(worksheet, shipResponse.ShipEstimates); return(shipResponse); }
public static ZohoSalesOrder Map(HSOrderWorksheet worksheet, List <ZohoLineItem> items, ZohoContact contact, IList <OrderPromotion> promotions) { var o = new ZohoSalesOrder() { reference_number = worksheet.Order.ID, salesorder_number = worksheet.Order.ID, date = worksheet.Order.DateSubmitted?.ToString("yyyy-MM-dd"), is_discount_before_tax = true, discount = decimal.ToDouble(promotions.Sum(p => p.Amount)), discount_type = "entity_level", line_items = worksheet.LineItems.Select(item => new ZohoLineItem { item_id = items.First(i => i.sku == item.SKU()).item_id, quantity = item.Quantity, rate = Math.Round((double)(item.UnitPrice ?? 0), 2), avatax_tax_code = item.Product.xp.Tax.Code //discount = decimal.ToDouble(promotions.Where(p => p.LineItemLevel == true && p.LineItemID == line_item.ID).Sum(p => p.Amount)), }).ToList(), tax_total = decimal.ToDouble(worksheet.Order.TaxCost), customer_name = contact.contact_name, sub_total = decimal.ToDouble(worksheet.Order.Subtotal), total = decimal.ToDouble(worksheet.Order.Total), customer_id = contact.contact_id, currency_code = contact.currency_code, currency_symbol = contact.currency_symbol, notes = promotions.Any() ? $"Promotions applied: {promotions.DistinctBy(p => p.Code).Select(p => p.Code).JoinString(" - ", p => p)}" : null //shipping_charge = decimal.ToDouble(order.ShippingCost), //TODO: Please mention any Shipping/miscellaneous charges as additional line items. }; // adding shipping as a line item foreach (var shipment in worksheet.ShipEstimateResponse.ShipEstimates) { var method = shipment.ShipMethods.FirstOrDefault(s => s.ID == shipment.SelectedShipMethodID); o.line_items.Add(new ZohoLineItem { item_id = items.First(i => i.sku == method?.ShippingSku()).item_id, quantity = 1, rate = Math.Round((double)(method?.Cost ?? 0), 2), avatax_tax_code = "FR" }); } return(o); }
public async Task <ZohoSalesOrder> CreateSalesOrder(HSOrderWorksheet orderWorksheet) { await _zoho.AuthenticateAsync(); // Step 1: Create contact (customer) in Zoho var contact = await CreateOrUpdateContact(orderWorksheet.Order); // Step 2: Create or update Items from LineItems/Products on Order var items = await CreateOrUpdateSalesLineItems(orderWorksheet.LineItems); // Step 3: Create item for shipments items.AddRange(await ApplyShipping(orderWorksheet)); // Step 4: create sales order with all objects from above var salesOrder = await CreateSalesOrder(orderWorksheet, items, contact); return(salesOrder); }
public decimal GetPurchaseOrderShipping(HSOrderWorksheet worksheet) { decimal POShippingCosts = 0; var POlineItemIDs = worksheet.GetLineItemsByProductType(ProductType.PurchaseOrder)?.Select(li => li.ID); if (POlineItemIDs?.Count() > 0) { foreach (var estimate in worksheet?.ShipEstimateResponse?.ShipEstimates) { var relatedLineItemIDs = estimate?.ShipEstimateItems?.Select(item => item?.LineItemID); var selectedShipMethod = estimate?.ShipMethods?.FirstOrDefault(method => method?.ID == estimate?.SelectedShipMethodID); if (relatedLineItemIDs != null && relatedLineItemIDs.Any(id => POlineItemIDs.Contains(id))) { POShippingCosts = POShippingCosts + (selectedShipMethod?.Cost ?? 0); } } } return(POShippingCosts); }
/// <summary> /// Promotion discounts are distributed evenly accross line items. Proportional distribution is required for accurate partial returns. /// To solve this, set lineItem.xp.LineTotalWithProportionalDiscounts on all LineItems. /// </summary> public async Task SetLineItemProportionalDiscount(HSOrderWorksheet order, List <OrderPromotion> promotions) { List <string> allLineItemIDsWithDiscounts = promotions .Where(promo => promo.LineItemLevel) .Select(promo => promo.LineItemID) .Distinct().ToList(); // X001, X002... List <string> allLineItemLevelPromoCodes = promotions .Where(promo => promo.LineItemLevel) .Select(promo => promo.Code) .Distinct().ToList(); // 25OFF, SAVE15... Dictionary <string, decimal> totalWeightedLineItemDiscounts = new Dictionary <string, decimal>(); foreach (string lineItemID in allLineItemIDsWithDiscounts) { // Initialize discounts at 0.00 totalWeightedLineItemDiscounts.Add(lineItemID, 0M); } // Calculate discounts one promo code at a time foreach (string promoCode in allLineItemLevelPromoCodes) { CalculateDiscountByPromoCode(promoCode, order, promotions, totalWeightedLineItemDiscounts); } foreach (string lineItemID in allLineItemIDsWithDiscounts) { var lineItemToUpdate = order.LineItems.FirstOrDefault(lineItem => lineItem.ID == lineItemID); lineItemToUpdate.LineTotal = lineItemToUpdate.LineSubtotal - totalWeightedLineItemDiscounts[lineItemID]; } await Throttler.RunAsync(order.LineItems, 100, 8, async li => { var patch = new HSPartialLineItem() { xp = new LineItemXp() { LineTotalWithProportionalDiscounts = li.LineTotal } }; await _oc.LineItems.PatchAsync(OrderDirection.All, order.Order.ID, li.ID, patch); }); }
private decimal GetPurchaseOrderPromotionDiscount(HSOrderWorksheet orderWorksheet, string supplierID) { var supplierLineItems = orderWorksheet?.LineItems?.Where(li => li?.SupplierID == supplierID); // Line-level discounts var lineItemDiscounts = supplierLineItems?.Sum(line => line?.PromotionDiscount); var supplierLineItemIDs = supplierLineItems?.Select(li => li?.ID); if (supplierLineItemIDs != null) { var orderCalculateResponseLines = orderWorksheet?.OrderCalculateResponse?.xp?.TaxResponse?.lines?.Where(avalaraLine => supplierLineItemIDs.Contains(avalaraLine?.lineNumber)); // Order-level discounts, as it applies to the line item and adjusted for line item pricing var orderCalculateLineDiscounts = orderCalculateResponseLines?.Sum(line => line?.discountAmount); var totalDiscount = lineItemDiscounts += orderCalculateLineDiscounts; return(totalDiscount != null ? (decimal)totalDiscount : 0M); } return(0M); }
private async Task <string> IncrementOrderAsync(HSOrderWorksheet worksheet) { if (worksheet.Order.xp.IsResubmitting == true) { // orders marked with IsResubmitting true are orders that were on hold and then declined // so buyer needs to resubmit but we don't want to increment order again return(worksheet.Order.ID); } if (worksheet.Order.ID.StartsWith(_settings.OrderCloudSettings.IncrementorPrefix)) { // order has already been incremented, no need to increment again return(worksheet.Order.ID); } var order = await _oc.Orders.PatchAsync(OrderDirection.Incoming, worksheet.Order.ID, new PartialOrder { ID = _settings.OrderCloudSettings.IncrementorPrefix + "{orderIncrementor}" }); return(order.ID); }
private decimal GetPurchaseOrderPromotionDiscount(HSOrderWorksheet orderWorksheet, IEnumerable <OrderPromotion> promosOnOrder, string supplierID) { var supplierLineItems = orderWorksheet?.LineItems?.Where(line => line?.SupplierID == supplierID); if (supplierLineItems == null || supplierLineItems.Count() == 0) { return(0M); } var lineItemDiscount = supplierLineItems.Sum(line => line.PromotionDiscount); var totalOrderLevelDiscount = promosOnOrder .Where(promo => promo.LineItemID == null && !promo.LineItemLevel) .Select(promo => promo.Amount).Sum(); var fractionOfOrderSubtotal = supplierLineItems.Select(l => l.LineSubtotal).Sum() / orderWorksheet.Order.Subtotal; var proportionalOrderDiscount = totalOrderLevelDiscount * fractionOfOrderSubtotal; return(lineItemDiscount + proportionalOrderDiscount); }