Split transactions

Jim's Avatar

Jim

15 Jan, 2026 02:34 AM

Hi guys, Having trouble creating/manipulating split transactions in Java. In Jython things seem to be easier.
Using Moneydance 2024.4 5253

Sample code:
```java public ParentTxn createTransaction(AccountBook book,
Account parentAccount, int date, List splits) {

ParentTxn newTxn = new ParentTxn(book);
newTxn.setDateInt(date);
newTxn.setTaxDateInt(date);
newTxn.setAccount(parentAccount);
newTxn.setDescription("Test Transaction");
newTxn.setStatus(AbstractTxn.STATUS_UNRECONCILED);

for (SplitData s : splits) {
    SplitTxn txnSplit = new SplitTxn(newTxn);

    // METHOD SEQUENCE UNDER REVIEW:
    txnSplit.setAmount(s.amount);
    txnSplit.setParentAmount(1.0, -s.amount); // Requirement for balance sync
    txnSplit.setAccount(s.targetAccount);    // Assign account last

    newTxn.addSplit(txnSplit);
}

book.getTransactionSet().addNewTxn(newTxn);
newTxn.syncItem();
return newTxn;

}




Test 1: Simple Multi-Leg Split - PASS




Scenario: A paycheck deposit with taxes and insurance. * Target (Parent): Checking * Split 1: Salary (Income) | Amount: -3500.00 * Split 2: Taxes (Expense) | Amount: +800.00 * Split 3: Insurance (Expense) | Amount: +200.00 * Expected Result: Parent register shows +$2,500.00.

Test 2: Split Receipt - PASS




Scenario: Single withdrawal split into two expense categories. * Target (Parent): Cash * Split 1: Groceries | Amount: 100.00 * Split 2: Hardware | Amount: 25.50 * Expected Result: Parent register shows -$125.50.

Test 3: Bank Transfer - FAIL




Scenario: Moving money between two bank accounts using a split. * Target (Parent): Checking * Split 1: Savings (Bank Account) | Amount: 100.00 * Expected Result: Parent shows -$100.00, Savings shows +$100.00. * Observation: Results in $0.00 parent value

Test 4: Updating Existing Splits - FAIL




Scenario: Replacing splits on an existing transaction. 1. Use txn.removeSplit(0) to clear an existing split. 2. Add new splits using the logic in the factory above. * Expected Result: Parent register reflects the sum of new splits. * Observation: FAIL. The parent register value resets to $0.00. Even calling syncItem() or TransactionSet.recalcBalances() does not restore the derived parent total.

Test 5: Zero-Sum Adjustment - PASS




Scenario: Internal reallocation between categories. * Target (Parent): Cash * Split 1: Misc Expense | Amount: +20.00 * Split 2: Other Income | Amount: -20.00 * Expected Result: Parent register shows $0.00. * Observation: Pass.
Any help appreciated. Thanks!
  1. 1 Posted by Stuart Beesley ... on 15 Jan, 2026 06:05 AM

    Stuart Beesley (Mr Toolbox)'s Avatar

    Firstly, don't call: book.getTransactionSet().addNewTxn(newTxn);
    just call: newTxn.syncItem();
    as otherwise you are (behind the scenes) calling syncItem() twice

    Secondly, Jython and Java usage of the API is identical in the sense of actual code and how you use it, so this cannot be your issue. Unless the way you store items in Jython is somehow different to how you are storing in Java (e.g. using an object by reference and accidentally reusing data from elsewhere).

    To the main point. I think your code is fighting itself.
    setParentAmount() simply calls: setAmount(-Math.round(newRate * newParentAmount), newRate, newParentAmount) - so I expect your setParentAmount() call is overwriting your previous setAmount() call.

    Do this:

    • call txnSplit.setAccount() first.

    • next (only) call this (assuming single currency and rate of 1.0: txnSplit.setAmount(newSplitAmount: Long, newParentAmount: Long) (or if you want longhand: setAmount(newSplitAmount, 1.0, -newParentAmount) )

    • lastly call syncItem()

    PS - I can't see how you are calling txnSplit.setAmount(s.amount); was that a typo - I cannot see a signature for only one parameter.

    Let me know how it goes?

  2. 2 Posted by Jim on 15 Jan, 2026 06:19 PM

    Jim's Avatar

    Hi, still struggling here.
    So I've had this jython code which has worked well for a while although I don't think it's as you recommend as it does add and sync.

    def internalCreateTxn(parentData, splitData):
        book = moneydance.getCurrentAccountBook()
        newTxn = ParentTxn(book)
        newTxn.setDateInt(parentData[0])
        newTxn.setAccount(parentData[2])
        newTxn.setDescription(parentData[3])
        # ... (Set other parent fields)
        
        for i in range(0, len(splitData)):
            txnSplit = SplitTxn(newTxn)
            val = splitData[i][0]
            
            txnSplit.setAmount(val)                # Step 1: Set Split Amount
            txnSplit.setParentAmount(1.0, -val)    # Step 2: Set Rate=1.0, Parent=-val
            
            txnSplit.setAccount(splitData[i][1])
            txnSplit.setDescription(splitData[i][2])
            newTxn.addSplit(txnSplit)              # Step 3: Add to Parent
            
        moneydance_data.getTransactionSet().addNewTxn(newTxn)
        newTxn.syncItem()
    

    When I have this code in Java, it always creates a transaction with a $0 value.

    public Transaction createTransaction(CreateTransactionRequest request) {
        AccountBook book = getAccountBook();
        // ... (Setup ParentTxn newTxn) ...
        
        for (Split s : request.getSplitsList()) {
            SplitTxn txnSplit = new SplitTxn(newTxn);
            long val = s.getAmount().getAmountMinor();
            
            txnSplit.setAmount(val);             # 1. Amount
            txnSplit.setParentAmount(1.0, -val); # 2. Parent/Rate
            txnSplit.setAccount(splitAcc);       # 3. Account (Last)
            
            newTxn.addSplit(txnSplit);           # 4. Add
        }
        book.getTransactionSet().addNewTxn(newTxn); 
        newTxn.syncItem();
        return convertTransaction(newTxn, null);
    }
    

    All of these variations below give me a $0 value actually

        for (Split s : request.getSplitsList()) {
            SplitTxn txnSplit = new SplitTxn(newTxn);
            txnSplit.setAccount(splitAcc); // Account FIRST
            
            long val = s.getAmount().getAmountMinor();
            txnSplit.setAmount(val, 1.0, -val); 
            
            txnSplit.setDescription(s.getMemo());
            newTxn.addSplit(txnSplit);
        }
        
        book.getTransactionSet().addNewTxn(newTxn);
        newTxn.syncItem();
        return convertTransaction(newTxn, null);
    
        for (Split s : request.getSplitsList()) {
            SplitTxn txnSplit = new SplitTxn(newTxn);
            txnSplit.setAccount(splitAcc);
            
            long val = s.getAmount().getAmountMinor();
            // SIMPLE CALL: Only Split Amount
            txnSplit.setAmount(val); 
            
            newTxn.addSplit(txnSplit);
        }
        
        book.getTransactionSet().addNewTxn(newTxn);
        newTxn.syncItem();
    
        for (Split s : request.getSplitsList()) {
            SplitTxn txnSplit = new SplitTxn(newTxn);
            txnSplit.setAccount(splitAcc);
            
            long val = s.getAmount().getAmountMinor();
            // STANDARD CALL: SplitAmount, ParentAmount
            txnSplit.setAmount(val, -val); 
            
            newTxn.addSplit(txnSplit);
        }
        
        book.getTransactionSet().addNewTxn(newTxn);
        newTxn.syncItem();
    
        for (Split s : request.getSplitsList()) {
            SplitTxn txnSplit = new SplitTxn(newTxn);
            txnSplit.setAccount(splitAcc);
            
            long val = s.getAmount().getAmountMinor();
            // TWO-STEP: Exactly matching Python calls
            txnSplit.setAmount(val);
            txnSplit.setParentAmount(1.0, -val); 
        }
        
        book.getTransactionSet().addNewTxn(newTxn);
        newTxn.syncItem();
    
        for (Split s : request.getSplitsList()) {
            SplitTxn txnSplit = new SplitTxn(newTxn);
            txnSplit.setAccount(splitAcc);
            
            long val = s.getAmount().getAmountMinor();
            txnSplit.setAmount(val);
            txnSplit.setParentAmount(1.0, -val);
            
            newTxn.addSplit(txnSplit); 
        }
      
        newTxn.syncItem();
     ```
    
    Any thoughts/help appreciated. I'm not sure what I'm missing here. 
    Thanks, Jim
    
  3. 3 Posted by Stuart Beesley ... on 16 Jan, 2026 08:35 AM

    Stuart Beesley (Mr Toolbox)'s Avatar

    Jim. Have you tried my suggestion? Can you try please and let me know the results.

    For each split.
    Set account first
    Only call txnSplit.setAmount(newSplitAmount: Long, newParentAmount: Long)

    Then Sync item.

    Does this work or not?

    If not. Please provide.
    The raw data for one of these that’s failing
    Add debug messages into the split loop to dump the values being used for the splits
    Perhaps print each split and txn to console.

  4. 4 Posted by Stuart Beesley ... on 17 Jan, 2026 09:57 AM

    Stuart Beesley (Mr Toolbox)'s Avatar

    As well as my suggestion and sending the types and raw data to look at.

    One difference I have found between Jython and Java, is that if there is an overloaded Java function which can take both integers, or long values, then it can sometimes get the wrong overload. For example when using DateRange(), I have to pass the value wrapped in a Java.lang.Integer(value). This is the only thing I can think of that could be otherwise messing up with the call that would be different from Jython. My gut says try my solution (and sending me the raw data requested)

  5. 5 Posted by Jim on 21 Jan, 2026 04:17 PM

    Jim's Avatar

    Hi, sorry for the radio silence. Back at it Still having issues.

    I tried this function:

        public com.moneydance.mcp.bridge.proto.Transaction createHardcodedCashTxn() {
            logToFile("\n--- [DEBUG-JAVA] STARTING HARDCODED TXN TEST ---");
            AccountBook book = getAccountBook();

        // Target: Cash
        com.infinitekind.moneydance.model.Account cash = findAccountByUUID(book.getRootAccount(), "64edb02a-9624-42d4-8970-2a793cf6abad");
        // Split: Misc
        com.infinitekind.moneydance.model.Account misc = findAccountByUUID(book.getRootAccount(), "d0ffe642-4097-4923-a835-36002b354c1a");
    
        if (cash == null || misc == null) {
            logToFile("[DEBUG-JAVA] ERROR: Accounts not found.");
            throw new RuntimeException("Test accounts not found");
        }
    
        ParentTxn p = new ParentTxn(book);
        logToFile("[DEBUG-JAVA] 1. ParentTxn created. UUID=" + p.getUUID() + " Value=" + p.getValue());
    
        p.setDateInt(20260115);
        p.setAccount(cash);
        p.setDescription("DEBUG HARDCODED TXN");
        p.setStatus(AbstractTxn.STATUS_UNRECONCILED);
        logToFile("[DEBUG-JAVA] 2. Parent properties set. Account=" + p.getAccount().getAccountName() + " Value=" + p.getValue());
    
        SplitTxn s = new SplitTxn(p);
        logToFile("[DEBUG-JAVA] 3. SplitTxn created via new SplitTxn(p). SplitCount=" + p.getSplitCount());
    
        s.setAccount(misc);
        logToFile("[DEBUG-JAVA] 4. Split account set to: " + s.getAccount().getAccountName());
    
        long val = 7777L; // $77.77
        logToFile("[DEBUG-JAVA] 5. Calling s.setAmount(" + val + ", " + (-val) + ")");
        s.setAmount(val, -val);
        logToFile("[DEBUG-JAVA] 6. Split Values: Amount=" + s.getAmount() + " ParentAmount=" + s.getParentAmount());
        logToFile("[DEBUG-JAVA] 7. Parent Value before addSplit: " + p.getValue());
    
        p.addSplit(s);
        logToFile("[DEBUG-JAVA] 8. After p.addSplit(s). SplitCount=" + p.getSplitCount() + " Parent Value=" + p.getValue());
    
        logToFile("[DEBUG-JAVA] 9. SKIPPING txnSet.addNewTxn(p) (Per Forum Advice)");
        // book.getTransactionSet().addNewTxn(p);
    
        logToFile("[DEBUG-JAVA] 10. Calling p.syncItem() ONLY");
        p.syncItem();
        logToFile("[DEBUG-JAVA] 11. Final Parent Value=" + p.getValue());
    
        return convertTransaction(p, null);
    }
    
    
    
    

    returns:

    --- [DEBUG-JAVA] STARTING HARDCODED TXN TEST ---
    [DEBUG-JAVA] 1. ParentTxn created. UUID=71a23ee4-2fc5-4ef8-9766-098a57db0b8f Value=0
    [DEBUG-JAVA] 2. Parent properties set. Account=Cash Value=0
    [DEBUG-JAVA] 3. SplitTxn created via new SplitTxn(p). SplitCount=0
    [DEBUG-JAVA] 4. Split account set to: Misc
    [DEBUG-JAVA] 5. Calling s.setAmount(7777, -7777)
    [DEBUG-JAVA] 6. Split Values: Amount=-7777 ParentAmount=7777
    [DEBUG-JAVA] 7. Parent Value before addSplit: 0
    [DEBUG-JAVA] 8. After p.addSplit(s). SplitCount=1 Parent Value=7777
    [DEBUG-JAVA] 9. SKIPPING txnSet.addNewTxn(p) (Per Forum Advice)
    [DEBUG-JAVA] 10. Calling p.syncItem() ONLY
    [DEBUG-JAVA] 11. Final Parent Value=0
    

    Thoughts?

  6. 6 Posted by Stuart Beesley ... on 21 Jan, 2026 06:14 PM

    Stuart Beesley (Mr Toolbox)'s Avatar

    That was fun. You make have hit a bug..

    • First always call at least s.setDescription(""); // or anything
    • Second. Use s.setAmount(val, val); // note the sign - as the parent value gets flipped

    Let me know?

  7. 7 Posted by Jim on 21 Jan, 2026 06:52 PM

    Jim's Avatar

    I'm not clear what you mean on the firstpoint. The code I pasted has this:

       p.setAccount(cash);
        p.setDescription("DEBUG HARDCODED TXN");
        p.setStatus(AbstractTxn.STATUS_UNRECONCILED);
    

    Did you mean something else?

  8. 8 Posted by Stuart Beesley ... on 21 Jan, 2026 09:47 PM

    Stuart Beesley (Mr Toolbox)'s Avatar

    Exactly as I said s.setDescription(“”)

    Ie on the split. It’s a bug. With no description the split self destructs.

  9. 9 Posted by Jim on 22 Jan, 2026 04:48 AM

    Jim's Avatar

    ah. on the spilt!.
    Well that fixed it! Ha! Never would have guessed.
    Thanks for your help!

  10. 10 Posted by Stuart Beesley ... on 22 Jan, 2026 08:11 AM

    Stuart Beesley (Mr Toolbox)'s Avatar

    👍

    It certainly wasn’t obvious.

  11. 11 Posted by Stuart Beesley ... on 22 Jan, 2026 08:43 AM

    Stuart Beesley (Mr Toolbox)'s Avatar

    Also note that in your Jython code you were calling:
    txnSplit.setDescription(()

    So as mentioned, Jython and Java should operate the same (ish)

  12. 12 Posted by Jim on 23 Jan, 2026 01:37 AM

    Jim's Avatar

    Yup. Thanks.

    Another related question. Do you have any advice on when, if ever, we should be using isDirty and .txnModified?

    Thanks

  13. 13 Posted by Stuart Beesley ... on 23 Jan, 2026 06:57 AM

    Stuart Beesley (Mr Toolbox)'s Avatar

    Never. I certainly never have.
    As far as I know they are for internal usage.
    Why?

  14. 14 Posted by Jim on 23 Jan, 2026 03:52 PM

    Jim's Avatar

    Just curious. I see them in the API, so wanted to confirm. I've never used them, but was wondering if I was missing something.

  15. 15 Posted by Stuart Beesley ... on 23 Jan, 2026 04:50 PM

    Stuart Beesley (Mr Toolbox)'s Avatar

    Yes, a rabbit hole - don't go there if you want a life!

  16. 16 Posted by Stuart Beesley ... on 23 Jan, 2026 04:51 PM

    Stuart Beesley (Mr Toolbox)'s Avatar

    PS - I have been informed that the "bug" I reported above has been fixed for the next release

    (not support, just a fellow user)

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