Esempio n. 1
0
 def load(self):
     schedule = self.schedule
     txn = schedule.ref
     self._start_date = txn.date
     self._start_date_fmt = self.table.document.app.format_date(self._start_date)
     self._stop_date = schedule.stop_date
     if self._stop_date is not None:
         self._stop_date_fmt = self.table.document.app.format_date(self._stop_date)
     else:
         self._stop_date_fmt = ''
     self._repeat_type = get_repeat_type_desc(
         schedule.repeat_type, schedule.start_date)
     self._interval = str(schedule.repeat_every)
     self._description = txn.description
     self._payee = txn.payee
     self._checkno = txn.checkno
     froms, tos = splitted_splits(txn.splits)
     self._from_count = len(froms)
     self._to_count = len(tos)
     UNASSIGNED = tr('Unassigned') if len(froms) > 1 else ''
     self._from = ', '.join(s.account.name if s.account is not None else UNASSIGNED for s in froms)
     UNASSIGNED = tr('Unassigned') if len(tos) > 1 else ''
     self._to = ', '.join(s.account.name if s.account is not None else UNASSIGNED for s in tos)
     try:
         self._amount = sum(s.amount for s in tos)
     except ValueError: # currency coercing problem
         currency = self.document.default_currency
         self._amount = sum(amount_convert(s.amount, currency, s.transaction.date) for s in tos)
     self.is_amount_native = self.document.is_amount_native(self._amount)
     self._amount_fmt = self.document.format_amount(self._amount)
Esempio n. 2
0
    def change_schedule(self, schedule, new_ref, repeat_type, repeat_every, stop_date):
        """Change attributes of ``schedule``.

        ``new_ref`` is a reference transaction that the schedule is going to repeat.

        :param schedule: :class:`.Schedule`
        :param new_ref: :class:`.Transaction`
        :param repeat_type: :class:`.RepeatType`
        :param stop_date: ``datetime.date``
        """
        for split in new_ref.splits:
            if split.account is not None:
                # same as in change_transaction()
                split.account = self.accounts.find(split.account.name, split.account.type)
        if schedule in self.schedules:
            action = Action(tr('Change Schedule'))
            action.change_schedule(schedule)
        else:
            action = Action(tr('Add Schedule'))
            action.added_schedules.add(schedule)
        self._undoer.record(action)
        original = schedule.ref
        min_date = min(original.date, new_ref.date)
        original.change(
            description=new_ref.description, payee=new_ref.payee,
            checkno=new_ref.checkno, notes=new_ref.notes, splits=new_ref.splits
        )
        schedule.change(
            start_date=new_ref.date,
            stop_date=stop_date,
            repeat_type=repeat_type,
            repeat_every=repeat_every)
        if schedule not in self.schedules:
            self.schedules.append(schedule)
        self._cook(from_date=min_date)
Esempio n. 3
0
 def _refresh(self):
     self.clear()
     self.has_multiple_currencies = self.document.accounts.has_multiple_currencies()
     self.assets = self.make_type_node(tr('ASSETS'), AccountType.Asset)
     self.liabilities = self.make_type_node(tr('LIABILITIES'), AccountType.Liability)
     self.net_worth = self._make_node(tr('NET WORTH'))
     net_worth_start = self.assets.start_amount - self.liabilities.start_amount
     net_worth_end = self.assets.end_amount - self.liabilities.end_amount
     budget_date_range = DateRange(date.today(), self.document.date_range.end)
     # The net worth's budget is not a simple subtraction, it must count the target-less budgets
     net_worth_budgeted = self.document.budgeted_amount(budget_date_range)
     net_worth_delta = net_worth_end - net_worth_start
     force_explicit_currency = self.has_multiple_currencies
     self.net_worth.start = self.document.format_amount(
         net_worth_start, force_explicit_currency=force_explicit_currency
     )
     self.net_worth.end = self.document.format_amount(
         net_worth_end, force_explicit_currency=force_explicit_currency
     )
     # TODO: move this value to budget view. except for tests, this isn't
     #       used any more.
     self.net_worth.budgeted = self.document.format_amount(
         net_worth_budgeted, force_explicit_currency=force_explicit_currency
     )
     self.net_worth.delta = self.document.format_amount(
         net_worth_delta, force_explicit_currency=force_explicit_currency
     )
     self.net_worth.delta_perc = get_delta_perc(net_worth_delta, net_worth_start)
     self.net_worth.is_total = True
     self.append(self.assets)
     self.append(self.liabilities)
     self.append(self.net_worth)
Esempio n. 4
0
 def load(self):
     transaction = self.transaction
     self._load_from_fields(transaction, self.FIELDS)
     self._date_fmt = None
     self._position = transaction.position
     splits = transaction.splits
     froms, tos = splitted_splits(splits)
     self._from_count = len(froms)
     self._to_count = len(tos)
     UNASSIGNED = tr('Unassigned') if len(froms) > 1 else ''
     get_display = lambda s: s.account.combined_display if s.account is not None else UNASSIGNED
     self._from = ', '.join(map(get_display, froms))
     UNASSIGNED = tr('Unassigned') if len(tos) > 1 else ''
     get_display = lambda s: s.account.combined_display if s.account is not None else UNASSIGNED
     self._to = ', '.join(map(get_display, tos))
     self._amount = transaction.amount
     self._amount_fmt = None
     self._mtime = datetime.datetime.fromtimestamp(transaction.mtime)
     if transaction.mtime > 0:
         self._mtime_fmt = self._mtime.strftime('%Y/%m/%d %H:%M')
     else:
         self._mtime_fmt = ''
     self._recurrent = transaction.is_spawn
     self._reconciled = any(split.reconciled for split in splits)
     self._can_set_amount = transaction.can_set_amount
Esempio n. 5
0
 def load(self):
     transaction = self.transaction
     self._load_from_fields(transaction, self.FIELDS)
     self._date_fmt = None
     self._position = transaction.position
     splits = transaction.splits
     froms, tos = splitted_splits(splits)
     self._from_count = len(froms)
     self._to_count = len(tos)
     UNASSIGNED = tr('Unassigned') if len(froms) > 1 else ''
     get_display = lambda s: s.account.combined_display if s.account is not None else UNASSIGNED
     self._from = ', '.join(map(get_display, froms))
     UNASSIGNED = tr('Unassigned') if len(tos) > 1 else ''
     get_display = lambda s: s.account.combined_display if s.account is not None else UNASSIGNED
     self._to = ', '.join(map(get_display, tos))
     self._amount = transaction.amount
     self._amount_fmt = None
     self._mtime = datetime.datetime.fromtimestamp(transaction.mtime)
     if transaction.mtime > 0:
         self._mtime_fmt = self._mtime.strftime('%Y/%m/%d %H:%M')
     else:
         self._mtime_fmt = ''
     self._recurrent = transaction.is_spawn
     self._reconciled = any(split.reconciled for split in splits)
     self._is_budget = transaction.is_budget
     self._can_set_amount = transaction.can_set_amount
