def is_mct(self):
     """*readonly*. ``bool``. Whether our splits contain more than one currency."""
     splits_with_amount = (s for s in self.splits if s.amount != 0)
     try:
         return not allsame(s.amount.currency for s in splits_with_amount)
     except ValueError: # no split with amount
         return False
Example #2
0
 def is_mct(self):
     """*readonly*. ``bool``. Whether our splits contain more than one currency."""
     splits_with_amount = (s for s in self.splits if s.amount != 0)
     try:
         return not allsame(s.amount.currency for s in splits_with_amount)
     except ValueError:  # no split with amount
         return False
Example #3
0
 def balance(self, strong_split=None, keep_two_splits=False):
     # strong_split is the split that was last edited.
     # keep_two_splits is a flag that, if enabled and that a strong_split is defined, causes the
     # balance process to set the weak split to the invert value of the strong one so that we
     # don't end up with a third unassigned split.
     
     # When the flag is false and for the special case where there is 2 splits on the same
     # "side" and a strong split, we reverse the weak split.
     if len(self.splits) == 2 and strong_split is not None:
         weak_split = self.splits[0] if self.splits[0] is not strong_split else self.splits[1]
         if keep_two_splits:
             weak_split.amount = -strong_split.amount
         elif (weak_split.amount > 0) == (strong_split.amount > 0): # on the same side
             weak_split.amount *= -1
     splits_with_amount = [s for s in self.splits if s.amount]
     if splits_with_amount and not allsame(s.amount.currency for s in splits_with_amount):
         self.balance_currencies(strong_split)
         return
     imbalance = sum(s.amount for s in self.splits)
     if not imbalance:
         return
     is_unassigned = lambda s: s.account is None and s is not strong_split
     imbalance = sum(s.amount for s in self.splits)
     if imbalance:
         unassigned = first(s for s in self.splits if is_unassigned(s))
         if unassigned is not None:
             unassigned.amount -= imbalance
         else:
             self.splits.append(Split(self, None, -imbalance))
     for split in self.splits[:]:
         if is_unassigned(split) and split.amount == 0:
             self.splits.remove(split)
Example #4
0
    def balance(self, strong_split=None, keep_two_splits=False):
        """Balance out :attr:`splits` if needed.

        A balanced transaction has all its splits making a zero sum. Balancing a transaction is
        rather easy: We sum all our splits and create an unassigned split of the opposite of that
        amount. To avoid polluting our splits, we look if we already have an unassigned split and,
        if we do, we adjust its amount instead of creating a new split.

        There's a special case to that rule, and that is when we have two splits. When those two
        splits are on the same "side" (both positive or both negative), we assume that the user has
        just reversed ``strong_split``'s side and that the logical next step is to also reverse the
        other split (the "weak" split), which we'll do.

        If ``keep_two_splits`` is true, we'll go one step further and adjust the weak split's amount
        to fit what was just entered in the strong split. If it's false, we'll create an unassigned
        split if needed.

        Easy, right? Things get a bit more complicated when a have a
        :ref:`multi-currency transaction <multi-currency-txn>`. When that happens, we do a more
        complicated balancing, which happens in :meth:`balance_currencies`.

        :param strong_split: The split that was last edited. The reason why we're balancing the
                             transaction now. If set, it will not be adjusted by the balancing
                             because we don't want to pull the rug from under our user's feet and
                             undo an edit he's just made.
        :type strong_split: :class:`Split`
        :param bool keep_two_splits: If set and if we have a two-split transaction, we'll keep it
                                     that way, adjusting the "weak" split amount as needed.
        """
        if len(self.splits) == 2 and strong_split is not None:
            weak_split = self.splits[0] if self.splits[
                0] is not strong_split else self.splits[1]
            if keep_two_splits:
                weak_split.amount = -strong_split.amount
            elif (weak_split.amount > 0) == (strong_split.amount >
                                             0):  # on the same side
                weak_split.amount *= -1
        splits_with_amount = [s for s in self.splits if s.amount]
        if splits_with_amount and not allsame(s.amount.currency
                                              for s in splits_with_amount):
            self.balance_currencies(strong_split)
            return
        imbalance = sum(s.amount for s in self.splits)
        if not imbalance:
            return
        is_unassigned = lambda s: s.account is None and s is not strong_split
        imbalance = sum(s.amount for s in self.splits)
        if imbalance:
            unassigned = first(s for s in self.splits if is_unassigned(s))
            if unassigned is not None:
                unassigned.amount -= imbalance
            else:
                self.splits.append(Split(self, None, -imbalance))
        for split in self.splits[:]:
            if is_unassigned(split) and split.amount == 0:
                self.splits.remove(split)
