Author Topic: Bug with "undeletable" split transaction  (Read 1377 times)

Offline jat255

Bug with "undeletable" split transaction
« on: July 25, 2019, 03:05:54 PM »
I think I've come across a bug in my budget...

I do split transactions for my paychecks. One of these splits is a transfer to an off-budget retirement account. I have two transactions in the retirement account that are listed as transfers to my checking account, but neither one has a matching split in the checking account. I cannot delete these transactions from my retirement account because it says "Cannot delete transfer of split transaction. This transfer is from a split transaction. To remove it, modify the split transaction this transfer originates from."

Is there a "super delete" option? I'm not sure how I ended up in this state.

Offline asromzek

Re: Bug with "undeletable" split transaction
« Reply #1 on: July 25, 2019, 04:03:01 PM »
@jat255

Was the split transaction entered using the mobile app? I remember having some issues with transfers in splits, and I'm almost certain the main app had the same issue. It's on my list of things to revisit. I remember that refreshing the browser sometimes would fix things after entering a split that contained a transfer. Have you tried that?
/s implied, unless stated otherwise.

Offline jat255

Re: Bug with "undeletable" split transaction
« Reply #2 on: July 25, 2019, 04:07:21 PM »
I think it came about as a result of some code that I have to automatically enter my paychecks that I added using https://github.com/jat255/python-financier/

Using GET requests, I can see that there are no splits defined for that transaction, but I'm wondering if some other deleted transaction is referencing it and that's why the front-end is getting messed up. I can use PUTs to change the memo and set the value to 0, but I'm getting a 409 conflict response when I try to issue a DELETE on the document.

Offline asromzek

Re: Bug with "undeletable" split transaction
« Reply #3 on: July 25, 2019, 04:10:39 PM »
What happens if you use a PUT request to set "_deleted = true"?
/s implied, unless stated otherwise.

Offline jat255

Re: Bug with "undeletable" split transaction
« Reply #4 on: July 25, 2019, 04:13:57 PM »
It worked! My savior :)

Offline asromzek

Re: Bug with "undeletable" split transaction
« Reply #5 on: July 25, 2019, 04:18:35 PM »
Awesome, I'll have to check out that python code. Are you automating transaction import, or using for quick manual entries?
/s implied, unless stated otherwise.

Offline jat255

Re: Bug with "undeletable" split transaction
« Reply #6 on: July 25, 2019, 04:50:37 PM »
I didn't originally write it (it's a fork of https://github.com/higoramp/python-financier), but I added some capabilities with regards to splits and transfers. I structure my bi-weekly paycheck as a split transaction with subtractions for all the deductions (healthcare, insurance, etc.) so I have a record of the gross wages. Since that doesn't typically change from week to week, I wrote a method that automates paycheck entry, including two transfers into a retirement account. It is not on the github (since it has private information), but I've been using it for about a year now without issues (until this issue popped up... not sure where it came from...).

Here's a sanitized version of what I'm using (this method goes in pythonfinancier/financier.py):