Esempio n. 6
0
 def _refresh(self):
     self.clear()
     self.has_multiple_currencies = self.document.accounts.has_multiple_currencies(
     )
     self.assets = self.make_type_node(tr('ASSETS'), AccountType.Asset)
     self.liabilities = self.make_type_node(tr('LIABILITIES'),
                                            AccountType.Liability)
     self.net_worth = self._make_node(tr('NET WORTH'))
     net_worth_start = self.assets.start_amount - self.liabilities.start_amount
     net_worth_end = self.assets.end_amount - self.liabilities.end_amount
     budget_date_range = DateRange(date.today(),
                                   self.document.date_range.end)
     # The net worth's budget is not a simple subtraction, it must count the target-less budgets
     net_worth_budgeted = self.document.budgeted_amount(budget_date_range)
     net_worth_delta = net_worth_end - net_worth_start
     force_explicit_currency = self.has_multiple_currencies
     self.net_worth.start = self.document.format_amount(
         net_worth_start, force_explicit_currency=force_explicit_currency)
     self.net_worth.end = self.document.format_amount(
         net_worth_end, force_explicit_currency=force_explicit_currency)
     # TODO: move this value to budget view. except for tests, this isn't
     #       used any more.
     self.net_worth.budgeted = self.document.format_amount(
         net_worth_budgeted,
         force_explicit_currency=force_explicit_currency)
     self.net_worth.delta = self.document.format_amount(
         net_worth_delta, force_explicit_currency=force_explicit_currency)
     self.net_worth.delta_perc = get_delta_perc(net_worth_delta,
                                                net_worth_start)
     self.net_worth.is_total = True
     self.append(self.assets)
     self.append(self.liabilities)
     self.append(self.net_worth)
Esempio n. 7
0
 def _refresh(self):
     self.clear()
     self.has_multiple_currencies = self.document.accounts.has_multiple_currencies()
     self.income = self.make_type_node(tr('INCOME'), AccountType.Income)
     self.expenses = self.make_type_node(tr('EXPENSES'), AccountType.Expense)
     self.net_income = self._make_node(tr('NET INCOME'))
     net_income = self.income.cash_flow_amount - self.expenses.cash_flow_amount
     last_net_income = self.income.last_cash_flow_amount - self.expenses.last_cash_flow_amount
     net_budgeted = self.income.budgeted_amount - self.expenses.budgeted_amount
     delta = net_income - last_net_income
     force_explicit_currency = self.has_multiple_currencies
     self.net_income.cash_flow = self.document.format_amount(
         net_income, force_explicit_currency=force_explicit_currency
     )
     self.net_income.last_cash_flow = self.document.format_amount(
         last_net_income, force_explicit_currency=force_explicit_currency
     )
     self.net_income.budgeted = self.document.format_amount(
         net_budgeted, force_explicit_currency=force_explicit_currency
     )
     self.net_income.delta = self.document.format_amount(
         delta, force_explicit_currency=force_explicit_currency
     )
     self.net_income.delta_perc = get_delta_perc(delta, last_net_income)
     self.net_income.is_total = True
     self.append(self.income)
     self.append(self.expenses)
     self.append(self.net_income)
Esempio n. 8
0
 def _refresh(self):
     self.clear()
     self.has_multiple_currencies = self.document.accounts.has_multiple_currencies(
     )
     self.income = self.make_type_node(tr('INCOME'), AccountType.Income)
     self.expenses = self.make_type_node(tr('EXPENSES'),
                                         AccountType.Expense)
     self.net_income = self._make_node(tr('NET INCOME'))
     net_income = self.income.cash_flow_amount - self.expenses.cash_flow_amount
     last_net_income = self.income.last_cash_flow_amount - self.expenses.last_cash_flow_amount
     net_budgeted = self.income.budgeted_amount - self.expenses.budgeted_amount
     delta = net_income - last_net_income
     force_explicit_currency = self.has_multiple_currencies
     self.net_income.cash_flow = self.document.format_amount(
         net_income, force_explicit_currency=force_explicit_currency)
     self.net_income.last_cash_flow = self.document.format_amount(
         last_net_income, force_explicit_currency=force_explicit_currency)
     self.net_income.budgeted = self.document.format_amount(
         net_budgeted, force_explicit_currency=force_explicit_currency)
     self.net_income.delta = self.document.format_amount(
         delta, force_explicit_currency=force_explicit_currency)
     self.net_income.delta_perc = get_delta_perc(delta, last_net_income)
     self.net_income.is_total = True
     self.append(self.income)
     self.append(self.expenses)
     self.append(self.net_income)
Esempio n. 9
0
 def _get_action_from_changed_transactions(self, transactions, global_scope=False):
     if len(transactions) == 1 and not transactions[0].is_spawn \
             and transactions[0] not in self.transactions:
         action = Action(tr('Add transaction'))
         action.added_transactions.add(transactions[0])
     else:
         action = Action(tr('Change transaction'))
         action.change_transactions(transactions, self.schedules)
     if global_scope:
         spawns, txns = extract(lambda x: x.is_spawn, transactions)
         action.change_transactions(spawns, self.schedules)
     return action
Esempio n. 10
0
 def _get_action_from_changed_transactions(self,
                                           transactions,
                                           global_scope=False):
     if len(transactions) == 1 and not transactions[0].is_spawn \
             and transactions[0] not in self.transactions:
         action = Action(tr('Add transaction'))
         action.added_transactions.add(transactions[0])
     else:
         action = Action(tr('Change transaction'))
         action.change_transactions(transactions, self.schedules)
     if global_scope:
         spawns, txns = extract(lambda x: x.is_spawn, transactions)
         action.change_transactions(spawns, self.schedules)
     return action
Esempio n. 11
0
    def __init__(self, mainwindow, target_account=None):
        super().__init__()
        if not hasattr(mainwindow, 'loader'):
            raise ValueError("Nothing to import!")
        self.mainwindow = mainwindow
        self.document = mainwindow.document
        self.app = self.document.app
        self._selected_pane_index = 0
        self._selected_target_index = 0

        def setfunc(index):
            self.view.set_swap_button_enabled(self.can_perform_swap())

        self.swap_type_list = LinkedSelectableList(items=[
            "<placeholder> Day <--> Month",
            "<placeholder> Month <--> Year",
            "<placeholder> Day <--> Year",
            tr("Description <--> Payee"),
            tr("Invert Amounts"),
        ],
                                                   setfunc=setfunc)
        self.swap_type_list.selected_index = SwapType.DayMonth
        self.panes = []
        self.import_table = ImportTable(self)

        self.loader = self.mainwindow.loader
        self.target_accounts = [
            a for a in self.document.accounts if a.is_balance_sheet_account()
        ]
        self.target_accounts.sort(key=lambda a: a.name.lower())
        accounts = []
        for account in self.loader.accounts:
            if account.is_balance_sheet_account():
                entries = self.loader.accounts.entries_for_account(account)
                if len(entries):
                    new_name = self.document.accounts.new_name(account.name)
                    if new_name != account.name:
                        self.loader.accounts.rename_account(account, new_name)
                    accounts.append(account)
        parsing_date_format = DateFormat.from_sysformat(
            self.loader.parsing_date_format)
        for account in accounts:
            target = target_account
            if target is None and account.reference:
                target = getfirst(t for t in self.target_accounts
                                  if t.reference == account.reference)
            self.panes.append(
                AccountPane(self, account, target, parsing_date_format))
