Clearing ParentTxn tags in jython

davidcullen's Avatar

davidcullen

16 Oct, 2020 08:14 AM

It’s been a while since I tinkered with md jython. I seem to be doing things incorrectly, and any guidance would be appreciated. I don’t know java very much, only python.
I sometimes manually add a keyword to a ParentTxn in error - I only want a tag in relevant splits.
I’ve written a little jython to detect these and programatically move the tags to each split and delete the parent tags. This is my code:

# """All tags deleted from ParentTxn and added to related Split(s)."""
txns = [t for t in txnset if isinstance(t, ParentTxn) and t.getKeywords()]
len = len(txns)
for txn in txns:  # cycle through Parents with tag(s)
    parent_tags = txn.getKeywords()  # ArrayList(unicode)
    txn.setKeywords([])  # clear Parent's tags
    for i in range(txn.getSplitCount()):
        this_split = txn.getSplit(i)
        split_tags = this_split.getKeywords()
        for tag in parent_tags:
            Misc.addNoDuplicates(split_tags, tag)  # add to existing split tags
            this_split.setKeywords(split_tags)
    txn.syncItem()
if txns:
    print "{0} ParentTxn Keywords moved to SplitTxns.”.format(len)
This all works correctly in updating the split tags EXCEPT that instead of nothing as ParentTxn tags, the ParentTxn now shows a tag of “private” (one of my other tags). I don’t know why “private” is being selected.
What am I doing wrong?
  1. 1 Posted by Stuart Beesley on 16 Oct, 2020 12:08 PM

    Stuart Beesley's Avatar

    Nice script! - I just played with this and it seems that the parent does not hold tag data - it’s always empty. On a txn with no split and a tag there is actually 1 split and this holds the txn and the tag. Given this, I don’t see how your parents can hold tags. I just tried to create this in MD and it won’t let me. So I cannot actually do what you are suggesting. Do you have an old version of MD? Perhaps the logic changed….

    Regards

    Stuart B
    [a fellow user]

  2. 2 Posted by Stuart Beesley on 16 Oct, 2020 04:00 PM

    Stuart Beesley's Avatar

    In fact I just ran a script over my entire DB and I did in fact find (only) 1 record with a parent tag (dated 2016). When I duplicate it and/or edit it, then the parent tag remains. However, if I rekey the txn exactly the same from scratch then I cannot get parent tag to populate. It always goes in split(0) tags... If you right click a txn in MD and choose show record details, you will see the raw data and where the tags are. So I do now believe the functionality changed and you can no longer set a parent (or master record) tag, only tags against the splits going forward.... ??

    Stuart
    [a fellow user]

  3. 3 Posted by Stuart Beesley on 16 Oct, 2020 04:03 PM

    Stuart Beesley's Avatar

    PS - thanks for the Python List Comprehension example ("txns = [t for t in txnset if isinstance(t, ParentTxn) and t.getKeywords()]"). Whilst I guessed what this did, I had to learn the syntax.. Nice coding!

    If you think I'm wrong about the parent tags, let me know?

    Stuart
    [a fellow user]

  4. 4 Posted by davidcullen on 17 Oct, 2020 01:36 AM

    davidcullen's Avatar

    Stuart,
    Thanks for taking the time to respond. I'll play around a little further. The script was working about six month's ago.
    I heartily recommend exploring List Comprehensions further. They can really make coding read a lot cleaner (but they are not always easier to read than normal loops).

  5. 5 Posted by Stuart Beesley on 17 Oct, 2020 06:03 AM

    Stuart Beesley's Avatar

    Let me know what you find. I’m interested in the answer too..

    Regards Stuart

    On 17 Oct 2020, at 02:36, davidcullen <[email blocked]> wrote:

    

  6. 6 Posted by Stuart Beesley on 17 Oct, 2020 10:46 AM

    Stuart Beesley's Avatar

    Hi, an update……

    It seems that you are correct. You *CAN* store tags against the parent, but not when actually viewing the parent txn in MD. The parent transaction in MD will only show the tags of its splits (odd but true)! To see the parent’s tags, you have to view the other side (the split) and then these Txns in MD show you the parent’s tags (or sometimes their own tags shown in []s, but these disappear when you go to type). So if I visit a split and then set the tag I am now actually setting the parent’s tags… And this means I can test your script!!

    OK, so you script fails for me..

    When I run it, getKeywords() is returning one of these <type 'java.util.Collections$UnmodifiableRandomAccessList’>
    So when Misc.addNoDuplicates() runs it crashes, with an error (I assume it can’t modify an unmodifiable list).
    So I changed the script as per below.. This worked for me. It stored the parent’s tags in the split’s tags (appended) and it blanked the parent’s tags to nothing..

    In fact I’ve tried all sorts of changing and blanking the parent’s tags and it works fine…

    Have you right-clicked the record to view the Txn details (raw data) and see what’s showing there?


    txns = [t for t in txnset if isinstance(t, ParentTxn) and t.getKeywords()]

    for x in txns:
        print x
    lenx = len(txns)
    for txn in txns: # cycle through Parents with tag(s)
        myPT=[]
        parent_tags = txn.getKeywords() # ArrayList(unicode)
        for pt in parent_tags:
            myPT.append(pt)
        txn.setKeywords([]) # clear Parent's tags
        for i in range(txn.getSplitCount()):
            this_split = txn.getSplit(i)
            split_tags = this_split.getKeywords()
            myST=[]
            for st in split_tags:
                myST.append(st)
            for tag in myPT:
                Misc.addNoDuplicates(myST, tag) # add to existing split tags
                this_split.setKeywords(myST)
        txn.syncItem()
    if txns:
        print "{0} ParentTxn Keywords moved to SplitTxns.".format(lenx)

    Interested to know what you find..?
    \

    On 16 Oct 2020, at 13:07, Stuart Beesley <[email blocked]> wrote:

    Nice script! - I just played with this and it seems that the parent does not hold tag data - it’s always empty. On a txn with no split and a tag there is actually 1 split and this holds the txn and the tag. Given this, I don’t see how your parents can hold tags. I just tried to create this in MD and it won’t let me. So I cannot actually do what you are suggesting. Do you have an old version of MD? Perhaps the logic changed….

    Regards

    Stuart B
    [a fellow user]

  7. 7 Posted by Stuart Beesley on 17 Oct, 2020 10:13 PM

    Stuart Beesley's Avatar

    An addition from Sean: “getKeys() used to return an unmodifiable list, but people got into trouble with that because they'd add an item to the list expecting it to be saved and it wouldn't, or they would add something to the list expecting it not to be saved (as in a copy) and it might be. I've forgotten which one was the original problem, but either way returning an unmodifiable list resolves the ambiguity”.

    This makes me think you are on an older version of MD? Perhaps try your script on 2020.1? (Not the beta preview 2021.0)?

  8. 8 Posted by davidcullen on 18 Oct, 2020 06:43 AM

    davidcullen's Avatar

    I am using md 2019.3 (1880). I will install md 2020 and try again. However, I'll be away a few days so may not post results till I get back.

  9. 9 Posted by davidcullen on 19 Oct, 2020 06:41 AM

    davidcullen's Avatar

    Stuart,
    This is getting very strange. I may be doing something wrong (or not doing something I should). I haven’t tried this programming much since my recent stroke (recovering well) so forgive me if I overlooked something obvious.
    Anyway, I have now installed md 2020.2(1929). I haven't yet tried to programmatically “transfer” parent tags to splits (my ultimate goal), but I have tried the following code to create transactions:

    from com.infinitekind.moneydance.model import ParentTxn
    from com.infinitekind.moneydance.model import SplitTxn
    root = moneydance.getCurrentAccount()  # Account
    book = moneydance.getCurrentAccountBook()  # AccountBook
    txnset = book.getTransactionSet()  # TransactionSet
    -- make new txns
    acc1 = root.getAccountByName("AUD cash")
    acc2 = root.getAccountByName("CFT")
    -- 1
    txn = ParentTxn(book) 
    txn.setDescription("Example Transaction 1") 
    txn.setDateInt(20201019) 
    txn.setAccount(acc1)
    txnSplit = SplitTxn(txn) 
    txnSplit.setAmount(100)
    txnSplit.setParentAmount(1.0, -100) 
    txnSplit.setAccount(acc2)
    txnSplit.setDescription("Split Test")
    txnSplit.setKeywords(["splitTag"])
    txn.addSplit(txnSplit)
    txn.syncItem()
    -- 2
    txn = ParentTxn(book) 
    txn.setDescription("Example Transaction 2") 
    txn.setDateInt(20201019) 
    txn.setAccount(acc1)
    txn.setKeywords(["parentTag"])
    txnSplit = SplitTxn(txn) 
    txnSplit.setAmount(100)
    txnSplit.setParentAmount(1.0, -100) 
    txnSplit.setAccount(acc2)
    txnSplit.setDescription("Split Test")
    txn.addSplit(txnSplit)
    txn.syncItem()
    -- 3
    txn = ParentTxn(book) 
    txn.setDescription("Example Transaction 3") 
    txn.setDateInt(20201019) 
    txn.setAccount(acc1)
    txn.setKeywords(["parentTag"])
    txnSplit = SplitTxn(txn) 
    txnSplit.setAmount(100)
    txnSplit.setParentAmount(1.0, -100) 
    txnSplit.setAccount(acc2)
    txnSplit.setDescription("Split Test")
    txnSplit.setKeywords(["splitTag"])
    txn.addSplit(txnSplit)
    txn.syncItem()
    
    This works (eventually). When first run, md shows the new 3 txns, but each shows a tax date of 00/00/0000 although, when I right click the new txn in md, it prompts me (each one separately) to Save the Transaction - and when I do so, the tax date of 00/00/0000 disappears!
    The 3 new txns show in md as (1) only a split keyword showing in the usual way. (2) a parent txn with keyword (showing [keyword] when viewing the split txn), and (3) both parent and split keywords (with the appropriate one showing when viewing the parent txn or right clicking Show Other Side to view the split.
    Any insights you can give would be appreciated.
    I'll keep exploring further....
  10. 10 Posted by davidcullen on 19 Oct, 2020 07:08 AM

    davidcullen's Avatar

    When I run the following code to find the parent txns with keywords, it correctly finds two txns (Example Transaction 2 & Example Transaction 3 in the previous examples).

    from com.infinitekind.moneydance.model import ParentTxn
    from com.infinitekind.moneydance.model import SplitTxn
    root = moneydance.getCurrentAccount()  # Account
    book = moneydance.getCurrentAccountBook()  # AccountBook
    txnset = book.getTransactionSet()  # TransactionSet
    txns = [t for t in txnset if isinstance(t, ParentTxn) and t.getKeywords()]
    len = len(txns)
    print(len)
    for t in txns:
        print t
    
    However, the output for each does not show the parent tags (parentTag) at all yet when viewed in md itself, those parent tags still are visible.
    Output:
    2
    [ParentTxn(cf1b4369-8dfa-4cce-a7ca-b8ba9766c33c) 20201019 desc=Example Transaction 3; val=-100; stat= ; #splits=1; chk=; acct=AUD cash; splits=SplitTxn: val=100; amt=-100; desc=Split Test; stat= ; cat=CFT; tags=[(samt:100)(acctid:f16cd26d-d818-411d-92e6-194c1d5e2a23)(obj_type:)(id:5fca879b-ff14-4149-a98a-e5907781ced6)(pamt:-100)(desc:Split Test)(tags:splitTag)]; ], ; ]
    [ParentTxn(9bb7f3e5-35f6-489a-9db3-a069bec6f43d) 20201019 desc=Example Transaction 2; val=-100; stat= ; #splits=1; chk=; acct=AUD cash; splits=SplitTxn: val=100; amt=-100; desc=Example Transaction 2; stat= ; cat=CFT; tags=[(samt:100)(acctid:f16cd26d-d818-411d-92e6-194c1d5e2a23)(obj_type:)(id:df818ce5-ec7d-407b-8d9d-9cd44f1af8b7)(pamt:-100)(desc:Example Transaction 2)(tags:)]; ], ; ]
    Finished running normally at Mon Oct 19 14:51:37 AWST 2020
    
    Strange...
  11. Support Staff 11 Posted by Sean Reilly on 19 Oct, 2020 09:51 AM

    Sean Reilly's Avatar

    Hi David,

    I think the differing output there is only because of the different implementations of .toString() on SplitTxn and ParentTxn. SplitTxn's toString() shows the keywords/tags but ParentTxn's doesn't. They actually are the same behind the scenes.

    Thanks,
    Sean

  12. 12 Posted by Stuart Beesley on 19 Oct, 2020 10:46 AM

    Stuart Beesley's Avatar

    Sean just beat me to it…. ;-> (but then he has that right as the ‘creator'… :-> )

    As mentioned, the class methods that print themselves are different and simply don’t display the tags on the ParentTxn… Code below:

    ParentTxn.toString()
    ====
      public String toString() {
        synchronized (this) {
          StringBuffer sb = new StringBuffer("[ParentTxn(" + this.syncID + ") ");
          sb.append(getDateInt()).append(' ');
          sb.append("desc=");
          sb.append(getDescription());
          sb.append("; ");
          sb.append("val=");
          sb.append(getValue());
          sb.append("; ");
          sb.append("stat=");
          sb.append(getStatusChar());
          sb.append("; ");
          sb.append("#splits=");
          sb.append(getSplitCount());
          sb.append("; ");
          sb.append("chk=");
          sb.append(getCheckNumber());
          sb.append("; ");
          sb.append("acct=");
          sb.append(getAccount());
          sb.append("; ");
          if (isDirty())
            sb.append("dirty; ");
          sb.append("splits=");
          for (int i = 0; i < getSplitCount(); i++) {
            sb.append(getSplit(i));
            sb.append(", ");
          }
          sb.append("; ]");
          return sb.toString();
        }
      }
    ====

    SplitTxn.toString()
    ====
      public String toString() {
        StringBuffer sb = new StringBuffer("SplitTxn: ");
        sb.append("val=");
        sb.append(getValue());
        sb.append("; ");
        sb.append("amt=");
        sb.append(getParentAmount());
        sb.append("; ");
        sb.append("desc=");
        sb.append(getDescription());
        sb.append("; ");
        sb.append("stat=");
        sb.append(getStatusChar());
        sb.append("; ");
        sb.append("cat=");
        sb.append(getAccount());
        sb.append("; ");
        if (getTags() != null) {
          SyncRecord syncRecord = getTags();
          sb.append("tags=[");
          for (String key : syncRecord.keySet())
            sb.append("(" + key + ":" + (String)syncRecord.get(key) + ")");
          sb.append("];");
        }
        if (isDirty())
          sb.append("dirty; ");
        sb.append(" ]");
        return sb.toString();
      }
    ======

    Stuart
    [a fellow user]

  13. 13 Posted by Stuart Beesley on 19 Oct, 2020 12:08 PM

    Stuart Beesley's Avatar

    I don’t think you are properly constructing the Transactions. I get the same as you with your code… Looking at the Transaction details….. On a good Txn, the Tax date seems to be set to the same as the date, and chk and memo exist as “”. This seems to work:

    Add this:
    txn.setMemo("")
    txn.setCheckNumber("")
    txn.setTaxDateInt(20201019)

    And possibly set oldID to -1

    I see there’s a constructor called:

    makeParentTxn​(
    AccountBook,
    book,
    int date,
    int taxDate,
    long dateEntered,
    java.lang.String checkNumber,
    Account account,
    java.lang.String description,
    java.lang.String memo,
    long id,
    byte status)

    Which actually does this:

      public static ParentTxn makeParentTxn(AccountBook book, int date, int taxDate, long dateEntered, String checkNumber, Account account, String description, String memo, long id, byte status) {
        ParentTxn txn = new ParentTxn(book);
        txn.setEditingMode();
        txn.setDateInt(date);
        txn.setTaxDateInt(taxDate);
        txn.setDateEntered(dateEntered);
        txn.setCheckNumber(checkNumber);
        txn.setAccount(account);
        txn.setDescription(description);
        txn.setMemo(memo);
        txn.setOldTxnID(id);
        txn.setStatus(status);
        return txn;

    So I assume this is the minimum set for a Txn…

    Hope this helps?

    ?

    Stuart

  14. 14 Posted by Stuart Beesley on 19 Oct, 2020 01:27 PM

    Stuart Beesley's Avatar

    For fun….. This code updates the ParentTxn’s print/toString() to include tags/keywords. I did try to override the actual method, but it’s prevented by a Final java class preventing it:
    ---
    from com.infinitekind.moneydance.model import ParentTxn
    from com.infinitekind.moneydance.model import SplitTxn
    from java.lang import StringBuffer

    root = moneydance.getCurrentAccount() # Account
    book = moneydance.getCurrentAccountBook() # AccountBook
    txnset = book.getTransactionSet() # TransactionSet

    def my_toString(txn):

        sb = StringBuffer("[ParentTxn(" + txn.getUUID() + ") ");
        sb.append(txn.getDateInt()).append(' ');
        sb.append("desc=");
        sb.append(txn.getDescription());
        sb.append("; ");
        sb.append("val=");
        sb.append(txn.getValue());
        sb.append("; ");
        sb.append("stat=");
        sb.append(txn.getStatusChar());
        sb.append("; ");
        sb.append("#splits=");
        sb.append(txn.getSplitCount());
        sb.append("; ");
        sb.append("chk=");
        sb.append(txn.getCheckNumber());
        sb.append("; ");
        sb.append("acct=");
        sb.append(txn.getAccount());
        sb.append("Keywords=");
        sb.append(txn.getKeywords());
        sb.append("; ");
        if (txn.isDirty()):
            sb.append("dirty; ");
        sb.append("splits=");
        for i in range(0, txn.getSplitCount()):
            sb.append(txn.getSplit(i));
            sb.append(", ");
        sb.append("; ]");
        return sb.toString();

            
    #-- make new txns
    acc1 = root.getAccountByName("TEST")
    acc2 = root.getAccountByName("Bills")
    #-- 1
    txn = ParentTxn(book)
    txn.setDescription("Example Transaction x1")
    txn.setDateInt(20201019)
    txn.setAccount(acc1)
    txn.setMemo("")
    txn.setCheckNumber("")
    txn.setTaxDateInt(20201019)
    txnSplit = SplitTxn(txn)
    txnSplit.setAmount(100)
    txnSplit.setParentAmount(1.0, -100)
    txnSplit.setAccount(acc2)
    txnSplit.setDescription("Split Test")
    txnSplit.setKeywords(["splitTag"])
    txn.addSplit(txnSplit)
    txn.syncItem()
    print my_toString(txn)
    #-- 2
    txn = ParentTxn(book)
    txn.setDescription("Example Transaction x2")
    txn.setDateInt(20201019)
    txn.setAccount(acc1)
    txn.setKeywords(["parentTag"])
    txn.setMemo("")
    txn.setCheckNumber("")
    txn.setTaxDateInt(20201019)
    txnSplit = SplitTxn(txn)
    txnSplit.setAmount(100)
    txnSplit.setParentAmount(1.0, -100)
    txnSplit.setAccount(acc2)
    txnSplit.setDescription("Split Test")
    txn.addSplit(txnSplit)
    txn.syncItem()
    print my_toString(txn)
    #-- 3
    txn = ParentTxn(book)
    txn.setDescription("Example Transaction x3")
    txn.setDateInt(20201019)
    txn.setAccount(acc1)
    txn.setKeywords(["parentTag"])
    txn.setMemo("")
    txn.setCheckNumber("")
    txn.setTaxDateInt(20201019)
    txnSplit = SplitTxn(txn)
    txnSplit.setAmount(100)
    txnSplit.setParentAmount(1.0, -100)
    txnSplit.setAccount(acc2)
    txnSplit.setDescription("Split Test")
    txnSplit.setKeywords(["splitTag"])
    txn.addSplit(txnSplit)
    txn.syncItem()
    print my_toString(txn)
    ---

  15. 15 Posted by davidcullen on 20 Oct, 2020 09:12 AM

    davidcullen's Avatar

    Thanks for all your insights. It seems things are working correctly now (having upgraded to md 2020.2. Here is my code:

    from com.infinitekind.moneydance.model import ParentTxn
    from com.infinitekind.moneydance.model import SplitTxn
    from java.lang import StringBuffer
    root = moneydance.getCurrentAccount()  # Account
    book = moneydance.getCurrentAccountBook()  # AccountBook
    txnset = book.getTransactionSet()  # TransactionSet
    txns = [t for t in txnset if isinstance(t, ParentTxn) and t.getKeywords()]
    for txn in txns:  # cycle through Parents with tag(s)
       parent_tags = set(txn.getKeywords())
       txn.setKeywords([])  # clear Parent's tags
        for i in range(txn.getSplitCount()):
            this_split = txn.getSplit(i)
           split_tags = set(this_split.getKeywords())
           split_tags = split_tags | parent_tags  # Union operator on two sets to prevent duplicates
            this_split.setKeywords(list(split_tags))
        txn.syncItem()
    
    This code appears now to successfully transfer ant parent tags to the relevant splits and then remove the keywords from the parent txns. This is exactly what I wanted. Occasionally, I wrongly key in a keyword to a parent (only splits should have tags, I think). The problem was that it was very difficult to locate the bad parent tags once made (to correct them manually). This script solves things automatically. The script now uses python sets to avoid duplication of tags, rather than md Misc.addNoDuplicates() as I encountered errors wth immutable java lists. Anyway, things now seem to work fine.
    The key point of all this seems to be that you can create keywords for ParentTxns (but this seems to cause problems) so the script detects and “moves” any to the relevant splits automatically.

    I also liked your “toString()” modification. I tweeked this a little so the function can accept either a ParentTxn or a SplitTxn as an argument, with:

    def parent_toString(txn):  # txn could be SplitTxn or ParentTxn
        """md ParentTxn toString() method does not print any parent tags"""
        txn = txn.getParentTxn()  # ensures txn is pointed to a parent
    
    then following all of your code. It works!
    Finally, a newbie question you may be able tho help with. How did you find out about the built-in toString() class methods? Where do I look? Please bear in mind that I am only a little conversant with java - I mainly use python.
  16. 16 Posted by Stuart Beesley on 20 Oct, 2020 09:26 AM

    Stuart Beesley's Avatar

    Ah, well we are both on a learning journey there… Another more experienced user passed on these tips to me (thanks <you know who you are>)..

    Download a Java decompiler - e.g. http://java-decompiler.github.io <http://java-decompiler.github.io/>
    Locate the moneydance.jar file within the Moneydance application package/contents
    Load this into the decompiler.
    Play

    Jython is quite useful as you can Override java classes/methods to make them work for you in Python - especially SwingX packages

    SCB

Reply to this discussion

Internal reply

Formatting help / Preview (switch to plain text) No formatting (switch to Markdown)

Attaching KB article:

»

Attached Files

You can attach files up to 10MB

If you don't have an account yet, we need to confirm you're human and not a machine trying to post spam.

Keyboard shortcuts

Generic

? Show this help
ESC Blurs the current field

Comment Form

r Focus the comment reply box
^ + ↩ Submit the comment

You can use Command ⌘ instead of Control ^ on Mac