<commit_before>// @flow
import R from 'ramda';
import {createSelector} from 'reselect';
import {arraySelector} from 'hw-react-shared';
import type {Transaction} from '../entities/Transaction';
import {TransactionResource} from '../entities/Transaction';
import {sumOfAmounts, beginningOfMonth} from './utils';
import type {Account} from '../entities/Account';
import {AccountResource} from '../entities/Account';
import type {Category} from '../entities/Category';
import {CategoryResource} from '../entities/Category';
import type {BudgetItem} from '../entities/BudgetItem';
import {BudgetItemResource} from '../entities/BudgetItem';
import {getSelectedMonthMoment, getPreviousMonthMoment} from './month';
import {
  budgetItemsInMonth,
  getBudgetItemsSumUpToPreviousMonth,
  budgetItemsUpToMonth
} from './budgetItems';
import {
  getToBeBudgetedSumUpToSelectedMonth,
  transactionsUpToMonth,
  flattenTransactions
} from './transactions';

/**
 * Returns the balance of the budget i.e. the total amount of money across accounts.
 */
export const getBudgetBalance = createSelector(
  arraySelector(TransactionResource),
  (transactions: Transaction[]) => sumOfAmounts(transactions.filter(t => !t.transfer_account_uuid))
);

/**
 * Returns the balance per account
 */
export const selectBalanceByAccountId = createSelector(
  arraySelector(AccountResource),
  arraySelector(TransactionResource),
  (accounts: Account[], transactions: Transaction[]) => {
    const withTransferAccount = R.filter(R.prop('transfer_account_uuid'));
    const reduceToAmountSumBy = R.reduceBy((acc, record) => acc + record.amount, 0);
    const sumByAccountId = reduceToAmountSumBy(R.prop('account_uuid'));
    const sumByTransferAccountId = reduceToAmountSumBy(R.prop('transfer_account_uuid'));
    return R.mergeWith(
      (a, b) => a - b,
      sumByAccountId(transactions),
      sumByTransferAccountId(withTransferAccount(transactions))
    );
  }
);

/**
 * Returns the sum of all budget items and transactions by category and by month (chronological)
 */
const sumOfBudgetItemsAndTransactionsByCategoryByMonth = createSelector(
  arraySelector(CategoryResource),
  arraySelector(BudgetItemResource),
  arraySelector(TransactionResource),
  (categories: Category[], budgetItems: BudgetItem[], transactions: Transaction[]) => {
    const result = new Map();

    // Initialize the result for each existing category.
    categories.forEach(c => result.set(c.uuid, new Map()));

    budgetItems.forEach(bi => {
      const categoryResult = result.get(bi.category_uuid) || new Map();
      categoryResult.set(bi.month, (categoryResult.get(bi.month) || 0) + bi.amount);
    });

    flattenTransactions(transactions).forEach(ft => {
      const month = beginningOfMonth(ft.date);
      const categoryResult = result.get(ft.category_uuid) || new Map();
      categoryResult.set(month, (categoryResult.get(month) || 0) + ft.amount);
    });

    // Sort the result chronologically
    const sortedResult = new Map();
    result.forEach((g, category_uuid) => {
      const sortedMonth = new Map();
      Array.from(g.keys()).sort().forEach(m => sortedMonth.set(m, g.get(m)));
      sortedResult.set(category_uuid, sortedMonth);
    });

    return sortedResult;
  }
);

/**
 * Returns overspendings by category and by month
 */
const getOverspendingByCategoryIdByMonth = createSelector(
  sumOfBudgetItemsAndTransactionsByCategoryByMonth,
  input => {
    const overspendings = new Map();
    input.forEach((g, category_uuid) => {
      let sumAccrossMonths = 0;
      g.forEach((value, month) => {
        sumAccrossMonths += value;
        if (sumAccrossMonths < 0) {
          const newMap = new Map();
          newMap.set(month, sumAccrossMonths);
          overspendings.set(category_uuid, newMap);
          sumAccrossMonths = 0;
        }
      });
    });
    return overspendings;
  }
);