Esempio n. 12
0
    def parse_file_for_import(self, filename):
        """Parses ``filename`` in preparation for importing.

        Opens and parses ``filename`` and try to determine its format by successively trying to read
        is as a moneyGuru file, an OFX, a QIF and finally a CSV. Once parsed, take the appropriate
        action for the file which is either to show the CSV options window or to call
        :meth:`load_parsed_file_for_import`.
        """
        default_date_format = DateFormat(self.app.date_format).sys_format
        for loaderclass in (native.Loader, ofx.Loader, qif.Loader, csv.Loader):
            try:
                loader = loaderclass(self.document.default_currency,
                                     default_date_format=default_date_format)
                loader.parse(filename)
                break
            except FileFormatError:
                pass
        else:
            # No file fitted
            raise FileFormatError(tr('%s is of an unknown format.') % filename)
        self.loader = loader
        if isinstance(self.loader, csv.Loader):
            panel = CSVOptions(self)
            panel.view = weakref.proxy(self.view.get_panel_view(panel))
            panel.show()
            return panel
        else:
            return self.load_parsed_file_for_import()
Esempio n. 13
0
    def toggle_entries_reconciled(self, entries):
        """Toggle the reconcile flag of `entries`.

        Sets the ``reconciliation_date`` to entries' date, or unset it when turning the flag off.

        :param entries: list of :class:`.Entry`
        """
        if not entries:
            return
        all_reconciled = not entries or all(entry.reconciled for entry in entries)
        newvalue = not all_reconciled
        action = Action(tr('Change reconciliation'))
        action.change_entries(entries, self.schedules)
        min_date = min(entry.date for entry in entries)
        spawns, entries = extract(lambda e: e.transaction.is_spawn, entries)
        action.change_transactions({e.transaction for e in spawns}, self.schedules)
        # spawns have to be processed before the action's recording, but
        # record() has to be called before we change the entries. This is why
        # we have this rather convulted code.
        if newvalue:
            for spawn in spawns:
                # XXX update transaction selection
                newtxn, newsplit = self._reconcile_spawn_split(
                    spawn, spawn.transaction.date)
                action.added_transactions.add(newtxn)
        self._undoer.record(action)
        if newvalue:
            for entry in entries:
                entry.split.reconciliation_date = entry.transaction.date
        else:
            for entry in entries:
                entry.split.reconciliation_date = None
        self._cook(from_date=min_date)
Esempio n. 14
0
    def delete_transactions(self, transactions, from_account=None):
        """Removes every transaction in ``transactions`` from the document.

        Adds undo recording, global scope querying, date range adjustments and UI triggers.

        :param transactions: a collection of :class:`.Transaction`.
        :param from_account: the :class:`.Account` from which the operation takes place, if any.
        """
        action = Action(tr('Remove transaction'))
        spawns, txns = extract(lambda x: x.is_spawn, transactions)
        global_scope = self._query_for_scope_if_needed(spawns)
        action.change_transactions(spawns, self.schedules)
        action.deleted_transactions |= set(txns)
        self._undoer.record(action)
        for txn in transactions:
            if txn.is_spawn:
                schedule = find_schedule_of_ref(txn.ref, self.schedules)
                assert schedule is not None
                if global_scope:
                    schedule.stop_before(txn)
                else:
                    schedule.delete(txn)
            else:
                self.transactions.remove(txn)
        min_date = min(t.date for t in transactions)
        self._cook(from_date=min_date)
        self.accounts.clean_empty_categories(from_account)
Esempio n. 15
0
 def save_edits(self):
     node = self.edited
     if node is None:
         return
     self.edited = None
     assert node.is_account or node.is_group
     if node.is_account:
         success = self.document.change_accounts([node.account], name=node.name)
     else:
         other = node.parent.find(
             lambda n: n.is_group and n.name.lower() == node.name.lower())
         success = other is None or other is node
         if success:
             accounts = self.document.accounts.filter(
                 groupname=node.oldname, type=node.parent.type)
             if accounts:
                 self.document.change_accounts(accounts, groupname=node.name)
             else:
                 self.document.touch()
             self.document.newgroups.discard((node.oldname, node.parent.type))
             self.document.newgroups.add((node.name, node.parent.type))
     self.mainwindow.revalidate()
     if not success:
         msg = tr("The account '{0}' already exists.").format(node.name)
         # we use _name because we don't want to change self.edited
         node._name = node.account.name if node.is_account else node.oldname
         self.mainwindow.show_message(msg)
Esempio n. 16
0
    def delete_transactions(self, transactions, from_account=None):
        """Removes every transaction in ``transactions`` from the document.

        Adds undo recording, global scope querying, date range adjustments and UI triggers.

        :param transactions: a collection of :class:`.Transaction`.
        :param from_account: the :class:`.Account` from which the operation takes place, if any.
        """
        action = Action(tr('Remove transaction'))
        spawns, txns = extract(lambda x: x.is_spawn, transactions)
        global_scope = self._query_for_scope_if_needed(spawns)
        action.change_transactions(spawns, self.schedules)
        action.deleted_transactions |= set(txns)
        self._undoer.record(action)
        # to avoid sweeping twn refs from under the rug of other spawns, we
        # perform schedule deletions at the end of the loop.
        schedule_deletions = []
        for txn in transactions:
            if txn.is_spawn:
                schedule = find_schedule_of_spawn(txn, self.schedules)
                assert schedule is not None
                if global_scope:
                    schedule.change(stop_date=txn.recurrence_date -
                                    datetime.timedelta(1))
                else:
                    schedule_deletions.append((schedule, txn.recurrence_date))
            else:
                self.transactions.remove(txn)
        for schedule, recurrence_date in schedule_deletions:
            schedule.delete_at(recurrence_date)
        min_date = min(t.date for t in transactions)
        self._cook(from_date=min_date)
        self.accounts.clean_empty_categories(from_account)