Example #5
0
 def can_move_transactions(self, transactions, before, after):
     assert transactions
     if any(isinstance(txn, Spawn) for txn in transactions):
         return False
     if not allsame(txn.date for txn in transactions):
         return False
     from_date = transactions[0].date
     before_date = before.date if before else None
     after_date = after.date if after else None
     return from_date in (before_date, after_date)
Example #6
0
 def toggle_selected_mark_state(self):
     selected = self.without_ref(self.selected_dupes)
     if not selected:
         return
     if allsame(self.results.is_marked(d) for d in selected):
         markfunc = self.results.mark_toggle
     else:
         markfunc = self.results.mark
     for dupe in selected:
         markfunc(dupe)
     self.notify('marking_changed')
Example #7
0
 def toggle_selected_mark_state(self):
     selected = self.without_ref(self.selected_dupes)
     if not selected:
         return
     if allsame(self.results.is_marked(d) for d in selected):
         markfunc = self.results.mark_toggle
     else:
         markfunc = self.results.mark
     for dupe in selected:
         markfunc(dupe)
     self.notify('marking_changed')
    def balance(self, strong_split=None, keep_two_splits=False):
        """Balance out :attr:`splits` if needed.

        A balanced transaction has all its splits making a zero sum. Balancing a transaction is
        rather easy: We sum all our splits and create an unassigned split of the opposite of that
        amount. To avoid polluting our splits, we look if we already have an unassigned split and,
        if we do, we adjust its amount instead of creating a new split.

        There's a special case to that rule, and that is when we have two splits. When those two
        splits are on the same "side" (both positive or both negative), we assume that the user has
        just reversed ``strong_split``'s side and that the logical next step is to also reverse the
        other split (the "weak" split), which we'll do.

        If ``keep_two_splits`` is true, we'll go one step further and adjust the weak split's amount
        to fit what was just entered in the strong split. If it's false, we'll create an unassigned
        split if needed.

        Easy, right? Things get a bit more complicated when a have a
        :ref:`multi-currency transaction <multi-currency-txn>`. When that happens, we do a more
        complicated balancing, which happens in :meth:`balance_currencies`.

        :param strong_split: The split that was last edited. The reason why we're balancing the
                             transaction now. If set, it will not be adjusted by the balancing
                             because we don't want to pull the rug from under our user's feet and
                             undo an edit he's just made.
        :type strong_split: :class:`Split`
        :param bool keep_two_splits: If set and if we have a two-split transaction, we'll keep it
                                     that way, adjusting the "weak" split amount as needed.
        """
        if len(self.splits) == 2 and strong_split is not None:
            weak_split = self.splits[0] if self.splits[0] is not strong_split else self.splits[1]
            if keep_two_splits:
                weak_split.amount = -strong_split.amount
            elif (weak_split.amount > 0) == (strong_split.amount > 0): # on the same side
                weak_split.amount *= -1
        splits_with_amount = [s for s in self.splits if s.amount]
        if splits_with_amount and not allsame(s.amount.currency for s in splits_with_amount):
            self.balance_currencies(strong_split)
            return
        imbalance = sum(s.amount for s in self.splits)
        if not imbalance:
            return
        is_unassigned = lambda s: s.account is None and s is not strong_split
        imbalance = sum(s.amount for s in self.splits)
        if imbalance:
            unassigned = first(s for s in self.splits if is_unassigned(s))
            if unassigned is not None:
                unassigned.amount -= imbalance
            else:
                self.splits.append(Split(self, None, -imbalance))
        for split in self.splits[:]:
            if is_unassigned(split) and split.amount == 0:
                self.splits.remove(split)
 def _load(self):
     transactions = self.mainwindow.selected_transactions
     if len(transactions) < 2:
         raise OperationAborted()
     self.can_change_accounts = all(len(t.splits) == 2 for t in transactions)
     self.can_change_amount = all(t.can_set_amount for t in transactions)
     self.date_field.value = date.today()
     self.description_field.text = ''
     self.payee_field.text = ''
     self.checkno_field.text = ''
     self.from_field.text = ''
     self.to_field.text = ''
     self.amount_field.value = 0
     self.currency = None
     first = transactions[0]
     if allsame(t.date for t in transactions):
         self.date_field.value = first.date
     if allsame(t.description for t in transactions):
         self.description_field.text = first.description
     if allsame(t.payee for t in transactions):
         self.payee_field.text = first.payee
     if allsame(t.checkno for t in transactions):
         self.checkno_field.text = first.checkno
     splits = flatten(t.splits for t in transactions)
     splits = [s for s in splits if s.amount]
     if splits and allsame(s.amount.currency for s in splits):
         self.currency = splits[0].amount.currency
     else:
         self.currency = self.document.default_currency
     self.currency_list.select(Currency.all.index(self.currency))
     if self.can_change_accounts:
         def get_from(t):
             s1, s2 = t.splits
             return s1 if s1.amount <=0 else s2
     
         def get_to(t):
             s1, s2 = t.splits
             return s2 if s1.amount <=0 else s1
     
         def get_name(split):
             return split.account.name if split.account is not None else ''
     
         if allsame(get_name(get_from(t)) for t in transactions):
             self.from_field.text = get_name(get_from(first))
         if allsame(get_name(get_to(t)) for t in transactions):
             self.to_field.text = get_name(get_to(first))
     if self.can_change_amount:
         if allsame(t.amount for t in transactions):
             self.amount_field.value = first.amount
     self._init_checkboxes()
