/// <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); }
/// <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); }
/// <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); }
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); }
/// <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."); }
/// <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."); }
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); }
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."); } }
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(); }
/// <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."); } }
/// <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); } }
/// <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); }