Esempio n. 17
0
class GeneralLedgerView(TransactionViewBase):
    VIEW_TYPE = PaneType.GeneralLedger
    PRINT_TITLE_FORMAT = tr('General Ledger from {start_date} to {end_date}')
    PRINT_VIEW_CLASS = EntryPrint

    def __init__(self, mainwindow):
        TransactionViewBase.__init__(self, mainwindow)
        self.gltable = self.table = GeneralLedgerTable(parent_view=self)
        self.maintable = self.gltable
        self.columns = self.maintable.columns
        self.restore_subviews_size()

    # --- Overrides
    def _refresh_totals(self):
        selected, total, total_debit, total_credit = self.gltable.get_totals()
        total_debit_fmt = self.document.format_amount(total_debit)
        total_credit_fmt = self.document.format_amount(total_credit)
        msg = tr("{0} out of {1} selected. Debit: {2} Credit: {3}")
        self.status_line = msg.format(selected, total, total_debit_fmt, total_credit_fmt)

    def _revalidate(self):
        self.gltable._revalidate()

    def save_preferences(self):
        self.gltable.columns.save_columns()

    def show(self):
        TransactionViewBase.show(self)
        self.gltable.show()
        self._refresh_totals()

    def update_transaction_selection(self, transactions):
        self._refresh_totals()
Esempio n. 18
0
 def __init__(self, table, account, date, total_debit, total_credit):
     super(TotalRow, self).__init__(table, account)
     self._date = date
     self._description = tr('TOTAL')
     # don't touch _increase and _decrease, they trigger editing.
     self._debit_fmt = table.document.format_amount(total_debit,
                                                    blank_zero=True)
     self._credit_fmt = table.document.format_amount(total_credit,
                                                     blank_zero=True)
     delta = total_debit - total_credit
     if delta:
         if account.is_credit_account():
             delta *= -1
         positive = delta > 0
         # format_amount doesn't explicitly put positive signs, so we have to put it ourselves.
         # However, if the delta is of foreign currency, we want the sign to be in front of the
         # amount, not in front of the currency code.
         delta_fmt = table.document.format_amount(abs(delta))
         sign = '+' if positive else '-'
         if delta_fmt[0].isdigit():
             delta_fmt = sign + delta_fmt
         else:
             # we have a currency code in front of our amount, a little trick is to replace the
             # only space character we have by space + sign
             delta_fmt = delta_fmt.replace(' ', ' ' + sign)
         self._balance_fmt = delta_fmt
     else:
         self._balance_fmt = ''
     self.is_bold = True
Esempio n. 19
0
    def parse_file_for_import(self, filename):
        """Parses ``filename`` in preparation for importing.

        Opens and parses ``filename`` and try to determine its format by successively trying to read
        is as a moneyGuru file, an OFX, a QIF and finally a CSV. Once parsed, take the appropriate
        action for the file which is either to show the CSV options window or to call
        :meth:`load_parsed_file_for_import`.
        """
        default_date_format = DateFormat(self.app.date_format).sys_format
        for loaderclass in (native.Loader, ofx.Loader, qif.Loader, csv.Loader):
            try:
                loader = loaderclass(
                    self.document.default_currency, default_date_format=default_date_format
                )
                loader.parse(filename)
                break
            except FileFormatError:
                pass
        else:
            # No file fitted
            raise FileFormatError(tr('%s is of an unknown format.') % filename)
        self.loader = loader
        if isinstance(self.loader, csv.Loader):
            panel = CSVOptions(self)
            panel.view = weakref.proxy(self.view.get_panel_view(panel))
            panel.show()
            return panel
        else:
            return self.load_parsed_file_for_import()
Esempio n. 20
0
    def load_from_xml(self, filename):
        """Clears the document and loads data from ``filename``.

        ``filename`` must be a path to a moneyGuru XML document.

        :param filename: ``str``
        """
        loader = native.Loader(self.default_currency)
        try:
            loader.parse(filename)
        except FileFormatError:
            raise FileFormatError(tr('"%s" is not a moneyGuru file') % filename)
        loader.load()
        self.clear()
        self._document_id = loader.document_id
        for propname in self._properties:
            if propname in loader.properties:
                self._properties[propname] = loader.properties[propname]
        self.accounts = loader.accounts
        self.oven._accounts = self.accounts
        self._undoer._accounts = self.accounts
        for transaction in loader.transactions:
            self.transactions.add(transaction, True)
        for recurrence in loader.schedules:
            self.schedules.append(recurrence)
        self.accounts.default_currency = self.default_currency
        self._cook()
        self._undoer.set_save_point()
        self._restore_preferences_after_load()
Esempio n. 21
0
class ScheduleView(BaseView):
    VIEW_TYPE = PaneType.Schedule
    PRINT_TITLE_FORMAT = tr('Schedules from {start_date} to {end_date}')

    def __init__(self, mainwindow):
        super().__init__(mainwindow)
        self.table = ScheduleTable(self)
        self.columns = self.table.columns
        self.restore_subviews_size()

    def _revalidate(self):
        self.table.refresh_and_show_selection()

    # --- Override
    def save_preferences(self):
        self.table.columns.save_columns()

    # --- Public
    def new_item(self):
        schedule_panel = SchedulePanel(self.mainwindow)
        schedule_panel.view = weakref.proxy(
            self.view.get_panel_view(schedule_panel))
        schedule_panel.new()
        return schedule_panel

    def edit_item(self):
        schedule_panel = SchedulePanel(self.mainwindow)
        schedule_panel.view = weakref.proxy(
            self.view.get_panel_view(schedule_panel))
        schedule_panel.load()
        return schedule_panel

    def delete_item(self):
        self.table.delete()
Esempio n. 22
0
    def change_transaction(self, original, new):
        """Changes the attributes of ``original`` so that they match those of ``new``.

        Adds undo recording, global scope querying, date range adjustments and UI triggers.

        :param original: :class:`.Transaction`
        :param new: :class:`.Transaction`, a modified copy of ``original``.
        """
        global_scope = self._query_for_scope_if_needed([original])
        action = Action(tr('Change transaction'))
        action.change_transactions([original], self.schedules)
        self._undoer.record(action)
        # don't forget that account up here is an external instance. Even if an account of
        # the same name exists in self.accounts, it's not gonna be the same instance.
        for split in new.splits:
            if split.account is not None:
                split.account = self.accounts.find(split.account.name,
                                                   split.account.type)
        original.change(splits=new.splits)
        min_date = min(original.date, new.date)
        self._change_transaction(original,
                                 date=new.date,
                                 description=new.description,
                                 payee=new.payee,
                                 checkno=new.checkno,
                                 notes=new.notes,
                                 global_scope=global_scope)
        self._cook(from_date=min_date)
        self.accounts.clean_empty_categories()
        self.date_range = self.date_range.around(original.date)
Esempio n. 23
0
    def delete_accounts(self, accounts, reassign_to=None):
        """Removes ``accounts`` from the document.

        If the account has entries assigned to it, these entries will be reassigned to the
        ``reassign_to`` account.

        :param accounts: List of :class:`.Account` to be removed.
        :param accounts: :class:`.Account` to use for reassignment.
        """
        # Recording the "Remove account" action into the Undoer is quite something...
        action = Action(tr('Remove account'))
        accounts = set(accounts)
        action.deleted_accounts |= accounts
        all_entries = flatten(self.accounts.entries_for_account(a) for a in accounts)
        if reassign_to is None:
            transactions = {e.transaction for e in all_entries if not e.transaction.is_spawn}
            transactions = {t for t in transactions if not t.affected_accounts() - accounts}
            action.deleted_transactions |= transactions
        action.change_entries(all_entries, self.schedules)
        affected_schedules = [s for s in self.schedules if accounts & s.affected_accounts()]
        for schedule in affected_schedules:
            action.change_schedule(schedule)
        self._undoer.record(action)
        for account in accounts:
            self.transactions.reassign_account(account, reassign_to)
            for schedule in affected_schedules:
                schedule.reassign_account(account, reassign_to)
            self.accounts.remove(account)
        self._cook()