// Returns for each category the amount available in the budget for that category for the month
export const getAvailableByCategoryIdForSelectedMonth = createSelector(<commit_msg>Fixed overspending over multiple months<commit_after>// @flow
import R from 'ramda';
import {createSelector} from 'reselect';
import {arraySelector} from 'hw-react-shared';
import type {Transaction} from '../entities/Transaction';
import {TransactionResource} from '../entities/Transaction';
import {sumOfAmounts, beginningOfMonth} from './utils';
import type {Account} from '../entities/Account';
import {AccountResource} from '../entities/Account';
import type {Category} from '../entities/Category';
import {CategoryResource} from '../entities/Category';
import type {BudgetItem} from '../entities/BudgetItem';
import {BudgetItemResource} from '../entities/BudgetItem';
import {getSelectedMonthMoment, getPreviousMonthMoment} from './month';
import {
  budgetItemsInMonth,
  getBudgetItemsSumUpToPreviousMonth,
  budgetItemsUpToMonth
} from './budgetItems';
import {
  getToBeBudgetedSumUpToSelectedMonth,
  transactionsUpToMonth,
  flattenTransactions
} from './transactions';

/**
 * Returns the balance of the budget i.e. the total amount of money across accounts.
 */
export const getBudgetBalance = createSelector(
  arraySelector(TransactionResource),
  (transactions: Transaction[]) => sumOfAmounts(transactions.filter(t => !t.transfer_account_uuid))
);

/**
 * Returns the balance per account
 */
export const selectBalanceByAccountId = createSelector(
  arraySelector(AccountResource),
  arraySelector(TransactionResource),
  (accounts: Account[], transactions: Transaction[]) => {
    const withTransferAccount = R.filter(R.prop('transfer_account_uuid'));
    const reduceToAmountSumBy = R.reduceBy((acc, record) => acc + record.amount, 0);
    const sumByAccountId = reduceToAmountSumBy(R.prop('account_uuid'));
    const sumByTransferAccountId = reduceToAmountSumBy(R.prop('transfer_account_uuid'));
    return R.mergeWith(
      (a, b) => a - b,
      sumByAccountId(transactions),
      sumByTransferAccountId(withTransferAccount(transactions))
    );
  }
);

/**
 * Returns the sum of all budget items and transactions by category and by month (chronological)
 */
const sumOfBudgetItemsAndTransactionsByCategoryByMonth = createSelector(
  arraySelector(CategoryResource),
  arraySelector(BudgetItemResource),
  arraySelector(TransactionResource),
  (categories: Category[], budgetItems: BudgetItem[], transactions: Transaction[]) => {
    const result = new Map();

    // Initialize the result for each existing category.
    categories.forEach(c => result.set(c.uuid, new Map()));

    budgetItems.forEach(bi => {
      const categoryResult = result.get(bi.category_uuid) || new Map();
      categoryResult.set(bi.month, (categoryResult.get(bi.month) || 0) + bi.amount);
    });

    flattenTransactions(transactions).forEach(ft => {
      const month = beginningOfMonth(ft.date);
      const categoryResult = result.get(ft.category_uuid) || new Map();
      categoryResult.set(month, (categoryResult.get(month) || 0) + ft.amount);
    });

    // Sort the result chronologically
    const sortedResult = new Map();
    result.forEach((g, category_uuid) => {
      const sortedMonth = new Map();
      Array.from(g.keys())
        .sort()
        .forEach(m => sortedMonth.set(m, g.get(m)));
      sortedResult.set(category_uuid, sortedMonth);
    });

    return sortedResult;
  }
);

/**
 * Returns overspendings by category and by month
 */
const getOverspendingByCategoryIdByMonth = createSelector(
  sumOfBudgetItemsAndTransactionsByCategoryByMonth,
  input => {
    const overspendings = new Map();
    input.forEach((g, category_uuid) => {
      let sumAccrossMonths = 0;
      g.forEach((value, month) => {
        sumAccrossMonths += value;
        if (sumAccrossMonths < 0) {
          // Make sure there is a Map for this category.
          if (!overspendings.get(category_uuid)) {
            overspendings.set(category_uuid, new Map());
          }

          // Extract Map to make Flow understand it is not undefined
          const categoryMap = overspendings.get(category_uuid);
          if (categoryMap) {
            // Add the overspending to the Map for this category.
            categoryMap.set(month, sumAccrossMonths);
          } else {
            throw new Error('This should not happen');
          }

          sumAccrossMonths = 0;
        }
      });
    });
    return overspendings;
  }
);

// Returns for each category the amount available in the budget for that category for the month
export const getAvailableByCategoryIdForSelectedMonth = createSelector(
{'fraction_non_alphanumeric': 0.11992533831077928, 'fraction_numerical': 0.0013999066728884741, 'mean_word_length': 4.0399764844209285, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6924788', 'n_tokens_mistral': 2382, 'n_tokens_neox': 2389, 'n_words': 604}