コード例 #1
0
 public CostBreakdown GetLifetimeCostBreakdown()
 {
     using (SqliteConnection conn = new SqliteConnection(_databaseConn))
     {
         conn.Open();
         SqliteCommand cmd = new SqliteCommand();
         cmd.Connection = conn;
         StringBuilder sql   = new StringBuilder("SELECT ");
         int           index = 0;
         foreach (Category category in Enum.GetValues(typeof(Category)))
         {
             if (index != 0)
             {
                 sql.Append(", ");
             }
             sql.Append($"SUM(CASE WHEN Category = @category{index} AND Type = 'Credit' THEN -Amount WHEN Category = @category{index} AND Type = 'Debit' THEN Amount ELSE 0 END) AS {category.ToString()}");
             cmd.Parameters.Add($"category{index}", SqliteType.Text).Value = category.ToString();
             index++;
         }
         sql.Append(" FROM Transactions");
         cmd.CommandText = sql.ToString();
         SqliteDataReader reader = cmd.ExecuteReader();
         if (reader.Read())
         {
             CostBreakdown costBreakdown = new CostBreakdown();
             costBreakdown.Auto      = reader["Auto"].ConvertSqliteStorageToDecimal();
             costBreakdown.Dining    = reader["Dining"].ConvertSqliteStorageToDecimal();
             costBreakdown.Grocery   = reader["Grocery"].ConvertSqliteStorageToDecimal();
             costBreakdown.Home      = reader["Home"].ConvertSqliteStorageToDecimal();
             costBreakdown.Interest  = -reader["Interest"].ConvertSqliteStorageToDecimal();
             costBreakdown.Loans     = reader["Loans"].ConvertSqliteStorageToDecimal();
             costBreakdown.Luxury    = reader["Luxury"].ConvertSqliteStorageToDecimal();
             costBreakdown.Misc      = reader["Misc"].ConvertSqliteStorageToDecimal();
             costBreakdown.Mortgage  = reader["Mortgage"].ConvertSqliteStorageToDecimal();
             costBreakdown.Paycheck  = -reader["Paycheck"].ConvertSqliteStorageToDecimal();
             costBreakdown.Travel    = reader["Travel"].ConvertSqliteStorageToDecimal();
             costBreakdown.Utilities = reader["Utilities"].ConvertSqliteStorageToDecimal();
             costBreakdown.Work      = reader["Work"].ConvertSqliteStorageToDecimal();
             costBreakdown.Payments  = reader["Payment"].ConvertSqliteStorageToDecimal();
             return(costBreakdown);
         }
     }
     return(null);
 }
コード例 #2
0
        public IEnumerable <CostBreakdown> GetYearCostBreakdowns()
        {
            List <CostBreakdown> costBreakdowns = new List <CostBreakdown>();

            using (SqliteConnection conn = new SqliteConnection(_databaseConn))
            {
                conn.Open();
                SqliteCommand cmd = new SqliteCommand();
                cmd.Connection = conn;
                StringBuilder sql   = new StringBuilder("SELECT ");
                int           index = 0;
                foreach (Category category in Enum.GetValues(typeof(Category)))
                {
                    sql.Append($"SUM(CASE WHEN Category = @category{index} AND Type = 'Credit' THEN -Amount WHEN Category = @category{index} AND Type = 'Debit' THEN Amount ELSE 0 END) AS {category.ToString()}, ");
                    cmd.Parameters.Add($"category{index}", SqliteType.Text).Value = category.ToString();
                    index++;
                }
                sql.Append("strftime('%Y', PostDate) as year FROM Transactions GROUP BY year");
                cmd.CommandText = sql.ToString();
                SqliteDataReader reader = cmd.ExecuteReader();
                while (reader.Read())
                {
                    CostBreakdown costBreakdown = new CostBreakdown();
                    costBreakdown.Auto       = reader["Auto"].ConvertSqliteStorageToDecimal();
                    costBreakdown.Dining     = reader["Dining"].ConvertSqliteStorageToDecimal();
                    costBreakdown.Grocery    = reader["Grocery"].ConvertSqliteStorageToDecimal();
                    costBreakdown.Home       = reader["Home"].ConvertSqliteStorageToDecimal();
                    costBreakdown.Interest   = -reader["Interest"].ConvertSqliteStorageToDecimal();
                    costBreakdown.Loans      = reader["Loans"].ConvertSqliteStorageToDecimal();
                    costBreakdown.Luxury     = reader["Luxury"].ConvertSqliteStorageToDecimal();
                    costBreakdown.Misc       = reader["Misc"].ConvertSqliteStorageToDecimal();
                    costBreakdown.Mortgage   = reader["Mortgage"].ConvertSqliteStorageToDecimal();
                    costBreakdown.Paycheck   = -reader["Paycheck"].ConvertSqliteStorageToDecimal();
                    costBreakdown.TimePeriod = new DateTime(Convert.ToInt32(reader["year"]), 1, 1);
                    costBreakdown.Travel     = reader["Travel"].ConvertSqliteStorageToDecimal();
                    costBreakdown.Utilities  = reader["Utilities"].ConvertSqliteStorageToDecimal();
                    costBreakdown.Work       = reader["Work"].ConvertSqliteStorageToDecimal();
                    costBreakdown.Payments   = reader["Payment"].ConvertSqliteStorageToDecimal();
                    costBreakdowns.Add(costBreakdown);
                }
                return(costBreakdowns);
            }
        }