Esempio n. 24
0
    def toggle_entries_reconciled(self, entries):
        """Toggle the reconcile flag of `entries`.

        Sets the ``reconciliation_date`` to entries' date, or unset it when turning the flag off.

        :param entries: list of :class:`.Entry`
        """
        if not entries:
            return
        all_reconciled = not entries or all(entry.reconciled
                                            for entry in entries)
        newvalue = not all_reconciled
        action = Action(tr('Change reconciliation'))
        action.change_entries(entries, self.schedules)
        min_date = min(entry.date for entry in entries)
        spawns, entries = extract(lambda e: e.transaction.is_spawn, entries)
        action.change_transactions({e.transaction
                                    for e in spawns}, self.schedules)
        # spawns have to be processed before the action's recording, but
        # record() has to be called before we change the entries. This is why
        # we have this rather convulted code.
        if newvalue:
            for spawn in spawns:
                # XXX update transaction selection
                newtxn, newsplit = self._reconcile_spawn_split(
                    spawn, spawn.transaction.date)
                action.added_transactions.add(newtxn)
        self._undoer.record(action)
        if newvalue:
            for entry in entries:
                entry.split.reconciliation_date = entry.transaction.date
        else:
            for entry in entries:
                entry.split.reconciliation_date = None
        self._cook(from_date=min_date)
Esempio n. 25
0
    def delete_transactions(self, transactions, from_account=None):
        """Removes every transaction in ``transactions`` from the document.

        Adds undo recording, global scope querying, date range adjustments and UI triggers.

        :param transactions: a collection of :class:`.Transaction`.
        :param from_account: the :class:`.Account` from which the operation takes place, if any.
        """
        action = Action(tr('Remove transaction'))
        spawns, txns = extract(lambda x: x.is_spawn, transactions)
        global_scope = self._query_for_scope_if_needed(spawns)
        action.change_transactions(spawns, self.schedules)
        action.deleted_transactions |= set(txns)
        self._undoer.record(action)
        for txn in transactions:
            if txn.is_spawn:
                schedule = find_schedule_of_ref(txn.ref, self.schedules)
                assert schedule is not None
                if global_scope:
                    schedule.stop_before(txn)
                else:
                    schedule.delete(txn)
            else:
                self.transactions.remove(txn)
        min_date = min(t.date for t in transactions)
        self._cook(from_date=min_date)
        self.accounts.clean_empty_categories(from_account)
Esempio n. 26
0
    def delete_transactions(self, transactions, from_account=None):
        """Removes every transaction in ``transactions`` from the document.

        Adds undo recording, global scope querying, date range adjustments and UI triggers.

        :param transactions: a collection of :class:`.Transaction`.
        :param from_account: the :class:`.Account` from which the operation takes place, if any.
        """
        action = Action(tr('Remove transaction'))
        spawns, txns = extract(lambda x: x.is_spawn, transactions)
        global_scope = self._query_for_scope_if_needed(spawns)
        action.change_transactions(spawns, self.schedules)
        action.deleted_transactions |= set(txns)
        self._undoer.record(action)
        # to avoid sweeping twn refs from under the rug of other spawns, we
        # perform schedule deletions at the end of the loop.
        schedule_deletions = []
        for txn in transactions:
            if txn.is_spawn:
                schedule = find_schedule_of_spawn(txn, self.schedules)
                assert schedule is not None
                if global_scope:
                    schedule.change(stop_date=txn.recurrence_date - datetime.timedelta(1))
                else:
                    schedule_deletions.append((schedule, txn.recurrence_date))
            else:
                self.transactions.remove(txn)
        for schedule, recurrence_date in schedule_deletions:
            schedule.delete_at(recurrence_date)
        min_date = min(t.date for t in transactions)
        self._cook(from_date=min_date)
        self.accounts.clean_empty_categories(from_account)
Esempio n. 27
0
    def change_transaction(self, original, new):
        """Changes the attributes of ``original`` so that they match those of ``new``.

        Adds undo recording, global scope querying, date range adjustments and UI triggers.

        :param original: :class:`.Transaction`
        :param new: :class:`.Transaction`, a modified copy of ``original``.
        """
        global_scope = self._query_for_scope_if_needed([original])
        action = Action(tr('Change transaction'))
        action.change_transactions([original], self.schedules)
        self._undoer.record(action)
        # don't forget that account up here is an external instance. Even if an account of
        # the same name exists in self.accounts, it's not gonna be the same instance.
        for split in new.splits:
            if split.account is not None:
                split.account = self.accounts.find(split.account.name, split.account.type)
        original.change(splits=new.splits)
        min_date = min(original.date, new.date)
        self._change_transaction(
            original, date=new.date, description=new.description,
            payee=new.payee, checkno=new.checkno, notes=new.notes, global_scope=global_scope
        )
        self._cook(from_date=min_date)
        self.accounts.clean_empty_categories()
        self.date_range = self.date_range.around(original.date)
Esempio n. 28
0
    def load_from_xml(self, filename):
        """Clears the document and loads data from ``filename``.

        ``filename`` must be a path to a moneyGuru XML document.

        :param filename: ``str``
        """
        loader = native.Loader(self.default_currency)
        try:
            loader.parse(filename)
        except FileFormatError:
            raise FileFormatError(
                tr('"%s" is not a moneyGuru file') % filename)
        loader.load()
        self.clear()
        self._document_id = loader.document_id
        for propname in self._properties:
            if propname in loader.properties:
                self._properties[propname] = loader.properties[propname]
        self.accounts = loader.accounts
        self.oven._accounts = self.accounts
        self._undoer._accounts = self.accounts
        for transaction in loader.transactions:
            self.transactions.add(transaction, True)
        for recurrence in loader.schedules:
            self.schedules.append(recurrence)
        self.budgets.start_date = loader.budgets.start_date
        self.budgets.repeat_type = loader.budgets.repeat_type
        self.budgets.repeat_every = loader.budgets.repeat_every
        for budget in loader.budgets:
            self.budgets.append(budget)
        self.accounts.default_currency = self.default_currency
        self._cook()
        self._undoer.set_save_point()
        self._restore_preferences_after_load()
