public void LogManualOrderChanges(
            [CosmosDBTrigger(
                 databaseName: "sourcing-engine",
                 collectionName: "manual-orders",
                 LeaseCollectionName = "sourcing-leases",
                 ConnectionStringSetting = "AzureCosmosDBConnectionString",
                 CreateLeaseCollectionIfNotExists = true), SwaggerIgnore] IReadOnlyList <Document> documents,
            [CosmosDB(ConnectionStringSetting = "AzureCosmosDBConnectionString"), SwaggerIgnore] DocumentClient documentClient,
            ILogger log)
        {
            log.LogInformation(@"Documents: {0}", documents);

            foreach (var document in documents)
            {
                ManualOrder mOrder = (dynamic)document;
                log.LogInformation(@"Order ID: {0} Manual Order: {1}", mOrder.atgOrderId, mOrder);

                // Track changes to specific properties
                log.LogInformation($"Order ID: {mOrder.atgOrderId}");
                log.LogInformation($"Order ID: {mOrder.atgOrderId} Order complete: {mOrder.orderComplete}");
                log.LogInformation($"Order ID: {mOrder.atgOrderId} Time completed: {mOrder.timeCompleted}");
                log.LogInformation($"Order ID: {mOrder.atgOrderId} Claimed: {mOrder.claimed}");
                log.LogInformation($"Order ID: {mOrder.atgOrderId} Time claimed: {mOrder.timeClaimed}");
                log.LogInformation($"Order ID: {mOrder.atgOrderId} Notes: {mOrder.notes}");
            }
        }
        /// <summary>
        ///     Updates an existing ManualOrder based on the ATG Order values, but does not overwrite the existing claimed, completed or notes values.
        /// </summary>
        /// <param name="atgOrderRes">ATG Order with sourcing fields.</param>
        /// <param name="manualOrderDoc">CosmosDB document that can be parsed to get the existing manual order.</param>
        /// <returns>The updated ManualOrder object.</returns>
        public ManualOrder UpdateManualOrder(AtgOrderRes atgOrderRes, Document manualOrderDoc)
        {
            try
            {
                ManualOrder existingManualOrder = (dynamic)manualOrderDoc;

                var updatedManualOrder = new ManualOrder(atgOrderRes)
                {
                    // Do not overwrite values that can be set by an ATG rep
                    claimed       = existingManualOrder.claimed,
                    timeClaimed   = existingManualOrder.timeClaimed,
                    orderComplete = existingManualOrder.orderComplete,
                    timeCompleted = existingManualOrder.timeCompleted,
                    notes         = existingManualOrder.notes
                };

                SetLogons(updatedManualOrder);

                return(updatedManualOrder);
            }
            catch (Exception ex)
            {
                var title = $"Error in UpdateManualOrder. Order ID: {atgOrderRes.atgOrderId}.";
                _logger.LogError(@"{Title} {Ex}", title, ex);
#if RELEASE
                var teamsMessage = new TeamsMessage(title, $"Error message: {ex.Message}. Stacktrace: {ex.StackTrace}", "red", SourcingEngineFunctions.errorLogsUrl);
                teamsMessage.LogToTeams(teamsMessage);
#endif
                throw;
            }
        }
        /// <summary>
        ///     Sets the TrilogieStatus, TrilogieErrorMessage and TrilogieOrderID fieldson the ManualOrder in CosmosDB based on the request values.
        /// </summary>
        /// <param name="atgOrderId">ID of the original ATG order.</param>
        /// <param name="document">Cosmos DocumentDB client.</param>
        /// <param name="trilogieReq">Request containing the results of submitting the order to Trilogie.</param>
        public static async Task UpdateTrilogieStatusOnManualOrder(string atgOrderId, DocumentClient document, TrilogieRequest trilogieReq)
        {
            try
            {
                var manualOrderDoc = await GetOrder <ManualOrder>(atgOrderId, document);

                ManualOrder manualOrder = (dynamic)manualOrderDoc;

                if (manualOrder != null)
                {
                    await SetTrilogieFieldsOnManualOrder(trilogieReq, manualOrder);
                }
                else // create a new manual order
                {
                    var orderController = new OrderController(new LocationController());

                    var orderDoc = await GetOrder <AtgOrderRes>(atgOrderId, document);

                    AtgOrderRes atgOrder = (dynamic)orderDoc;

                    manualOrder = orderController.CreateManualOrder(atgOrder, trilogieReq);
                }

                var containerName = Environment.GetEnvironmentVariable("MANUAL_ORDERS_CONTAINER_NAME");
                var collectionUri = UriFactory.CreateDocumentCollectionUri("sourcing-engine", containerName);

                _ = document.UpsertDocumentAsync(collectionUri, manualOrder);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Exception in UpdateTrilogieStatusOnManualOrder");
                throw;
            }
        }
        public static async Task RunSourcingOnStaleOrders(
            [HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req,
            [CosmosDB(ConnectionStringSetting = "AzureCosmosDBConnectionString"), SwaggerIgnore] DocumentClient documentClient,
            ILogger log)
        {
            try
            {
                var manualOrdersCollectionUri = UriFactory.CreateDocumentCollectionUri("sourcing-engine", "manual-orders");

                var query = new SqlQuerySpec
                {
                    QueryText = "SELECT * FROM c WHERE c.orderComplete = false AND c.claimed = false"
                };

                var incompleteOrderDocs = documentClient.CreateDocumentQuery <Document>(manualOrdersCollectionUri, query, option).AsEnumerable();
                log.LogInformation($"Incomplete Order Count: {incompleteOrderDocs.Count()}");

                if (incompleteOrderDocs.Count() == 0)
                {
                    return;
                }

                foreach (var orderDoc in incompleteOrderDocs)
                {
                    try
                    {
                        ManualOrder manualOrder = (dynamic)orderDoc;
                        log.LogInformation($"Order ID: {manualOrder.atgOrderId}");

                        var atgOrderDoc = await OrderController.GetOrder <AtgOrderRes>(manualOrder.atgOrderId, documentClient);

                        AtgOrderRes atgOrderRes = (dynamic)atgOrderDoc;
                        log.LogInformation(@"ATG Order: {0}", atgOrderRes);

                        var sourcingController = InitializeSourcingController(log);

                        await sourcingController.StartSourcing(documentClient, atgOrderRes);
                    }
                    catch (Exception ex)
                    {
                        var title        = "Error in RunSourcingOnStaleOrders loop";
                        var teamsMessage = new TeamsMessage(title, $"Error message: {ex.Message}. Stacktrace: {ex.StackTrace}", "yellow", errorLogsUrl);
                        teamsMessage.LogToTeams(teamsMessage);
                        log.LogError(@"{0}: {1}", title, ex);
                    }
                }
            }
            catch (Exception ex)
            {
                var title        = "Error in RunSourcingOnStaleOrders";
                var teamsMessage = new TeamsMessage(title, $"Error message: {ex.Message}. Stacktrace: {ex.StackTrace}", "red", errorLogsUrl);
                teamsMessage.LogToTeams(teamsMessage);
                log.LogError(@"{0}: {1}", title, ex);
            }
        }
        /// <summary>
        ///     Sets the shipFromLogon value for each line item based on the shipFrom value.
        /// </summary>
        /// <param name="manualOrder">Manual Order object containing shipFrom values for each line item.</param>
        public void SetLogons(ManualOrder manualOrder)
        {
            manualOrder.sellLogon = locationController.GetBranchLogonID(manualOrder.sellWhse);

            manualOrder.sourcing.ForEach(source =>
            {
                if (!string.IsNullOrEmpty(source.shipFrom))
                {
                    source.shipFromLogon = locationController.GetBranchLogonID(source.shipFrom);
                }
            });
        }
        /// <summary>
        ///     Creates a new manual order object for orders that cannot be auto-sourced and require manual intervention by a rep.
        /// </summary>
        /// <param name="atgOrderRes">ATG Order with sourcing fields.</param>
        /// <returns>The manual order object that was created from the ATG order.</returns>
        public ManualOrder CreateManualOrder(AtgOrderRes atgOrderRes, TrilogieRequest trilogieReq = null)
        {
            try
            {
                var manualOrder = new ManualOrder(atgOrderRes, trilogieReq);

                SetLogons(manualOrder);

                return(manualOrder);
            }
            catch (Exception ex)
            {
                var title = $"Error in CreateOrUpdateManualOrder. Order ID: {atgOrderRes.atgOrderId}.";
                _logger.LogError(@"{Title} {Ex}", title, ex);
#if RELEASE
                var teamsMessage = new TeamsMessage(title, $"Error message: {ex.Message}. Stacktrace: {ex.StackTrace}", "red", SourcingEngineFunctions.errorLogsUrl);
                teamsMessage.LogToTeams(teamsMessage);
#endif
                throw;
            }
        }
        public static async Task ReSourceBackorderedItems(
            [TimerTrigger("0 */15 * * * *")] TimerInfo timer, // every 15 mins
            [CosmosDB(ConnectionStringSetting = "AzureCosmosDBConnectionString"), SwaggerIgnore] DocumentClient documentClient,
            ILogger log)
        {
            try
            {
                var currentHour = OrderController.GetCurrentEasternHour();
                log.LogInformation($"Current Hour: {currentHour}");

                // Only run within 6am - 7pm EST
                if (currentHour < 6 || currentHour >= 19)
                {
                    return;
                }

                // Get open orders with backordered item(s)
                var query = new SqlQuerySpec
                {
                    QueryText = @"
                        SELECT VALUE c FROM c 
                        JOIN s IN c.sourcing 
                        JOIN i IN s.items 
                        WHERE c.orderComplete = false 
                            AND c.claimed = false 
                            AND (i.sourcingMessage = 'Backordered.' OR CONTAINS(i.sourcingMessage, 'does not have the required quantity'))"
                };

                var manualOrdersCollectionUri = UriFactory.CreateDocumentCollectionUri("sourcing-engine", "manual-orders");
                var manualOrderDocs           = documentClient.CreateDocumentQuery <Document>(manualOrdersCollectionUri, query, option).AsEnumerable();

                // Get the matching ATG Order from query results and run sourcing
                foreach (var manualOrderDoc in manualOrderDocs)
                {
                    try
                    {
                        ManualOrder manualOrder = (dynamic)manualOrderDoc;
                        log.LogInformation($"Order ID: {manualOrder.atgOrderId}");

                        var atgOrderDoc = await OrderController.GetOrder <AtgOrderRes>(manualOrder.atgOrderId, documentClient);

                        AtgOrderRes atgOrder = (dynamic)atgOrderDoc;

                        await InitializeSourcingController(log).StartSourcing(documentClient, atgOrder);
                    }
                    catch (Exception ex)
                    {
                        var title        = "Error in ReSourceBackorderedItems foreach loop.";
                        var teamsMessage = new TeamsMessage(title, $"Error message: {ex.Message}. Stacktrace: {ex.StackTrace}", "yellow", errorLogsUrl);
                        teamsMessage.LogToTeams(teamsMessage);
                        log.LogError(ex, title);
                    }
                }
            }
            catch (Exception ex)
            {
                var title        = "Error in ReSourceBackorderedItems.";
                var teamsMessage = new TeamsMessage(title, $"Error message: {ex.Message}. Stacktrace: {ex.StackTrace}", "red", errorLogsUrl);
                teamsMessage.LogToTeams(teamsMessage);
                log.LogError(ex, title);
            }
        }
        /// <summary>
        ///     Sets the Trilogie fields on the Manual Order, incld. error message, status and order ID.
        /// </summary>
        /// <param name="trilogieReq">Request containing the results of submitting the order to Trilogie.</param>
        /// <param name="manualOrder">Manual Order object that matches the ATG Order from the request.</param>
        public static async Task SetTrilogieFieldsOnManualOrder(TrilogieRequest trilogieReq, ManualOrder manualOrder)
        {
            manualOrder.trilogieErrorMessage = trilogieReq.TrilogieErrorMessage;
            manualOrder.trilogieOrderId      = trilogieReq.TrilogieOrderId;
            manualOrder.trilogieStatus       = trilogieReq.TrilogieStatus.ToString();
#if DEBUG
            // Sets to false if order failed in Trilogie
            manualOrder.orderComplete = trilogieReq.TrilogieStatus == TrilogieStatus.Pass;

            if (manualOrder.orderComplete)
            {
                manualOrder.timeCompleted = GetCurrentEasternTime();
            }
            else
            {
                manualOrder.timeCompleted = null;
            }
#endif
        }