/// <inheritdoc />
        public async Task <(Order finishedOrder, BalanceBook updatedBalance)> FinishOrder()
        {
            var order = await storage.GetOrder();

            if (order.Costs.Count < 2)
            {
                throw new BadRequestException("Order needs to have at least 2 participant to be completed.");
            }

            if (order.DateClosed.HasValue)
            {
                throw new BadRequestException("This order has already been finalized.");
            }

            var balanceBook = await storage.GetBalanceBook();

            var sharedPart = order.SharedCost / order.Costs.Count;

            foreach (var cost in order.Costs.Values.Where(cost => cost.DebtorId != order.Owner.UniqueId))
            {
                var eater     = new FoodUser(cost.DebtorId, cost.DebtorName);
                var addedDebt = cost.Value + sharedPart;

                balanceBook.AddDebt(eater, order.Owner, addedDebt);
            }

            order.DateClosed = DateTimeOffset.UtcNow;

            // Maybe add transactions here?
            await storage.UpsertBalanceBook(balanceBook);

            await storage.UpdateOrder(order);

            return(order, balanceBook);
        }
Beispiel #2
0
        /// <summary>
        /// Forgive mentioned user's debt fully. Order-less way to alter balance.
        /// </summary>
        private async Task HandleForgiveAction(SlashCommand command)
        {
            try
            {
                // Example: ['forgive', '@user']
                var args = command.Text.Split(' ');
                if (args.Length != 2)
                {
                    throw new FormatException();
                }

                var debtor   = FoodUser.CreateFromString(args[1].Trim());
                var creditor = new FoodUser(command.UserId, command.UserName);

                if (debtor.UniqueId == creditor.UniqueId)
                {
                    await SendError(command.ResponseUrl, new BadRequestException("You can't do it to yourself."));

                    return;
                }

                await foodService.ResetCredit(debtor, creditor);
            }
            catch (FormatException)
            {
                await SendError(command.ResponseUrl,
                                new BadRequestException($"Incorrect format, expected: `/food {CommandTexts.Forgive} @<user>`"));
            }
        }
        public async Task <(Order reopenedOrder, BalanceBook updatedBalance)> ReopenOrder()
        {
            var order = await storage.GetOrder();

            if (order?.DateClosed == null)
            {
                throw new BadRequestException("There is no valid order to reopen.");
            }

            var balanceBook = await storage.GetBalanceBook();

            var sharedPart = order.SharedCost / order.Costs.Count;

            foreach (var cost in order.Costs.Values.Where(cost => cost.DebtorId != order.Owner.UniqueId))
            {
                var eater     = new FoodUser(cost.DebtorId, cost.DebtorName);
                var addedDebt = cost.Value + sharedPart;

                balanceBook.AddDebt(order.Owner, eater, addedDebt);
            }

            order.DateClosed = null;

            await storage.UpdateOrder(order);

            await storage.UpsertBalanceBook(balanceBook);

            return(order, balanceBook);
        }
Beispiel #4
0
        /// <summary>
        /// Add some debt by caller to mentioned user. Order-less way to alter balance.
        /// </summary>
        private async Task HandleOweAction(SlashCommand command)
        {
            try
            {
                // Example: ['owe', '@user' '4.99']
                var args = command.Text.Split(' ');
                if (args.Length != 3)
                {
                    throw new FormatException();
                }

                var creditor = FoodUser.CreateFromString(args[1].Trim());
                var debtor   = new FoodUser(command.UserId, command.UserName);

                if (debtor.UniqueId == creditor.UniqueId)
                {
                    await SendError(command.ResponseUrl, new BadRequestException("You can't do it to yourself."));

                    return;
                }

                var creditValue = decimal.Parse(args[2].Replace(',', '.'));
                await foodService.OweCredit(debtor, creditor, creditValue);
            }
            catch (FormatException)
            {
                await SendError(command.ResponseUrl,
                                new BadRequestException($"Incorrect format, expected: `/food {CommandTexts.Owe} @<user> <cost>`"));
            }
        }
        public async Task <Order> AddEater(FoodUser eater, decimal cost, string item)
        {
            var order = await storage.GetOrder();

            if (order == null)
            {
                throw new BadRequestException("There is no order open.");
            }

            if (!order.IsOpen)
            {
                throw new BadRequestException("Order is closed and cannot be altered.");
            }

            order.Costs[eater.UniqueId] = new Cost
            {
                DebtorId   = eater.UniqueId,
                DebtorName = eater.FriendlyName,
                Value      = cost,
                Item       = item
            };

            if (order.Costs[eater.UniqueId].Value == 0)
            {
                order.Costs.Remove(eater.UniqueId);
            }

            await storage.UpdateOrder(order);

            return(order);
        }
Beispiel #6
0
        public PairBalance(FoodUser a, FoodUser b)
        {
            var sortedPair = CreateSortedPair(a, b);

            PartyA     = sortedPair.A;
            PartyB     = sortedPair.B;
            Balance    = 0;
            LastChange = DateTimeOffset.UtcNow;
        }
        public async Task <PairBalance> OweCredit(FoodUser debtor, FoodUser creditor, decimal value)
        {
            var balanceBook = await storage.GetBalanceBook();

            balanceBook.AddDebt(debtor, creditor, value);
            await storage.UpsertBalanceBook(balanceBook);

            return(balanceBook.GetBalance(debtor, creditor));
        }
        /// <summary>
        /// Returns balance between the given user pair.
        /// </summary>
        public PairBalance GetBalance(FoodUser a, FoodUser b)
        {
            var pairKey = PairBalance.CreateKey(a, b);

            if (!Balances.ContainsKey(pairKey))
            {
                Balances.Add(pairKey, new PairBalance(a, b));
            }

            return(Balances[pairKey]);
        }
        /// <summary>
        /// Increases debtor's debt to creditor by specific amount.
        /// </summary>
        /// <param name="debtor">User losing credit.</param>
        /// <param name="creditor">User gaining credit.</param>
        /// <param name="amtLost">Positive number, amount of money lost.</param>
        /// <returns>New balance state.</returns>
        public PairBalance AddDebt(FoodUser debtor, FoodUser creditor, decimal amtLost)
        {
            if (debtor.UniqueId == creditor.UniqueId)
            {
                throw new BadRequestException("You can't add debt to yourself.");
            }
            var pair = GetBalance(debtor, creditor);

            pair.AddDebt(debtor, amtLost);
            LastChange = DateTimeOffset.UtcNow;

            return(pair);
        }