Esempio n. 29
0
 def __init__(self, table, account, date, total_debit, total_credit):
     super(TotalRow, self).__init__(table, account)
     self._date = date
     self._description = tr('TOTAL')
     # don't touch _increase and _decrease, they trigger editing.
     self._debit_fmt = table.document.format_amount(total_debit, blank_zero=True)
     self._credit_fmt = table.document.format_amount(total_credit, blank_zero=True)
     delta = total_debit - total_credit
     if delta:
         if account.is_credit_account():
             delta *= -1
         positive = delta > 0
         # format_amount doesn't explicitly put positive signs, so we have to put it ourselves.
         # However, if the delta is of foreign currency, we want the sign to be in front of the
         # amount, not in front of the currency code.
         delta_fmt = table.document.format_amount(abs(delta))
         sign = '+' if positive else '-'
         if delta_fmt[0].isdigit():
             delta_fmt = sign + delta_fmt
         else:
             # we have a currency code in front of our amount, a little trick is to replace the
             # only space character we have by space + sign
             delta_fmt = delta_fmt.replace(' ', ' ' + sign)
         self._balance_fmt = delta_fmt
     else:
         self._balance_fmt = ''
     self.is_bold = True
Esempio n. 30
0
 def __init__(self, table, date, balance, reconciled_balance, account):
     super(PreviousBalanceRow, self).__init__(table, account)
     self._date = date
     self._balance = balance
     self._reconciled_balance = reconciled_balance
     self._description = tr('Previous Balance')
     self._reconciled = False
     self.is_bold = True
Esempio n. 31
0
 def __init__(self, table, date, balance, reconciled_balance, account):
     super(PreviousBalanceRow, self).__init__(table, account)
     self._date = date
     self._balance = balance
     self._reconciled_balance = reconciled_balance
     self._description = tr('Previous Balance')
     self._reconciled = False
     self.is_bold = True
Esempio n. 32
0
def parse_amount(string, currency, **kwargs):
    try:
        return amount_parse(string, currency, with_expression=False, **kwargs)
    except UnsupportedCurrencyError:
        msg = tr(
            "Unsupported currency: {}. Aborting load. Did you disable a currency plugin?"
        ).format(currency)
        raise FileFormatError(msg)
Esempio n. 33
0
 def show(self):
     self._default_layout = Layout(tr('Default'))
     self.layout = self._default_layout
     self._refresh_columns()
     self._refresh_lines()
     self._refresh_targets()
     self.view.refresh_layout_menu()
     self.view.show()
Esempio n. 34
0
 def _refresh_totals(self):
     selected = len(self.mainwindow.selected_transactions)
     total = len(self.visible_transactions)
     currency = self.document.default_currency
     total_amount = sum(amount_convert(t.amount, currency, t.date) for t in self.mainwindow.selected_transactions)
     total_amount_fmt = self.document.format_amount(total_amount)
     msg = tr("{0} out of {1} selected. Amount: {2}")
     self.status_line = msg.format(selected, total, total_amount_fmt)
Esempio n. 35
0
 def show(self):
     self._default_layout = Layout(tr('Default'))
     self.layout = self._default_layout
     self._refresh_columns()
     self._refresh_lines()
     self._refresh_targets()
     self.view.refresh_layout_menu()
     self.view.show()
Esempio n. 36
0
    def __init__(self, mainwindow, target_account=None):
        super().__init__()
        if not hasattr(mainwindow, 'loader'):
            raise ValueError("Nothing to import!")
        self.mainwindow = mainwindow
        self.document = mainwindow.document
        self.app = self.document.app
        self._selected_pane_index = 0
        self._selected_target_index = 0

        def setfunc(index):
            self.view.set_swap_button_enabled(self.can_perform_swap())
        self.swap_type_list = LinkedSelectableList(items=[
            "<placeholder> Day <--> Month",
            "<placeholder> Month <--> Year",
            "<placeholder> Day <--> Year",
            tr("Description <--> Payee"),
            tr("Invert Amounts"),
        ], setfunc=setfunc)
        self.swap_type_list.selected_index = SwapType.DayMonth
        self.panes = []
        self.import_table = ImportTable(self)

        self.loader = self.mainwindow.loader
        self.target_accounts = [
            a for a in self.document.accounts if a.is_balance_sheet_account()]
        self.target_accounts.sort(key=lambda a: a.name.lower())
        accounts = []
        for account in self.loader.accounts:
            if account.is_balance_sheet_account():
                entries = self.loader.accounts.entries_for_account(account)
                if len(entries):
                    new_name = self.document.accounts.new_name(account.name)
                    if new_name != account.name:
                        self.loader.accounts.rename_account(account, new_name)
                    accounts.append(account)
        parsing_date_format = DateFormat.from_sysformat(self.loader.parsing_date_format)
        for account in accounts:
            target = target_account
            if target is None and account.reference:
                target = getfirst(
                    t for t in self.target_accounts if t.reference == account.reference
                )
            self.panes.append(
                AccountPane(self, account, target, parsing_date_format))
Esempio n. 37
0
    def change_accounts(self,
                        accounts,
                        name=NOEDIT,
                        type=NOEDIT,
                        currency=NOEDIT,
                        groupname=NOEDIT,
                        account_number=NOEDIT,
                        inactive=NOEDIT,
                        notes=NOEDIT):
        """Properly sets properties for ``accounts``.

        Sets ``accounts``' properties in a proper manner. Attributes
        corresponding to arguments set to ``NOEDIT`` will not be touched.

        :param accounts: List of :class:`.Account` to be changed.
        :param name: ``str``
        :param type: :class:`.AccountType`
        :param currency: :class:`.Currency`
        :param groupname: ``str``
        :param account_number: ``str``
        :param inactive: ``bool``
        :param notes: ``str``
        """
        assert all(a is not None for a in accounts)
        # Check for name clash
        for account in accounts:
            if name is not NOEDIT and name:
                other = self.accounts.find(name)
                if (other is not None) and (other != account):
                    return False
        action = Action(tr('Change account'))
        action.change_accounts(accounts)
        self._undoer.record(action)
        for account in accounts:
            kwargs = {}
            if name is not NOEDIT and name:
                self.accounts.rename_account(account, name.strip())
            if (type is not NOEDIT) and (type != account.type):
                kwargs.update({'type': type, 'groupname': None})
            if currency is not NOEDIT:
                entries = self.accounts.entries_for_account(account)
                assert not any(e.reconciled for e in entries)
                kwargs['currency'] = currency
            if groupname is not NOEDIT:
                kwargs['groupname'] = groupname
            if account_number is not NOEDIT:
                kwargs['account_number'] = account_number
            if inactive is not NOEDIT:
                kwargs['inactive'] = inactive
            if notes is not NOEDIT:
                kwargs['notes'] = notes
            if kwargs:
                account.change(**kwargs)
        self._cook()
        self.transactions.clear_cache()
        return True