Example #10
0
    def _load(self, transactions):
        assert len(transactions) >= 2
        self.can_change_accounts = all(
            len(t.splits) == 2 for t in transactions)
        self.can_change_amount = all(t.can_set_amount for t in transactions)
        self.date_field.value = date.today()
        self.description_field.text = ''
        self.payee_field.text = ''
        self.checkno_field.text = ''
        self.from_field.text = ''
        self.to_field.text = ''
        self.amount_field.value = 0
        self.currency = None
        first = transactions[0]
        if allsame(t.date for t in transactions):
            self.date_field.value = first.date
        if allsame(t.description for t in transactions):
            self.description_field.text = first.description
        if allsame(t.payee for t in transactions):
            self.payee_field.text = first.payee
        if allsame(t.checkno for t in transactions):
            self.checkno_field.text = first.checkno
        splits = flatten(t.splits for t in transactions)
        splits = [s for s in splits if s.amount]
        if splits and allsame(s.amount.currency for s in splits):
            self.currency = splits[0].amount.currency
        else:
            self.currency = self.document.default_currency
        self.currency_list.select(Currency.all.index(self.currency))
        if self.can_change_accounts:

            def get_from(t):
                s1, s2 = t.splits
                return s1 if s1.amount <= 0 else s2

            def get_to(t):
                s1, s2 = t.splits
                return s2 if s1.amount <= 0 else s1

            def get_name(split):
                return split.account.name if split.account is not None else ''

            if allsame(get_name(get_from(t)) for t in transactions):
                self.from_field.text = get_name(get_from(first))
            if allsame(get_name(get_to(t)) for t in transactions):
                self.to_field.text = get_name(get_to(first))
        if self.can_change_amount:
            if allsame(t.amount for t in transactions):
                self.amount_field.value = first.amount
        self._init_checkboxes()
