Example #1
0
        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);
        }
Example #3
0
 public decimal GetPurchaseOrderTotal(HSOrderWorksheet worksheet)
 {
     return(worksheet.LineItems
            .Where(li => li.Product.xp.ProductType == ProductType.PurchaseOrder)
            .Select(li => li.LineTotal)
            .Sum());
 }
Example #4
0
        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);
        }
Example #5
0
        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];
            }
        }
Example #7
0
        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
                }
            });
        }
Example #8
0
        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);
        }
Example #9
0
        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);
        }
Example #10
0
        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);
        }
Example #11
0
        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);
 }
Example #13
0
        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);
        }
Example #14
0
        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;
        }
Example #15
0
        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);
            }
        }
Example #16
0
        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);
        }
Example #17
0
        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
                    });
                }
            }
        }
Example #18
0
        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);
                }
            }
        }
Example #19
0
        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)));
        }
Example #20
0
        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);
        }
Example #21
0
        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);
        }
Example #24
0
        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);
        }
Example #25
0
        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);
        }
Example #26
0
        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);
            });
        }
Example #28
0
        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);
        }
Example #29
0
        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);
        }