Beispiel #10
0
        /// <summary>
        /// Returns true if given <see cref="user"/> is owed money by the other one.
        /// </summary>
        public bool HasPositiveBalance(FoodUser user)
        {
            if (user.UniqueId == PartyA.UniqueId)
            {
                return(Balance > 0);
            }

            if (user.UniqueId == PartyB.UniqueId)
            {
                return(Balance < 0);
            }

            throw new InvalidOperationException("Invalid pair.");
        }
Beispiel #11
0
        /// <summary>
        /// Sorts any two users alphabetically.
        /// </summary>
        public static (FoodUser A, FoodUser B) CreateSortedPair(FoodUser a, FoodUser b)
        {
            if (string.CompareOrdinal(a.UniqueId, b.UniqueId) < 0)
            {
                return(a, b);
            }

            if (string.CompareOrdinal(a.UniqueId, b.UniqueId) > 0)
            {
                return(b, a);
            }

            throw new ArgumentException("You can't create a pair from the same user.");
        }
Beispiel #12
0
        public void GetMyRecipesTest()
        {
            //arrange
            FoodUser user = new FoodUser();

            user.SetProperties(); // Set Here

            //act
            user.GetMyRecipes();
            int actual   = user.MyRecipes.Count;
            int expected = 2;

            //assert
            Assert.AreEqual(expected, actual);
        }
Beispiel #13
0
        public async Task <PairBalance> ResetCredit(FoodUser debtor, FoodUser creditor)
        {
            var balanceBook = await storage.GetBalanceBook();

            var pair = balanceBook.GetBalance(debtor, creditor);

            if (pair.HasPositiveBalance(creditor))
            {
                pair.AddDebt(creditor, pair.Balance);
                await storage.UpsertBalanceBook(balanceBook);

                return(pair);
            }
            else
            {
                throw new BadRequestException("Given user doesn't owe you anything.");
            }
        }
Beispiel #14
0
        public async Task CancelOpenOrder(FoodUser caller)

        {
            var order = await storage.GetOrder();

            if (order == null || !order.IsOpen)
            {
                throw new BadRequestException("There is no valid order to cancel.");
            }

            const int minTimeout = 30;

            if (caller.UniqueId != order.Owner.UniqueId &&
                DateTimeOffset.UtcNow - order.DateCreated < TimeSpan.FromMinutes(minTimeout))
            {
                throw new BadRequestException($"Only order owner can cancel the order during first {minTimeout} minutes.");
            }

            await storage.DeleteOrder();
        }
Beispiel #15
0
        /// <summary>
        /// Increases user's debt by given amount.
        /// </summary>
        /// <param name="userLosingMoney">User losing money.</param>
        /// <param name="amtLost">Positive number, amount of money lost.</param>
        public void AddDebt(FoodUser userLosingMoney, decimal amtLost)
        {
            if (amtLost <= 0)
            {
                throw new ArgumentException("Amount needs to be a positive number.");
            }

            if (userLosingMoney.UniqueId == PartyA.UniqueId)
            {
                Balance -= amtLost;
            }
            else if (userLosingMoney.UniqueId == PartyB.UniqueId)
            {
                Balance += amtLost;
            }
            else
            {
                throw new ArgumentException("User doesn't belong in this pair.");
            }
        }
Beispiel #16
0
        /// <summary>
        /// Add someone else as eater to currently open order and publish updated order to slack.
        /// Update current entry, if already added.
        /// Can be used to host external guests.
        /// </summary>
        private async Task HandleEatAsOtherAction(SlashCommand command)
        {
            try
            {
                // Example: ['eat', 'batman', '12.50', 'pizza']
                var args = command.Text.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList();

                if (args.Count < 3)
                {
                    throw new FormatException();
                }

                // Eater
                var eaterStr = args[1].Trim();
                var eater    = FoodUser.CreateFromString(eaterStr);

                // Cost
                args[2] = args[2].Replace(',', '.');
                var cost = decimal.Parse(args[2], CultureInfo.InvariantCulture);

                // Optional item description
                string item = null !;
                if (args.Count >= 4)
                {
                    item = item = string.Join(' ', args.Skip(3).ToArray());
                }

                var updatedOrder = await foodService.AddEater(eater : eater, cost, item);
                await SendCommandResponse(command, SlackFormatter.BuildOrderMessage(updatedOrder));
            }
            catch (FormatException)
            {
                await SendError(command.ResponseUrl,
                                new BadRequestException($"Incorrect format, expected: `/food {CommandTexts.EatAs} <who> <cost> [description]`"));
            }
            catch (Exception x)
            {
                await SendError(command.ResponseUrl, x);
            }
        }
Beispiel #17
0
        /// <summary>
        /// Creates a string key that uniquely identifies a pair (in any argument order).
        /// </summary>

        public static string CreateKey(FoodUser a, FoodUser b)
        {
            var sortedPair = CreateSortedPair(a, b);

            return(sortedPair.A.UniqueId + "@@@" + sortedPair.B.UniqueId);
        }