/// <summary>
 /// Returns whether or not another <see cref="DateTimeSpan"/> in any way overlaps this
 /// <see cref="DateTimeSpan"/>.
 /// </summary>
 /// <param name="span">The DateTimeSpan to test for any overlaps.</param>
 public bool Overlaps(DateTimeSpan span)
 {
     return(span.Equals(this) ||
            (span.Start < End && span.End == End) ||
            (span.End > Start && span.Start == Start) ||
            (span.Contains(Start) && Contains(span.End)) ||
            (span.Contains(End) && Contains(span.Start)) ||
            (span.Contains(Start) && span.Contains(End)) ||
            (Contains(span.Start) && Contains(span.End)));
 }
Beispiel #2
0
        /// <summary>
        /// Tries to find and return all items of a product type during a timespan that aren't reserved.
        /// </summary>
        /// <param name="productId">The product type whose items to return.</param>
        /// <param name="span">The time period to test for unreserved items.</param>
        /// <returns>A subset of all items of the given product type, or null if the product type has no items.</returns>
        private List <ProductItem> Core_GetUnreservedItems(string productId, DateTimeSpan span)
        {
            List <ProductItem> items = Core_getProductItems(new string[] { productId })[productId].ToList();

            if (!items.Any())
            {
                return(null);
            }

            var condition = new MySqlConditionBuilder();

            condition.NewGroup();
            foreach (var item in items)
            {
                condition.Or()
                .Column("product_item")
                .Equals(item.Id, item.GetIndex("PRIMARY").Columns[0].Type);
            }
            condition.EndGroup();
            condition.And()
            .Column("end")
            .GreaterThanOrEqual()
            .Operand(span.Start, MySqlDbType.DateTime);
            condition.And()
            .Column("start")
            .LessThanOrEqual()
            .Operand(span.End, MySqlDbType.DateTime);

            List <LoanItem> loans = Connection.Select <LoanItem>(condition).ToList();

            foreach (var loan in loans)
            {
                if (!items.Any(x => x.Id == loan.ProductItem))
                {
                    continue;
                }
                var loanSpan = new DateTimeSpan(loan.Start, loan.End);
                if (span.Overlaps(loanSpan))
                {
                    items.Remove(items.First(x => x.Id == loan.ProductItem));
                }
            }
            return(items);
        }