Esempio n. 38
0
    def delete_accounts(self, accounts, reassign_to=None):
        """Removes ``accounts`` from the document.

        If the account has entries assigned to it, these entries will be reassigned to the
        ``reassign_to`` account.

        :param accounts: List of :class:`.Account` to be removed.
        :param accounts: :class:`.Account` to use for reassignment.
        """
        # Recording the "Remove account" action into the Undoer is quite something...
        action = Action(tr('Remove account'))
        accounts = set(accounts)
        action.deleted_accounts |= accounts
        all_entries = flatten(
            self.accounts.entries_for_account(a) for a in accounts)
        if reassign_to is None:
            transactions = {
                e.transaction
                for e in all_entries if not e.transaction.is_spawn
            }
            transactions = {
                t
                for t in transactions if not t.affected_accounts() - accounts
            }
            action.deleted_transactions |= transactions
        action.change_entries(all_entries, self.schedules)
        affected_schedules = [
            s for s in self.schedules if accounts & s.affected_accounts()
        ]
        for schedule in affected_schedules:
            action.change_schedule(schedule)
        for account in accounts:
            affected_budgets = [
                b for b in self.budgets
                if b.account == account or b.target == account
            ]
            if account.is_income_statement_account() and reassign_to is None:
                action.deleted_budgets |= set(affected_budgets)
            else:
                for budget in affected_budgets:
                    action.change_budget(budget)
        self._undoer.record(action)
        for account in accounts:
            self.transactions.reassign_account(account, reassign_to)
            for schedule in affected_schedules:
                schedule.reassign_account(account, reassign_to)
            for budget in affected_budgets:
                if budget.account == account:
                    if reassign_to is None:
                        self.budgets.remove(budget)
                    else:
                        budget.account = reassign_to
                elif budget.target == account:
                    budget.target = reassign_to
            self.accounts.remove(account)
        self._cook()
Esempio n. 39
0
 def _check_amount_values(self, lines, ci):
     for line in lines:
         for attr in [CsvField.Amount, CsvField.Increase, CsvField.Decrease]:
             if attr not in ci:
                 continue
             index = ci[attr]
             value = line[index]
             try:
                 base.parse_amount(value, self.default_currency)
             except ValueError:
                 raise FileLoadError(tr("The Amount column has been set on a column that doesn't contain amounts."))
Esempio n. 40
0
 def _load_budget(self, budget):
     if budget is None:
         raise OperationAborted
     self.original = budget
     self.budget = budget.replicate()
     self._accounts = [a for a in self.document.accounts if a.is_income_statement_account()]
     if not self._accounts:
         msg = tr("Income/Expense accounts must be created before budgets can be set.")
         raise OperationAborted(msg)
     self._accounts.sort(key=ACCOUNT_SORT_KEY)
     self.account_list.refresh()
     self.account_list.select(self._accounts.index(budget.account) if budget.account is not None else 0)
Esempio n. 41
0
class ProfitView(AccountSheetView):
    SAVENAME = 'ProfitView'
    VIEW_TYPE = PaneType.Profit
    PRINT_TITLE_FORMAT = tr('Profit and Loss from {start_date} to {end_date}')

    def __init__(self, mainwindow):
        AccountSheetView.__init__(self, mainwindow)
        self.sheet = self.istatement = IncomeStatement(self)
        self.columns = self.istatement.columns
        self.graph = self.pgraph = ProfitGraph(self)
        self.pie = CashFlowPieChart(self)
        self.restore_subviews_size()
Esempio n. 42
0
    def new_account(self, type, groupname):
        """Create a new account in the document.

        Creates a new account of type ``type``, within the ``group`` (which can be ``None`` to
        indicate no group). The new account will have a unique name based on the string
        "New Account" (if it exists already, a unique number will be appended to it). Once created,
        the account is added to the account list.

        :param type: :class:`.AccountType`
        :param group: :class:`.Group`
        :rtype: :class:`.Account`
        """
        name = self.accounts.new_name(tr('New account'))
        account = self.accounts.create(name, self.default_currency, type)
        if groupname:
            account.change(groupname=groupname)
        action = Action(tr('Add account'))
        action.added_accounts.add(account)
        self._undoer.record(action)
        self.touch()
        return account
Esempio n. 43
0
 def _load(self, accounts):
     self.deleted_accounts = set(accounts)
     all_accounts = list(self.document.accounts)
     target_accounts = sorted(
         (a for a in all_accounts if a not in self.deleted_accounts),
         key=ACCOUNT_SORT_KEY)
     target_account_names = [a.name for a in target_accounts]
     target_account_names.insert(0, tr('No Account'))
     self._target_accounts = target_accounts
     self._target_accounts.insert(0, None)
     self.account_list[:] = target_account_names
     self.account_list.select(0)
Esempio n. 44
0
 def _load(self, accounts):
     self.deleted_accounts = set(accounts)
     all_accounts = list(self.document.accounts)
     target_accounts = sorted(
         (a for a in all_accounts if a not in self.deleted_accounts),
         key=ACCOUNT_SORT_KEY)
     target_account_names = [a.name for a in target_accounts]
     target_account_names.insert(0, tr('No Account'))
     self._target_accounts = target_accounts
     self._target_accounts.insert(0, None)
     self.account_list[:] = target_account_names
     self.account_list.select(0)
Esempio n. 45
0
    def new_account(self, type, groupname):
        """Create a new account in the document.

        Creates a new account of type ``type``, within the ``group`` (which can be ``None`` to
        indicate no group). The new account will have a unique name based on the string
        "New Account" (if it exists already, a unique number will be appended to it). Once created,
        the account is added to the account list.

        :param type: :class:`.AccountType`
        :param group: :class:`.Group`
        :rtype: :class:`.Account`
        """
        name = self.accounts.new_name(tr('New account'))
        account = self.accounts.create(name, self.default_currency, type)
        if groupname:
            account.change(groupname=groupname)
        action = Action(tr('Add account'))
        action.added_accounts.add(account)
        self._undoer.record(action)
        self.touch()
        return account
Esempio n. 46
0
 def materialize_spawn(self, spawn):
     assert spawn.is_spawn
     schedule = find_schedule_of_ref(spawn.ref, self.schedules)
     assert schedule is not None
     action = Action(tr('Materialize transaction'))
     action.change_schedule(schedule)
     schedule.delete(spawn)
     materialized = spawn.materialize()
     action.added_transactions |= {materialized}
     self._undoer.record(action)
     self.transactions.add(materialized)
     self._cook(from_date=materialized.date)
Esempio n. 47
0
 def materialize_spawn(self, spawn):
     assert spawn.is_spawn
     schedule = find_schedule_of_spawn(spawn, self.schedules)
     assert schedule is not None
     action = Action(tr('Materialize transaction'))
     action.change_schedule(schedule)
     materialized = spawn.materialize()
     action.added_transactions |= {materialized}
     self._undoer.record(action)
     schedule.delete_at(spawn.recurrence_date)
     self.transactions.add(materialized)
     self._cook(from_date=materialized.date)
