Example #1
0
        public ExpenseRepaymentCollection Post([FromBody] IEnumerable <User> users)
        {
            if (users == null || !users.Any() || users.All(u => !u.Expenses.Any()))
            {
                // empty result passed
                ThrowResponseException(HttpStatusCode.BadRequest, "Empty list of users entered");
            }

            if (users.SelectMany(u => u.Expenses).Any(exp => exp < 0))
            {
                // one or more expenses has a negative amount
                ThrowResponseException(HttpStatusCode.BadRequest, "One or more Expense line items have a negative expense amount");
            }

            ExpenseRepaymentCollection result = new ExpenseRepaymentCollection();

            // Flatten list into 1 expense line item per name, with their total expenditure
            var totalExpenses = users.GroupBy(u => u.Name)
                                .Select(x =>
                                        new ExpenseLineItem()
            {
                Name          = x.Key,
                ExpenseAmount = x.SelectMany(y => y.Expenses).Sum()
            }
                                        ).ToList();

            decimal totalExpenditure = 0;

            try
            {
                totalExpenditure = totalExpenses.Sum(e => e.ExpenseAmount);
            }
            catch (OverflowException)
            {
                ThrowResponseException(HttpStatusCode.BadRequest, "Expenses entered are total greater than maximum precision");
            }


            if (totalExpenditure == 0)
            {
                return(result);
            }

            decimal equalShares = totalExpenditure / totalExpenses.Count;

            if (totalExpenses.All(e => e.EqualWithinOneCent(equalShares)))
            {
                // All users have already paid equally, no repayments need to be done
                return(result);
            }

            //Because we only ever need to look at the front of the line, a stack is more efficient
            Stack <ExpenseLineItem> paidMoreThanEqual = new Stack <ExpenseLineItem>(totalExpenses.Where(e => e.ExpenseAmount > equalShares));
            Stack <ExpenseLineItem> paidLessThanEqual = new Stack <ExpenseLineItem>(totalExpenses.Where(e => e.ExpenseAmount < equalShares));

            List <ExpenseRepayment> repayments = new List <ExpenseRepayment>();

            while (paidMoreThanEqual.Any() && paidLessThanEqual.Any())
            {
                ExpenseLineItem currentUnderpayer = paidLessThanEqual.Peek();
                ExpenseLineItem currentOverpayer  = paidMoreThanEqual.Peek();

                decimal amountToPay = Math.Min(currentOverpayer.ExpenseAmount - equalShares, equalShares - currentUnderpayer.ExpenseAmount);

                repayments.Add(new ExpenseRepayment()
                {
                    PayFrom = currentUnderpayer.Name, PayTo = currentOverpayer.Name, Amout = amountToPay
                });

                currentOverpayer.ExpenseAmount  -= amountToPay;
                currentUnderpayer.ExpenseAmount += amountToPay;

                if (currentUnderpayer.EqualWithinOneCent(equalShares))
                {
                    paidLessThanEqual.Pop();
                }

                if (currentOverpayer.EqualWithinOneCent(equalShares))
                {
                    paidMoreThanEqual.Pop();
                }
            }

            result.Repayments = repayments.ToArray();
            result.Status     = (byte)(result.Repayments.Any() ? 1 : 0);
            return(result);
        }