Example #11
0
    def balance_currencies(self, strong_split=None):
        """Balances a :ref:`multi-currency transaction <multi-currency-txn>`.

        Balancing out multi-currencies transasctions can be real easy because we consider that
        currencies can never mix (and we would never make the gross mistake of using market exchange
        rates to do our balancing), so, if we have at least one split on each side of different
        currencies, we consider ourselves balanced and do nothing.

        However, we might be in a situation of "logical imbalance", which means that the transaction
        doesn't logically makes sense. For example, if all our splits are on the same side, we can't
        possibly balance out. If we have EUR and CAD splits, that CAD splits themselves balance out
        but that EUR splits are all on the same side, we have a logical imbalance.

        This method finds those imbalance and fix them by creating unsassigned splits balancing out
        every currency being in that situation.

        :param strong_split: The split that was last edited. See :meth:`balance`.
        :type strong_split: :class:`Split`
        """
        splits_with_amount = [s for s in self.splits if s.amount != 0]
        if not splits_with_amount:
            return
        currency2balance = defaultdict(int)
        for split in splits_with_amount:
            currency2balance[split.amount.currency] += split.amount
        imbalanced = stripfalse(currency2balance.values()
                                )  # filters out zeros (balances currencies)
        # For a logical imbalance to be possible, all imbalanced amounts must be on the same side
        if imbalanced and allsame(amount > 0 for amount in imbalanced):
            unassigned = [
                s for s in self.splits
                if s.account is None and s is not strong_split
            ]
            for amount in imbalanced:
                split = first(
                    s for s in unassigned
                    if s.amount == 0 or s.amount.currency == amount.currency)
                if split is not None:
                    if split.amount == amount:  # we end up with a null split, remove it
                        self.splits.remove(split)
                    else:
                        split.amount -= amount  # adjust
                else:
                    self.splits.append(Split(self, None, -amount))
Example #12
0
 def balance_currencies(self, strong_split=None):
     splits_with_amount = [s for s in self.splits if s.amount != 0]
     if not splits_with_amount:
         return
     currency2balance = defaultdict(int)
     for split in splits_with_amount:
         currency2balance[split.amount.currency] += split.amount
     imbalanced = stripfalse(currency2balance.values()) # filters out zeros (balances currencies)
     # For a logical imbalance to be possible, all imbalanced amounts must be on the same side
     if imbalanced and allsame(amount > 0 for amount in imbalanced):
         unassigned = [s for s in self.splits if s.account is None and s is not strong_split]
         for amount in imbalanced:
             split = first(s for s in unassigned if s.amount == 0 or s.amount.currency == amount.currency)
             if split is not None:
                 if split.amount == amount: # we end up with a null split, remove it
                     self.splits.remove(split)
                 else:
                     split.amount -= amount # adjust
             else:
                 self.splits.append(Split(self, None, -amount))
Example #13
0
    def balance_currencies(self, strong_split=None):
        """Balances a :ref:`multi-currency transaction <multi-currency-txn>`.

        Balancing out multi-currencies transasctions can be real easy because we consider that
        currencies can never mix (and we would never make the gross mistake of using market exchange
        rates to do our balancing), so, if we have at least one split on each side of different
        currencies, we consider ourselves balanced and do nothing.

        However, we might be in a situation of "logical imbalance", which means that the transaction
        doesn't logically makes sense. For example, if all our splits are on the same side, we can't
        possibly balance out. If we have EUR and CAD splits, that CAD splits themselves balance out
        but that EUR splits are all on the same side, we have a logical imbalance.

        This method finds those imbalance and fix them by creating unsassigned splits balancing out
        every currency being in that situation.

        :param strong_split: The split that was last edited. See :meth:`balance`.
        :type strong_split: :class:`Split`
        """
        splits_with_amount = [s for s in self.splits if s.amount != 0]
        if not splits_with_amount:
            return
        currency2balance = defaultdict(int)
        for split in splits_with_amount:
            currency2balance[split.amount.currency] += split.amount
        imbalanced = stripfalse(currency2balance.values()) # filters out zeros (balances currencies)
        # For a logical imbalance to be possible, all imbalanced amounts must be on the same side
        if imbalanced and allsame(amount > 0 for amount in imbalanced):
            unassigned = [s for s in self.splits if s.account is None and s is not strong_split]
            for amount in imbalanced:
                split = first(s for s in unassigned if s.amount == 0 or s.amount.currency == amount.currency)
                if split is not None:
                    if split.amount == amount: # we end up with a null split, remove it
                        self.splits.remove(split)
                    else:
                        split.amount -= amount # adjust
                else:
                    self.splits.append(Split(self, None, -amount))
Example #14
0
 def is_mct(self):
     splits_with_amount = (s for s in self.splits if s.amount != 0)
     try:
         return not allsame(s.amount.currency for s in splits_with_amount)
     except ValueError: # no split with amount
         return False