Merging certain types of list objects is a useful feature of QuickBooks. For example, merging accounts is useful for eliminating redundant accounts within a given account type. For example, a company file might have an expense account that tracks paper reams and a different expense account that tracks office supplies. You may want to change this to have only a single account–the office supplies account–that tracks paper reams along with other office supplies.
The QuickBooks UI solves these types of problems by allowing users to merge accounts, customers, and vendors. Beginning with qbXML spec 7.0 and QuickBooks 2008, these types of list merge operations can be performed through the SDK by using the ListMerge request. The ability to merge Classes was added in qbXML 8.0 and QB 2009.
This page describes the use of ListMerge and the work you need to do before you actually invoke ListMerge.
ListMerge is a request that allows you to merge the following:
Note
Note
ListMerge requires the company file to be opened in single user mode.
What happens in the merge is that one of the objects, the “merge-from” object, is merged into the other object, the “merge-to” object. For example, suppose the expense account “Paper Reams” is merged into the expense account “Office Supplies.” After the merge, there is only the “Office Supplies” account, and all transactions in the past that referenced “Paper Reams” now automatically reference “Office Supplies.” This means that balances will change in the merged-to object as it receives the merged-from balances.
You cannot do a ListMerge if an accountant copy of the company exists–if you do, you’ll get an error. The idea is that any merges ought to be done in the accountant copy. Beginning with QB 2009 and qbXML 8.0, you can use the Account Copy Exists request to check for this before issuing the list merge request.
No.
The merging rules are not many in number, but they are different for accounts, customers, and vendors. The following table shows the rules applying to each:
| Objects to Merge | Required Pre-Merge Conditions | How to Satisfy the Conditions |
|---|---|---|
| Accounts |
|
|
| Class | No requirements | NA. |
| Customers |
|
|
| Vendors | You cannot merge a customer and a vendor | Cannot change this. |
To merge two accounts,
The Account object contains an AccountType field. You need to compare this type before proceeding to merge accounts.
If the AccountType is the same, you next need to determine whether the accounts to be merged have the same Sublevel (yes, there is a Sublevel field in the Ret!). If Sublevels are not equal, you need to do an AccountMod on one of them to change its Sublevel–by changing the ParentRef (or removing it if a Sublevel of 0 is desired).
The following code snippet does a query for two hardcoded accounts, and arbitrarily picks the first as the “merge-from” account, and the second as the “merge-to” account, and adjusts the Sublevel accordingly. After the Sublevel is fixed by changing the ParentRef, we invoke AccountMod to make the changes, store the changed EditSequence, and then do the list merge.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | SessionManager.OpenConnection "", "IDN List Merge Sample"
SessionManager.BeginSession "", omDontCare
Dim ListMerge_Set As IMsgSetRequest
Set ListMerge_Set = SessionManager.CreateMsgSetRequest("US", 7, 0)
ListMerge_Set.Attributes.OnError = roeContinue
‘Do query to get latest Sublevel and EditSequence data for the Accounts:
‘We hardcode the accounts just for convenience
Dim AccountQuery As IAccountQuery
Set AccountQuery = ListMerge_Set.AppendAccountQueryRq
AccountQuery.ORAccountListQuery.FullNameList.Add "OfficeSupplies:Paper Reams"
AccountQuery.ORAccountListQuery.FullNameList.Add "Office Supplies"
Dim ListMergeAddResp As IMsgSetResponse
Set ListMergeAddResp = SessionManager.DoRequests(ListMerge_Set)
‘Process the query response: we need ListID, EditSequence, Sublevel, and
‘of course, AccountType.
Dim MyResponse As IResponse
Dim AccountRet As IAccountRet Dim ResponseType As Integer
Dim AccountRetList As IAccountRetList
Dim AccountType_MergeFrom As Integer
Dim AccountType_MergeTo As Integer
Dim Sublevel_MergeTo As String
Dim Sublevel_MergeFrom As String
Dim ListID_MergeFrom As String
Dim ListID_MergeTo As String
Dim EditSequence_MergeTo As String
Dim EditSequence_MergeFrom As String
Dim ParentRef_MergeTo As String
ParentRef_MergeTo = "empty"
Dim ParentRef_MergeFrom As String
ParentRef_MergeFrom = "empty"
Set MyResponse = ListMergeAddResp.ResponseList.GetAt(0)
If (Not MyResponse.Detail Is Nothing) Then
ResponseType = MyResponse.Type.getValue
If (Not ResponseType = rtAccountQueryRs)
Then Exit Sub
End If
End If
‘Store the values from the first account, which we arbitrarily use as the ‘merge from account.
Set AccountRetList = MyResponse.Detail
Set AccountRet = AccountRetList.GetAt(0)
ListID_MergeFrom = AccountRet.ListID.getValue
EditSequence_MergeFrom = AccountRet.EditSequence.getValue
AccountType_MergeFrom = AccountRet.AccountType.getValue
Sublevel_MergeFrom = AccountRet.Sublevel.getValue
‘If Sublevel is 0, there won’t be any ParentRef
If (Not AccountRet.ParentRef Is Nothing)
Then
ParentRef_MergeFrom = AccountRet.ParentRef.FullName.getValue
End If
‘Store the values from the second account, which we arbitrarily use as
‘the merge-to account.
Set AccountRet = AccountRetList.GetAt(1)
ListID_MergeTo = AccountRet.ListID.getValue
EditSequence_MergeTo = AccountRet.EditSequence.getValue
AccountType_MergeTo = AccountRet.AccountType.getValue
Sublevel_MergeTo = AccountRet.Sublevel.getValue
If (Not AccountRet.ParentRef Is Nothing)
Then
ParentRef_MergeTo = AccountRet.ParentRef.FullName.getValue
End If
‘Now for the checking: AccountTypes and Sublevels must match.
If (Not AccountType_MergeFrom = AccountType_MergeTo)
Then
MsgBox "Only accounts of the same AccountType can be merged!)"
Exit Sub
End If
‘For convenience only, we just check for the condition where merge-from
‘has higher sublevel than merge-to. If merge-to sublevel is 1 or greater,
‘we set the merge-from ParentRef equal to the ParentRef of the merge-to,
‘as this results in same Sublevel. If merge-to has no ParentRef, we make
‘merge-from have no ParentRef either.
If (Sublevel_MergeFrom > Sublevel_MergeTo) Then If (Not ParentRef_MergeTo = "empty")
Then
ParentRef_MergeFrom = ParentRef_MergeTo Else
ParentRef_MergeFrom = "empty"
End If
‘Now Mod the merge-from account, changing it’s ParentRef as needed ListMerge_Set.ClearRequests
Dim AccountMod As IAccountMod
Set AccountMod = ListMerge_Set.AppendAccountModRq
AccountMod.ListID.setValue ListID_MergeFrom
AccountMod.EditSequence.setValue EditSequence_MergeFrom
If (ParentRef_MergeFrom = "empty")
Then
AccountMod.ParentRef.FullName.SetEmpty Else
AccountMod.ParentRef.FullName.setValue ParentRef_MergeFrom End If
End If
Set ListMergeAddResp = SessionManager.DoRequests(ListMerge_Set)
‘Process the response to the AccountMod to get the updated EditSequence.
Set MyResponse = ListMergeAddResp.ResponseList.GetAt(0)
If (Not MyResponse.Detail Is Nothing)
Then
ResponseType = MyResponse.Type.getValue
If (Not ResponseType = rtAccountModRs)
Then MsgBox "unexpected response type"
Exit Sub
End If
End If
Set AccountRet = MyResponse.Detail
EditSequence_MergeFrom = AccountRet.EditSequence.getValue
‘We have everything we need to do the merge, so let’s do it.
Dim ListMergeAdder As IListMerge
ListMerge_Set.ClearRequests
Set ListMergeAdder = ListMerge_Set.AppendListMergeRq
ListMergeAdder.ListMergeType.setValue lmtAccount
ListMergeAdder.MergeFrom.ListID.setValue ListID_MergeFrom
ListMergeAdder.MergeFrom.EditSequence.setValue EditSequence_MergeFrom
ListMergeAdder.MergeTo.ListID.setValue ListID_MergeTo
ListMergeAdder.MergeTo.EditSequence.setValue EditSequence_MergeTo
Set ListMergeAddResp = SessionManager.DoRequests(ListMerge_Set)
‘Take a peek at what happened
MsgBox ListMergeAddResp.ToXMLString
SessionManager.EndSession
SessionManager.CloseConnection
|
The following sample code merges two classes:
1 2 3 4 5 6 7 8 9 10 11 12 | Dim qbSessionManager As QBSessionManager Dim msgSetRequest As IMsgSetRequest qbSessionManager = New QBSessionManager() qbSessionManager.OpenConnection2("", "BocaLupa", ENConnectionType.ctLocalQBD) qbSessionManager.BeginSession("", ENOpenMode.omDontCare) msgSetRequest = qbSessionManager.CreateMsgSetRequest("US", 8, 0) msgSetRequest.Attributes.OnError = ENRqOnError.roeStop Dim classMerj As IListMerge classMerj = msgSetRequest.AppendListMergeRq classMerj.ListMergeType.SetValue(ENListMergeType.lmtClass) classMerj.MergeFrom.ListID.SetValue("80000001-1232787304") classMerj.MergeFrom.EditSequence.SetValue("1232787304") classMerj.MergeTo.ListID.SetValue("80000002-1232787304") classMerj.MergeFrom.EditSequence.SetValue("1231787354") |
And the same thing in XML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?xml version="1.0" ?> <?qbxml version="8.0"?> <QBXML> <QBXMLMsgsRq onError = "stopOnError"> <ListMergeRq requestID = "0"> <ListMergeType>Class</ListMergeType> <MergeFrom> <ListID>80000001-1232787304</ListID> <EditSequence>1232787304</EditSequence> </MergeFrom> <MergeTo> <ListID>80000002-1232787304</ListID> <EditSequence>1232787304</EditSequence> </MergeTo> </ListMergeRq> </QBXMLMsgsRq> </QBXML> |
To merge two customers/customer jobs,
See if both customers/jobs have children. To do this, use a customer query with a NameFilter that has its MatchCriterion set to “Contains” and the Name element set to
the full name of the customer or customer job. This will return you the customer/ customer job and all child jobs of that customer/customer job.
If both customer/jobs have children, move all the children of the move-from customer/ job to the merge-to customer/job. To do this, do a CustomerMod on each child job, changing the ParentRef from the merge-from customer/job to the merge-to customer/ job. Alternatively, you could do a CustomerMod on each child job and make it inactive by setting the IsActive field to False. Or, to be more extreme, you could do a ListDel on each child job, but that is not recommended.
Using the ListIDs and recent EditSequence from merge-from and merge-to customer/ jobs, invoke the ListMerge request
The following code snippets show to do the various tasks you’ll need to do, building a customer query to look for child jobs, changing the ParentRef in a child job so it points to the merge-to customer, and building the ListMerge request.
Here is a snippet that builds customer query to look for child jobs for the customer job Jacobsen, Doug:Kitchen. The query will return Jacobsen, Doug:Kitchen and any child jobs because those child jobs will have Jacobsen, Doug:Kitchen within their fullname.
1 2 3 4 5 6 7 8 9 10 11 | SessionManager.OpenConnection "", "IDN List Merge Sample" SessionManager.BeginSession "", omDontCare Dim ListMerge_Set As IMsgSetRequest Set ListMerge_Set = SessionManager.CreateMsgSetRequest("US", 7, 0) ListMerge_Set.Attributes.OnError = roeContinue Dim custQuery As ICustomerQuery Set custQuery = ListMerge_Set.AppendCustomerQueryRq custQuery.ORCustomerListQuery.CustomerListFilter.ORNameFilter.NameFilter.MatchCriterion.setValue mcContains custQuery.ORCustomerListQuery.CustomerListFilter.ORNameFilter.NameFilter.Name.setValue "Jacobsen, Doug:Kitchen" Dim ListMergeSampleResp As IMsgSetResponse Set ListMergeSampleResp = SessionManager.DoRequests(ListMerge_Set) |
If the customer query turns up child jobs, and you have child jobs in both merge-to and merge-from customer/jobs, you need to change the ParentRef in each merge-from child job so that it points to the merge-to customer. The following code snippet shows the CustomerMod used to change this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | SessionManager.OpenConnection "", "IDN List Merge Sample"
SessionManager.BeginSession "", omDontCare
Dim ListMerge_Set As IMsgSetRequest
Set ListMerge_Set = SessionManager.CreateMsgSetRequest("US", 7, 0)
ListMerge_Set.Attributes.OnError = roeContinue
Dim custMod As ICustomerMod
Set custMod = ListMerge_Set.AppendCustomerModRq
‘We’re modifying one of the child jobs
custMod.EditSequence.setValue"1197731900"
custMod.ListID.setValue "800000AA-1197731900"
‘Changing the ParentRef to point to the merge-to customer/job
custMod.ParentRef.ListID.setValue "1F0000-933272658"
Dim ListMergeSampleResp As IMsgSetResponse
Set ListMergeSampleResp = SessionManager.DoRequests(ListMerge_Set)
|
After any child jobs have been moved over to the merge-to customer/job, you can invoke the ListMerge request, as shown in the following code snippet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | SessionManager.OpenConnection "", "IDN List Merge Sample" SessionManager.BeginSession "", omDontCare Dim ListMerge_Set As IMsgSetRequest Set ListMerge_Set = SessionManager.CreateMsgSetRequest("US", 7, 0) ListMerge_Set.Attributes.OnError = roeContinue Dim ListMergeAdder As IListMerge Set ListMergeAdder = ListMerge_Set.AppendListMergeRq ListMergeAdder.ListMergeType.setValue lmtCustomer ListMergeAdder.MergeFrom.ListID.setValue "80000007-1188956785" ListMergeAdder.MergeFrom.EditSequence.setValue "1188956785" ListMergeAdder.MergeTo.ListID.setValue "80000002-1188956165" ListMergeAdder.MergeTo.EditSequence.setValue "1188956165" Dim ListMergeSampleResp As IMsgSetResponse Set ListMergeSampleResp = SessionManager.DoRequests(ListMerge_Set) |
There aren’t any tricks to merging vendors. Just supply the list IDs and the latest EditSequences to the ListMerge request.