Esempio n. 48
0
 def make_group_node(self, groupname, grouptype):
     node = self._make_node(groupname)
     node.is_group = True
     node.oldname = groupname # in case we rename
     accounts = self.document.accounts.filter(
         groupname=groupname, type=grouptype)
     for account in sorted(accounts, key=ACCOUNT_SORT_KEY):
         node.append(self.make_account_node(account))
     node.is_excluded = bool(accounts) and set(accounts) <= self.document.excluded_accounts # all accounts excluded
     if not node.is_excluded:
         node.append(self.make_total_node(node, tr('Total ') + groupname))
     node.append(self.make_blank_node())
     return node
Esempio n. 49
0
class NetWorthView(AccountSheetView):
    SAVENAME = 'NetWorthView'
    VIEW_TYPE = PaneType.NetWorth
    PRINT_TITLE_FORMAT = tr(
        "Net Worth at {end_date}, starting from {start_date}")

    def __init__(self, mainwindow):
        AccountSheetView.__init__(self, mainwindow)
        self.sheet = self.bsheet = BalanceSheet(self)
        self.columns = self.bsheet.columns
        self.graph = self.nwgraph = NetWorthGraph(self)
        self.pie = BalancePieChart(self)
        self.restore_subviews_size()
Esempio n. 50
0
    def delete_budgets(self, budgets):
        """Removes ``budgets`` from the document.

        :param budgets: list of :class:`.Budget`
        """
        if not budgets:
            return
        action = Action(tr('Remove Budget'))
        action.deleted_budgets |= set(budgets)
        self._undoer.record(action)
        for budget in budgets:
            self.budgets.remove(budget)
        self._cook(from_date=self.budgets.start_date)
Esempio n. 51
0
    def delete_budgets(self, budgets):
        """Removes ``budgets`` from the document.

        :param budgets: list of :class:`.Budget`
        """
        if not budgets:
            return
        action = Action(tr('Remove Budget'))
        action.deleted_budgets |= set(budgets)
        self._undoer.record(action)
        for budget in budgets:
            self.budgets.remove(budget)
        self._cook(from_date=self.budgets.start_date)
Esempio n. 52
0
    def change_accounts(
            self, accounts, name=NOEDIT, type=NOEDIT, currency=NOEDIT,
            groupname=NOEDIT, account_number=NOEDIT, inactive=NOEDIT,
            notes=NOEDIT):
        """Properly sets properties for ``accounts``.

        Sets ``accounts``' properties in a proper manner. Attributes
        corresponding to arguments set to ``NOEDIT`` will not be touched.

        :param accounts: List of :class:`.Account` to be changed.
        :param name: ``str``
        :param type: :class:`.AccountType`
        :param currency: :class:`.Currency`
        :param groupname: ``str``
        :param account_number: ``str``
        :param inactive: ``bool``
        :param notes: ``str``
        """
        assert all(a is not None for a in accounts)
        # Check for name clash
        for account in accounts:
            if name is not NOEDIT and name:
                other = self.accounts.find(name)
                if (other is not None) and (other != account):
                    return False
        action = Action(tr('Change account'))
        action.change_accounts(accounts)
        self._undoer.record(action)
        for account in accounts:
            kwargs = {}
            if name is not NOEDIT and name:
                self.accounts.rename_account(account, name.strip())
            if (type is not NOEDIT) and (type != account.type):
                kwargs.update({'type': type, 'groupname': None})
            if currency is not NOEDIT:
                entries = self.accounts.entries_for_account(account)
                assert not any(e.reconciled for e in entries)
                kwargs['currency'] = currency
            if groupname is not NOEDIT:
                kwargs['groupname'] = groupname
            if account_number is not NOEDIT:
                kwargs['account_number'] = account_number
            if inactive is not NOEDIT:
                kwargs['inactive'] = inactive
            if notes is not NOEDIT:
                kwargs['notes'] = notes
            if kwargs:
                account.change(**kwargs)
        self._cook()
        self.transactions.clear_cache()
        return True
Esempio n. 53
0
    def change_schedule(self, schedule, new_ref, repeat_type, repeat_every,
                        stop_date):
        """Change attributes of ``schedule``.

        ``new_ref`` is a reference transaction that the schedule is going to repeat.

        :param schedule: :class:`.Schedule`
        :param new_ref: :class:`.Transaction`
        :param repeat_type: :class:`.RepeatType`
        :param stop_date: ``datetime.date``
        """
        for split in new_ref.splits:
            if split.account is not None:
                # same as in change_transaction()
                split.account = self.accounts.find(split.account.name,
                                                   split.account.type)
        if schedule in self.schedules:
            action = Action(tr('Change Schedule'))
            action.change_schedule(schedule)
        else:
            action = Action(tr('Add Schedule'))
            action.added_schedules.add(schedule)
        self._undoer.record(action)
        original = schedule.ref
        min_date = min(original.date, new_ref.date)
        original.change(description=new_ref.description,
                        payee=new_ref.payee,
                        checkno=new_ref.checkno,
                        notes=new_ref.notes,
                        splits=new_ref.splits)
        schedule.start_date = new_ref.date
        schedule.repeat_type = repeat_type
        schedule.repeat_every = repeat_every
        schedule.stop_date = stop_date
        schedule.reset_spawn_cache()
        if schedule not in self.schedules:
            self.schedules.append(schedule)
        self._cook(from_date=min_date)
Esempio n. 54
0
 def compute_pie_data(self, data):
     data = [(name, float(amount)) for name, amount in data.items() if amount > 0]
     data.sort(key=lambda t: t[1], reverse=True)
     data = [(name, amount, i % (COLOR_COUNT-1)) for i, (name, amount) in enumerate(data)]
     if len(data) > self.slice_count:
         others = data[self.slice_count-1:]
         others_total = sum(amount for _, amount, _ in others)
         del data[self.slice_count-1:]
         data.append((tr('Others'), others_total, COLOR_COUNT-1))
     total = sum(amount for _, amount, _ in data)
     if not total:
         return None
     fmt = lambda name, amount: '%s %1.1f%%' % (name, amount / total * 100)
     return [(fmt(name, amount), amount, color) for name, amount, color in data]
Esempio n. 55
0
    def duplicate_transactions(self, transactions):
        """Create copies of ``transactions`` in the document.

        Adds undo recording and UI triggers.

        :param transactions: a collection of :class:`.Transaction` to duplicate.
        """
        if not transactions:
            return
        action = Action(tr('Duplicate transactions'))
        duplicated = [txn.replicate() for txn in transactions]
        action.added_transactions |= set(duplicated)
        self._undoer.record(action)
        self._add_transactions(duplicated)
Esempio n. 56
0
    def delete_schedules(self, schedules):
        """Removes ``schedules`` from the document.

        :param schedules: list of :class:`.Schedule`
        """
        if not schedules:
            return
        action = Action(tr('Remove Schedule'))
        action.deleted_schedules |= set(schedules)
        self._undoer.record(action)
        for schedule in schedules:
            self.schedules.remove(schedule)
        min_date = min(s.ref.date for s in schedules)
        self._cook(from_date=min_date)