コード例 #3
0
        public IEnumerable <CostBreakdown> GetMonthCostBreakdowns()
        {
            Logger.Log("Retrieving breakdowns of costs by month");
            List <CostBreakdown> costBreakdowns = new List <CostBreakdown>();

            using (SQLiteConnection conn = new SQLiteConnection(_databaseConn))
            {
                conn.Open();
                SQLiteCommand cmd   = new SQLiteCommand(conn);
                StringBuilder sql   = new StringBuilder("SELECT ");
                int           index = 0;
                foreach (TransactionCategory category in Enum.GetValues(typeof(TransactionCategory)))
                {
                    sql.Append($"SUM(CASE WHEN Category = @category{index} AND Type = 'Credit' THEN -Amount WHEN Category = @category{index} AND Type = 'Debit' THEN Amount ELSE 0 END) AS {category.ToString()}, ");
                    cmd.Parameters.Add($"category{index}", DbType.String).Value = category.ToString();
                    index++;
                }
                sql.Append("strftime('%m-%Y', PostDate) as monthyear FROM Transactions GROUP BY monthyear");
                cmd.CommandText = sql.ToString();
                SQLiteDataReader reader = cmd.ExecuteReader();
                while (reader.Read())
                {
                    CostBreakdown costBreakdown = new CostBreakdown();
                    costBreakdown.Auto       = Convert.ToDecimal(reader["Auto"]);
                    costBreakdown.Dining     = Convert.ToDecimal(reader["Dining"]);
                    costBreakdown.Grocery    = Convert.ToDecimal(reader["Grocery"]);
                    costBreakdown.Home       = Convert.ToDecimal(reader["Home"]);
                    costBreakdown.Interest   = -Convert.ToDecimal(reader["Interest"]);
                    costBreakdown.Loans      = Convert.ToDecimal(reader["Loans"]);
                    costBreakdown.Luxury     = Convert.ToDecimal(reader["Luxury"]);
                    costBreakdown.Misc       = Convert.ToDecimal(reader["Misc"]);
                    costBreakdown.Mortgage   = Convert.ToDecimal(reader["Mortgage"]);
                    costBreakdown.Paycheck   = -Convert.ToDecimal(reader["Paycheck"]);
                    costBreakdown.TimePeriod = Convert.ToDateTime(reader["monthyear"]);
                    costBreakdown.Travel     = Convert.ToDecimal(reader["Travel"]);
                    costBreakdown.Utilities  = Convert.ToDecimal(reader["Utilities"]);
                    costBreakdown.Work       = Convert.ToDecimal(reader["Work"]);
                    costBreakdown.Payments   = Convert.ToDecimal(reader["Payment"]);
                    costBreakdowns.Add(costBreakdown);
                }
                return(costBreakdowns);
            }
        }