Code: [Select]
    def add_paycheck(self, account_name):
            """
            Add an instance of a paycheck to an account.

            Parameters
            ----------
            account_name : str
                Name of the account to use

            Returns
            -------
                JSON response of the database upon inserting the transaction
            """
            from datetime import datetime

            this_id = str(uuid.uuid4())
            memo = "2019 pay period ## paycheck"
            date = datetime.now().strftime('%Y-%m-%d')
            this_retire1_uuid = str(uuid.uuid4())
            this_retire2_uuid = str(uuid.uuid4())

            other_retire1_uuid = str(uuid.uuid4())
            other_retire2_uuid = str(uuid.uuid4())

            # getting account from either map or database
            account_id = self.find_account(account_name)['_id']
            retire_name = 'retirement account name'

            # getting payee or creating a new one
            payee_id = self.get_or_create_payee("my workplace")['_id']

            id_transaction = self.get_id_transaction(this_id)
            tr = self.get_transaction(id_transaction)

            net_value = 9999.99 * 100
            retire1_val = 888.88 * 100
            retire2_val = 777.77 * 100

            transactions = [{'value': -111.11 * 100,
                             'payee_name': '',
                             'category_name': 'incomeNextMonth',
                             'memo': '#split1',
                             'id': str(uuid.uuid4())},
                            {'value': -1 * retire1_val,
                             'payee_name': '',
                             'category_name': 'incomeNextMonth',
                             'memo': '#split2',
                             'transfer': other_retire1_uuid,
                             'id': this_retire1_uuid},
                            {'value': -1 * retire2_val,
                             'payee_name': '',
                             'category_name': 'incomeNextMonth',
                             'transfer': other_retire2_uuid,
                             'memo': '#split3',
                             'id': this_retire2_uuid},
                            {'value': -111.11 * 100,
                             'payee_name': '',
                             'category_name': 'incomeNextMonth',
                             'memo': '#split4',
                             'id': str(uuid.uuid4())},
                            {'value': -111.11 * 100,
                             'payee_name': '',
                             'category_name': 'incomeNextMonth',
                             'memo': '#split5',
                             'id': str(uuid.uuid4())},
                            {'value': -111.11 * 100,
                             'payee_name': '',
                             'category_name': 'incomeNextMonth',
                             'memo': '#split6',
                             'id': str(uuid.uuid4())},
                            {'value': -11.11 * 100,
                             'payee_name': '',
                             'category_name': 'incomeNextMonth',
                             'memo': '#split7',
                             'id': str(uuid.uuid4())},
                            {'value': -11.11 * 100,
                             'payee_name': '',
                             'category_name': 'incomeNextMonth',
                             'memo': '#split8',
                             'id': str(uuid.uuid4())},
                            {'value': -11.11 * 100,
                             'payee_name': '',
                             'category_name': 'incomeNextMonth',
                             'memo': '#split9',
                             'id': str(uuid.uuid4())},
                            {'value': -11.11 * 100,
                             'payee_name': '',
                             'category_name': 'incomeNextMonth',
                             'memo': '#split10',
                             'id': str(uuid.uuid4())},
                            {'value': 99999 * 100,
                             'payee_name': '',
                             'category_name': 'incomeNextMonth',
                             'memo': '#gross_wages',
                             'id': str(uuid.uuid4())}]

            for i, t in enumerate(transactions):
                t['category'] = self.find_category(t.pop('category_name'))['_id']
                if t['payee_name'] is not '':
                    t['payee'] = self.get_or_create_payee(t.pop('payee_name'))['_id']
                else:
                    # null payee
                    pass
                transactions\[i\] = t

            if not tr or '_id' not in tr:
                # category is "split"
                doc = {'_id': id_transaction, 'value': value,
                       'account': account_id,
                       'payee': payee_id, 'date': date,
                       'category': 'split', 'memo': memo,
                       'splits': transactions}
                self.logger.debug('Adding', doc)

                if '_rev' in tr:
                    doc['_rev'] = tr['_rev']
                self.logger.debug(
                    'importing transaction {0}'.format(doc['_id']))

                added_doc =  self.cdb.save(self.user_db, doc)

                inserted = self.find_transaction(memo=memo, date=date)[0][
                    'splits']

                self.save_transaction(account_name=retire_name,
                                      category_name=None,
                                      value=retire1_val,
                                      date=date,
                                      payee_name=None,
                                      memo='#retirement',
                                      id=other_retire1_uuid,
                                      transfer_id=this_retire1_uuid)

                self.save_transaction(account_name=retire_name,
                                      category_name=None,
                                      value=retire2_val,
                                      date=date,
                                      payee_name=None,
                                      memo='#retirement2',
                                      id=other_retire2_uuid,
                                      transfer_id=this_retire2_uuid)

                return added_doc
            else:
                self.logger.warning(
                    'transaction {0} has already been imported '.format(
                        tr['_id']))

So I suppose it's mostly manual still, but it would not be too much work to write a small extension to take the output of a ofx file or something and batch upload transactions.