Beispiel #3
0
        public JObject resizeLoan(JObject request)
        {
            //Get arguments
            request.TryGetValue("loanId", out JToken loanItemID);
            request.TryGetValue("start", out JToken requestStart);
            request.TryGetValue("end", out JToken requestEnd);

            // Verify the arguments
            List <string> failedVerifications = new List <string>();

            if (loanItemID == null || loanItemID.Type != JTokenType.Integer)
            {
                failedVerifications.Add("loanId");
            }
            if (requestStart == null || !(requestStart.Type == JTokenType.String || requestStart.Type == JTokenType.Date))
            {
                failedVerifications.Add("start");
            }
            if (requestEnd == null || !(requestEnd.Type == JTokenType.String || requestEnd.Type == JTokenType.Date))
            {
                failedVerifications.Add("end");
            }

            if (failedVerifications.Any())
            {
                return(Templates.InvalidArguments(failedVerifications.ToArray()));
            }

            // Parse arguments
            DateTime start;
            DateTime end;
            string   loanID = loanItemID.ToString();

            try { start = DateTime.Parse(requestStart.ToString()); } catch (Exception) { return(Templates.InvalidArgument("Unable to parse 'start'")); }
            try { end = DateTime.Parse(requestEnd.ToString()); } catch (Exception) { return(Templates.InvalidArgument("Unable to parse 'end'")); }
            if (end < start)
            {
                return(Templates.InvalidArguments("'end' must come after 'start'"));
            }
            DateTimeSpan newLoanSpan = new DateTimeSpan(start, end);

            if (newLoanSpan.Start < DateTime.Now.Date)
            {
                return(Templates.InvalidArgument("'start' may not be set earlier than today."));
            }
            if (newLoanSpan.Duration > MaxLoanDuration)
            {
                return(Templates.InvalidArgument($"Duration of the loan may not exceed {MaxLoanDuration.Days} days."));
            }

            // Build a condition to get the specific loan
            var condition = new MySqlConditionBuilder();

            // Automatically limit results to current user only if their permission is User
            if (CurrentUser.Permission <= UserPermission.User)
            {
                condition.And().Column("user").Equals(CurrentUser.Username, MySqlDbType.String);
            }
            condition.And().Column("id").Equals(loanID, MySqlDbType.Int32);

            //Get the specified loanItem if it exists. If it doesn't, throw an error.
            LoanItem oldLoan = Connection.Select <LoanItem>(condition).FirstOrDefault();

            if (oldLoan == null)
            {
                return(Templates.NoSuchLoan(loanID));
            }

            // Return a loanExpired template if the loan has already ended
            if (oldLoan.End < DateTime.Now)
            {
                return(Templates.LoanExpired());
            }
            if (oldLoan.Start < DateTime.Now && oldLoan.Start != start)
            {
                return(Templates.LoanAlreadyStarted());
            }

            // Build condition
            condition = new MySqlConditionBuilder()
                        .Column("product_item")
                        .Equals(oldLoan.ProductItem, MySqlDbType.Int32);
            // Select only relevant loans
            condition.And().Column("end").GreaterThanOrEqual().Operand(start, MySqlDbType.DateTime);
            condition.And().Column("start").LessThanOrEqual().Operand(end, MySqlDbType.DateTime);

            //Get all loanItems for this loan's product_item.
            var loans = Connection.Select <LoanItem>(condition).ToList();

            //Check for conflicting loanItems
            bool canResize = true;

            foreach (var loan in loans)
            {
                var loanSpan = new DateTimeSpan(loan.Start, loan.End);
                if (newLoanSpan.Overlaps(loanSpan))
                {
                    canResize = false;
                    break;
                }
            }

            //Create response
            var     responseData = new JObject();
            JObject response     = new JObject()
            {
                { "reason", null },
                { "responseData", responseData }
            };

            if (canResize)
            {
                // Update loan
                oldLoan.Start = start;
                oldLoan.End   = end;
                Connection.Update(oldLoan);
            }
            else if (oldLoan.IsAcquired && !canResize)             // If loan is not eligible for a item reassignment
            {
                return(Templates.LoanResizeFailed());
            }
            else             // If the loan is eligible for an item reassignment
            {
                // Try to retrieve a list of unreserved equivalent products
                var unreservedAlts = Core_GetUnreservedItems(oldLoan.GetProductItem(Connection).ProductId, newLoanSpan);
                if (unreservedAlts == null)                 // Normally this value should not be able to be null. This block is just a failsafe that adds context.
                {
                    throw new InvalidOperationException("Expected type of 'unreservedAlts' is List, but nullreference was found.");
                }
                if (!unreservedAlts.Any())
                {
                    Log.Error("Failed at line 107");
                    return(Templates.LoanResizeFailed());
                }
                // Update loan
                oldLoan.ProductItem = unreservedAlts.First().Id.Value;
                oldLoan.Start       = start;
                oldLoan.End         = end;
                Connection.Update(oldLoan);
                // Return new data in response and add message for context
                responseData["product_item"] = oldLoan.ProductItem;
                response["message"]          = "Loan has been reassigned.";
            }

            return(response);
        }