コード例 #4
0
 private void OnNavToMonthView(CostBreakdown breakdown)
 {
     try
     {
         if (breakdown != null)
         {
             IEnumerable <Transaction> viewTransactions = _transactionRepository.GetTransactionsByMonth(breakdown.TimePeriod);
             _monthlyCostsViewModel.SetTransactions(viewTransactions);
             _monthlyCostsViewModel.SetBreakdown(breakdown);
             CurrentViewModel = _monthlyCostsViewModel;
         }
         else
         {
             WpfMessageBox.ShowDialog("Warning", "No transaction data available for the current month.", MessageBoxButton.OK, MessageIcon.Information);
         }
     }
     catch (Exception ex)
     {
         WpfMessageBox.ShowDialog("Error", ex.Message, MessageBoxButton.OK, MessageIcon.Error);
     }
 }
コード例 #5
0
        public bool FinalizeXact()
        {
            // Scan through and compute the total balance for the xact.  This is used
            // for auto-calculating the value of xacts with no cost, and the per-unit
            // price of unpriced commodities.

            Value balance  = null;
            Post  nullPost = null;

            foreach (Post post in Posts)
            {
                if (!post.MustBalance)
                {
                    continue;
                }

                Amount p = post.Cost ?? post.Amount;
                if (p != null && !p.IsEmpty)
                {
                    Logger.Current.Debug("xact.finalize", () => String.Format("post must balance = {0}", p.Reduced()));
                    // If the amount was a cost, it very likely has the
                    // "keep_precision" flag set, meaning commodity display precision
                    // is ignored when displaying the amount.  We never want this set
                    // for the balance, so we must clear the flag in a temporary to
                    // avoid it propagating into the balance.
                    balance = Value.AddOrSetValue(balance, Value.Get(p.KeepPrecision ? p.Rounded().Reduced() : p.Reduced()));
                }
                else if (nullPost != null)
                {
                    bool postAccountBad     = AccountEndsWithSpecialChar(post.Account.FullName);
                    bool nullPostAccountBad = AccountEndsWithSpecialChar(nullPost.Account.FullName);

                    if (postAccountBad || nullPostAccountBad)
                    {
                        throw new LogicError(String.Format(LogicError.ErrorMessagePostingWithNullAmountsAccountMayBeMisspelled, postAccountBad ? post.Account.FullName : nullPost.Account.FullName));
                    }
                    else
                    {
                        throw new LogicError(LogicError.ErrorMessageOnlyOnePostingWithNullAmountAllowedPerTransaction);
                    }
                }
                else
                {
                    nullPost = post;
                }
            }
            Validator.Verify(() => balance == null || balance.IsValid);

            Logger.Current.Debug("xact.finalize", () => String.Format("initial balance = {0}", balance));
            Logger.Current.Debug("xact.finalize", () => String.Format("balance is {0}", balance.Label()));
            if (balance != null && balance.Type == ValueTypeEnum.Balance)
            {
                Logger.Current.Debug("xact.finalize", () => String.Format("balance commodity count = {0}", balance.AsBalance.Amounts.Count()));
            }

            // If there is only one post, balance against the default account if one has
            // been set.

            if (Journal != null && Journal.Bucket != null && Posts.Count == 1 && !Value.IsNullOrEmpty(balance))
            {
                nullPost = new Post()
                {
                    Account = Journal.Bucket, Flags = SupportsFlagsEnum.ITEM_GENERATED
                };
                nullPost.State = Posts.First().State;
                AddPost(nullPost);
            }

            if (nullPost == null && balance != null && balance.Type == ValueTypeEnum.Balance && balance.AsBalance.Amounts.Count == 2)
            {
                // When an xact involves two different commodities (regardless of how
                // many posts there are) determine the conversion ratio by dividing the
                // total value of one commodity by the total value of the other.  This
                // establishes the per-unit cost for this post for both commodities.

                Logger.Current.Debug("xact.finalize", () => "there were exactly two commodities, and no null post");

                bool sawCost = false;
                Post topPost = null;

                foreach (Post post in Posts)
                {
                    if (!Amount.IsNullOrEmpty(post.Amount) && post.MustBalance)
                    {
                        if (post.Amount.HasAnnotation)
                        {
                            topPost = post;
                        }
                        else if (topPost == null)
                        {
                            topPost = post;
                        }
                    }

                    if (post.Cost != null && !post.Flags.HasFlag(SupportsFlagsEnum.POST_COST_CALCULATED))
                    {
                        sawCost = true;
                        break;
                    }
                }

                if (!sawCost && topPost != null)
                {
                    Balance bal = balance.AsBalance;

                    Logger.Current.Debug("xact.finalize", () => "there were no costs, and a valid top_post");

                    Amount x = bal.Amounts.ElementAt(0).Value;
                    Amount y = bal.Amounts.ElementAt(1).Value;

                    if ((bool)x && (bool)y)
                    {
                        if (x.Commodity != topPost.Amount.Commodity)
                        {
                            Amount z = x; x = y; y = z;
                        }

                        Logger.Current.Debug("xact.finalize", () => String.Format("primary   amount = {0}", x));
                        Logger.Current.Debug("xact.finalize", () => String.Format("secondary amount = {0}", y));

                        Commodity comm        = x.Commodity;
                        Amount    perUnitCost = (y / x).Abs().Unrounded();

                        Logger.Current.Debug("xact.finalize", () => String.Format("per_unit_cost = {0}", perUnitCost));

                        foreach (Post post in Posts)
                        {
                            Amount amt = post.Amount;

                            if (post.MustBalance && amt != null && amt.Commodity == comm)
                            {
                                balance.InPlaceSubtract(Value.Get(amt));
                                post.Cost  = perUnitCost * amt;
                                post.Flags = post.Flags | SupportsFlagsEnum.POST_COST_CALCULATED;
                                balance.InPlaceAdd(Value.Get(post.Cost));

                                Logger.Current.Debug("xact.finalize", () => String.Format("set post->cost to = {0}", post.Cost));
                            }
                        }
                    }
                }
            }

            var postsCopy = new List <Post>(Posts);

            if (HasDate)
            {
                foreach (Post post in postsCopy)
                {
                    if (post.Cost == null)
                    {
                        continue;
                    }

                    if (post.Amount.Commodity == post.Cost.Commodity)
                    {
                        throw new BalanceError(BalanceError.ErrorMessageAPostingsCostMustBeOfADifferentCommodityThanItsAmount);
                    }

                    CostBreakdown breakdown = CommodityPool.Current.Exchange(post.Amount, post.Cost, false, !post.Flags.HasFlag(SupportsFlagsEnum.POST_COST_VIRTUAL), GetDate());

                    if (post.Amount.HasAnnotation && post.Amount.Annotation.Price != null)
                    {
                        if (breakdown.BasisCost.Commodity == breakdown.FinalCost.Commodity)
                        {
                            Logger.Current.Debug("xact.finalize", () => String.Format("breakdown.basis_cost = {0}", breakdown.BasisCost));
                            Logger.Current.Debug("xact.finalize", () => String.Format("breakdown.final_cost = {0}", breakdown.FinalCost));
                            Amount gainLoss = breakdown.BasisCost - breakdown.FinalCost;
                            if ((bool)gainLoss)
                            {
                                Logger.Current.Debug("xact.finalize", () => String.Format("gain_loss = {0}", gainLoss));
                                gainLoss.InPlaceRound();
                                Logger.Current.Debug("xact.finalize", () => String.Format("gain_loss rounds to = {0}", gainLoss));
                                if (post.MustBalance)
                                {
                                    balance = Value.AddOrSetValue(balance, Value.Get(gainLoss.Reduced()));
                                }

                                post.Cost.InPlaceAdd(gainLoss);
                                Logger.Current.Debug("xact.finalize", () => String.Format("added gain_loss, balance = {0}", balance));
                            }
                            else
                            {
                                Logger.Current.Debug("xact.finalize", () => "gain_loss would have displayed as zero");
                            }
                        }
                    }
                    else
                    {
                        post.Amount = breakdown.Amount.HasAnnotation ?
                                      new Amount(breakdown.Amount, new Annotation(breakdown.Amount.Annotation.Price, breakdown.Amount.Annotation.Date,
                                                                                  post.Amount.HasAnnotation ? post.Amount.Annotation.Tag : breakdown.Amount.Annotation.Tag)
                        {
                            ValueExpr = breakdown.Amount.Annotation.ValueExpr
                        })
                            : breakdown.Amount;
                        Logger.Current.Debug("xact.finalize", () => String.Format("added breakdown, balance = {0}", balance));
                    }

                    if (post.Flags.HasFlag(SupportsFlagsEnum.POST_COST_FIXATED) && post.Amount.HasAnnotation && post.Amount.Annotation.Price != null)
                    {
                        Logger.Current.Debug("xact.finalize", () => "fixating annotation price");
                        post.Amount.Annotation.IsPriceFixated = true;
                    }
                }
            }

            if (nullPost != null)
            {
                // If one post has no value at all, its value will become the inverse of
                // the rest.  If multiple commodities are involved, multiple posts are
                // generated to balance them all.

                Logger.Current.Debug("xact.finalize", () => "there was a null posting");
                AddBalancingPost postAdder = new AddBalancingPost(this, nullPost);

                if (balance != null) // DM - all further steps makes sense only if the balance is not null
                {
                    if (balance.Type == ValueTypeEnum.Balance)
                    {
                        balance.AsBalance.MapSortedAmounts(amt => postAdder.Amount(amt));
                    }
                    else if (balance.Type == ValueTypeEnum.Amount)
                    {
                        postAdder.Amount(balance.AsAmount);
                    }
                    else if (balance.Type == ValueTypeEnum.Integer)
                    {
                        postAdder.Amount(balance.AsAmount);
                    }
                    else if (!Value.IsNullOrEmpty(balance) && !balance.IsRealZero)
                    {
                        throw new BalanceError(BalanceError.ErrorMessageTransactionDoesNotBalance);
                    }
                }

                balance = Value.Empty;
            }
            Logger.Current.Debug("xact.finalize", () => String.Format("resolved balance = ", balance));

            if (!Value.IsNullOrEmpty(balance) && !balance.IsZero)
            {
                ErrorContext.Current.AddErrorContext(ItemContext(this, "While balancing transaction"));
                ErrorContext.Current.AddErrorContext("Unbalanced remainder is:");
                ErrorContext.Current.AddErrorContext(Value.ValueContext(balance));
                ErrorContext.Current.AddErrorContext("Amount to balance against:");
                ErrorContext.Current.AddErrorContext(Value.ValueContext(Magnitude()));
                throw new BalanceError(BalanceError.ErrorMessageTransactionDoesNotBalance);
            }

            // Add a pointer to each posting to their related accounts

            if (this is Xact)
            {
                bool allNull  = true;
                bool someNull = false;

                foreach (Post post in Posts)
                {
                    if (post.Account == null)
                    {
                        throw new InvalidOperationException("assert(post->account);");
                    }

                    if (!Amount.IsNullOrEmpty(post.Amount))
                    {
                        allNull = false;
                        post.Amount.InPlaceReduce();
                    }
                    else
                    {
                        someNull = true;
                    }

                    if (post.Flags.HasFlag(SupportsFlagsEnum.POST_DEFERRED))
                    {
                        if (!Amount.IsNullOrEmpty(post.Amount))
                        {
                            post.Account.AddDeferredPosts(Id, post);
                        }
                    }
                    else
                    {
                        post.Account.AddPost(post);
                    }

                    post.XData.Visited         = true; // POST_EXT_VISITED
                    post.Account.XData.Visited = true; // ACCOUNT_EXT_VISITED;
                }

                if (allNull)
                {
                    return(false); // ignore this xact completely
                }
                else if (someNull)
                {
                    throw new BalanceError(BalanceError.ErrorMessageThereCannotBeNullAmountsAfterBalancingATransaction);
                }
            }

            Validator.Verify(() => Valid());

            return(true);
        }