Beispiel #4
0
        public JObject addLoan(JObject request)
        {
            //Get arguments
            request.TryGetValue("productId", out JToken requestProductId);
            request.TryGetValue("start", out JToken requestStart);
            request.TryGetValue("end", out JToken requestEnd);

            // Verify the arguments
            List <string> failedVerifications = new List <string>();

            if (requestProductId == null || requestProductId.Type != JTokenType.String)
            {
                failedVerifications.Add("productID");
            }
            if (requestStart == null || !(requestStart.Type == JTokenType.String || requestStart.Type == JTokenType.Date))
            {
                failedVerifications.Add("start");
            }
            if (requestEnd == null || !(requestEnd.Type == JTokenType.String || requestEnd.Type == JTokenType.Date))
            {
                failedVerifications.Add("end");
            }

            if (failedVerifications.Any())
            {
                return(Templates.InvalidArguments(failedVerifications.ToArray()));
            }

            // Parse arguments
            DateTime start;
            DateTime end;
            string   productId = requestProductId.ToString();

            try { start = DateTime.Parse(requestStart.ToString()); }
            catch (Exception) { return(Templates.InvalidArgument("Unable to parse 'start'")); }
            try { end = DateTime.Parse(requestEnd.ToString()); }
            catch (Exception) { return(Templates.InvalidArgument("Unable to parse 'end'")); }
            if (end < start)
            {
                return(Templates.InvalidArguments("'end' must come after 'start'"));
            }
            var newLoanSpan = new DateTimeSpan(start, end);

            if (newLoanSpan.Start < DateTime.Now.Date)
            {
                return(Templates.InvalidArgument("'start' may not be set earlier than today."));
            }
            if (newLoanSpan.Duration > MaxLoanDuration)
            {
                return(Templates.InvalidArgument($"Duration of the loan may not exceed {MaxLoanDuration.Days} days."));
            }

            // Get an unreserved product item
            ProductItem item;

            try {
                item = Core_GetUnreservedItems(productId, newLoanSpan).FirstOrDefault();
            } catch (Exception) {
                return(Templates.NoItemsForProduct(productId));
            }
            if (item == null)
            {
                return(Templates.NoItemsForProduct($"Product '{productId}' has no items available during this time."));
            }

            var loan = new LoanItem(null, CurrentUser.Username, item.Id.Value, start, end);

            Connection.Upload(loan);

            //Create response
            JObject response = new JObject()
            {
                { "reason", null },
                { "responseData", new JObject()
                  {
                      { "loanId", loan.Id },
                      { "productItem", item.Id }
                  } }
            };

            return(response);
        }
        public JObject getProductAvailability(JObject request)
        {
            //Get arguments
            request.TryGetValue("products", out JToken productValue);
            if (productValue == null || (productValue.Type != JTokenType.String && productValue.Type != JTokenType.Array))
            {
                return(Templates.MissingArguments("statType"));
            }

            //Parse arguments
            List <string> productIDs = new List <string>();

            if (productValue.Type == JTokenType.String)
            {
                //TODO Allow * to be used as value, selecting all products
                productIDs.Add(productValue.ToObject <string>());
            }
            else if (productValue.Type == JTokenType.Array)
            {
                productIDs = productValue.ToObject <List <string> >();
            }

            // Build condition that matches all relevant productItems and get said productItems
            // The lookup groups the productItems and loanItems to a single product id.
            var condition         = new MySqlConditionBuilder("product", MySqlDbType.String, productIDs.ToArray());
            var productItemsArray = Connection.Select <ProductItem>(condition).ToArray();
            var productItems      = productItemsArray.ToLookup(x => x.ProductId);

            // Build condition that matches all relevand loanItems and get said loanItems
            condition = new MySqlConditionBuilder("product_item", MySqlDbType.Int32, productItemsArray.Select(x => x.Id.Value).Cast <object>().ToArray());
            var loanItems = Connection.Select <LoanItem>(condition).ToLookup(x => productItemsArray.First(y => y.Id == x.ProductItem).ProductId);

            // DateTimeSpan representing now->midnight for filtering relevant loans
            DateTimeSpan today = new DateTimeSpan(DateTime.Now, DateTime.Now.Date.AddDays(1));

            // Build response data
            var responseData = new JObject();

            foreach (var productID in productIDs)
            {
                var items         = productItems[productID];
                var loans         = loanItems[productID];
                var relevantLoans = loans.Where(x => today.Overlaps(x.Start, x.End)).ToArray();

                var entry = new JObject()
                {
                    { "total", items.Count() },
                    { "totalReservations", loans.Count() },
                    { "reservations", relevantLoans.Length },
                    { "loanedOut", relevantLoans.Count(x => x.IsAcquired) },
                    { "inStock", items.Count() - relevantLoans.Count(x => x.IsAcquired) },
                    { "available", items.Count() - relevantLoans.Length }
                };

                responseData.Add(productID, entry);
            }

            //Return response
            return(new JObject()
            {
                { "reason", null },
                { "responseData", responseData }
            });
        }
 public bool Equals(DateTimeSpan span) => span.Start == Start && span.End == End;
        public JObject getUnavailableDates(JObject request)
        {
            // Get arguments
            request.TryGetValue("productId", out JToken requestProductId);
            request.TryGetValue("start", out JToken requestStart);
            request.TryGetValue("end", out JToken requestEnd);

            // Verify arguments
            List <string> failedVerifications = new List <string>();

            if (requestProductId == null || requestProductId.Type != JTokenType.String)
            {
                return(Templates.InvalidArgument("productId"));
            }
            if (requestStart == null)
            {
                return(Templates.InvalidArgument("start"));
            }
            if (requestEnd == null)
            {
                return(Templates.InvalidArgument("end"));
            }

            if (failedVerifications.Any())
            {
                return(Templates.InvalidArguments(failedVerifications.ToArray()));
            }

            // Parse arguments
            DateTime start;
            DateTime end;

            try { start = requestStart == null ? new DateTime() : DateTime.Parse(requestStart.ToString()); }
            catch (Exception) { return(Templates.InvalidArgument("Unable to parse 'start'")); }
            try { end = requestEnd == null ? new DateTime() : DateTime.Parse(requestEnd.ToString()); }
            catch (Exception) { return(Templates.InvalidArgument("Unable to parse 'end'")); }
            var range = new DateTimeSpan(start, end);

            if (range.Duration.Days > 122)
            {
                return(Templates.InvalidArgument("start and end may not be more than 122 days apart."));
            }

            // Get all items
            var condition    = new MySqlConditionBuilder("product", MySqlDbType.String, requestProductId.ToString());
            var productItems = Connection.Select <ProductItem>(condition).ToArray();

            // Return empty response if no items were found
            if (!productItems.Any())
            {
                return new JObject()
                       {
                           { "reason", null },
                           { "responseData", new JArray() }
                       }
            }
            ;

            // Get all loans within the specified range
            condition = new MySqlConditionBuilder("product_item", MySqlDbType.Int32, productItems.Select(x => x.Id).Cast <object>().ToArray());
            condition.And()
            .Column("end")
            .GreaterThanOrEqual()
            .Operand(range.Start, MySqlDbType.DateTime);
            condition.And()
            .Column("start")
            .LessThanOrEqual()
            .Operand(range.End, MySqlDbType.DateTime);
            var loanItems = Connection.Select <LoanItem>(condition).ToArray();

            // No idea if this is the most efficient way, but it basically iterates
            // through all dates of a loan and slaps them in a cache. If the cache
            // already contains the date, it increases the counter.
            // If the counter is larger or equal to the total amount of items, we
            // consider that date unavailable.
            var dateCache = new Dictionary <DateTime, int>();

            foreach (var loan in loanItems)
            {
                while (loan.Start < loan.End)
                {
                    if (loan.Start < range.Start)
                    {
                        loan.Start = loan.Start.AddDays(1);
                        continue;
                    }
                    if (loan.End > range.End)
                    {
                        break;
                    }
                    if (dateCache.ContainsKey(loan.Start))
                    {
                        dateCache[loan.Start]++;
                    }
                    else
                    {
                        dateCache[loan.Start] = 1;
                    }
                    loan.Start = loan.Start.AddDays(1);
                }
            }

            // Build base response
            var     responseData = new JArray();
            JObject response     = new JObject()
            {
                { "reason", null },
                { "responseData", responseData }
            };

            // Add all dates with an amount of loans greater or equal to the max available items
            foreach ((DateTime date, int count) in dateCache)
            {
                if (count >= productItems.Length)
                {
                    responseData.Add((long)(date.ToUniversalTime() - Epoch).TotalMilliseconds);
                }
            }

            return(response);
        }
    }