I have an Opportunity that contains one Item Group...
# suitescript
a
I have an Opportunity that contains one Item Group with 4 items inside it. Including the item group line and the end group line, that's 6 lines. If I manually in the NetSuite UI remove everything in the item sublist, then add the Item Group back in and save, it saves successfully. But if I do the same thing in Suitescript with the Opportunity loaded in dynamic mode, I get a TRANS_UNBALNCD error when the script tries to save the Opportunity after making the same changes. How do I fix this error when my script is doing the same things that are happening manually? I am setting the fields in this order on each line: item, groupsetup, units, quantity, price, rate, amount, taxcode, taxrate.
b
what does the code look like. setting the groupsetup field is not normal
j
How are you adding & removing things? I find in general that when removing lines in SuiteScript, starting from the end of the list and working backwards works best.
a
I remove the lines in reverse order, but I create them again and overwrite the values on each line created in forward order. I only tried setting the groupsetup field to see if it would make a difference. This problem was happening without that, so I added it to see if it would help. It works perfectly fine on Estimates and Opportunities that have no Item Groups. There's been some back and forth with whether to run this in dynamic mode and use selectNewLine, selectLine, and commitLine, or in non-dynamic mode and just setSublistValue, so there might be some code and comments that look inconsistent about that. Here's a snippet of the current code with a bunch of comment blocks removed. Some variable declarations/definitions may have occurred outside the snippet but you get the idea:
Copy code
const sourceRec = record.load({type: record.Type.ESTIMATE, id: contextValue.id, isDynamic: false});
const targetRecId = sourceRec.getValue({fieldId: "opportunity"});
const targetRec = record.load({type: record.Type.OPPORTUNITY, id: targetRecId, isDynamic: false});

const targetLineCount = targetRec.getLineCount({sublistId: sublist});
for (let targetLine = targetLineCount - 1; targetLine >= 0; targetLine--){
    const targetLineItemType = targetRec.getSublistValue({sublistId: sublist, fieldId: "itemtype", line: targetLine});
    if ("EndGroup" == targetLineItemType){
        log.debug(`Skipping removal of End Group line on target ${targetRec.type} with ID ${targetRec.id}`, `targetLine: ${targetLine}`);
        continue; //Deleting an End Group line is not allowed. Delete the Group instead.
    }
    targetRec.removeLine({sublistId: sublist, line: targetLine});
    log.debug(`Removed ${targetLineItemType} line ${targetLine} during sync of sublist ${sublist}`, `Target Record: ${targetRec.type} with ID ${targetRec.id}`);
    changes.anyChange = true;
}
const checkEmptyTargetLineCount = targetRec.getLineCount({sublistId: sublist});
log.debug(`After removing all lines, target record has ${checkEmptyTargetLineCount} lines in sublist ${sublist}`, `Target Record: ${targetRec.type} with ID ${targetRec.id}`);

const sourceLineCount = sourceRec.getLineCount({sublistId: sublist});

let groupTotal = 0;
for (let i = 0; i < sourceLineCount; i++){
    const sourceItem = sourceRec.getSublistValue({sublistId: sublist, fieldId: "item", line: i});
    const sourceItemType = sourceRec.getSublistValue({sublistId: sublist, fieldId: "itemtype", line: i});
    const sourceUnits = sourceRec.getSublistValue({sublistId: sublist, fieldId: "units", line: i});
    const sourceQuantity = sourceRec.getSublistValue({sublistId: sublist, fieldId: "quantity", line: i});
    const sourceRate = sourceRec.getSublistValue({sublistId: sublist, fieldId: "rate", line: i});
    const sourceAmount = sourceRec.getSublistValue({sublistId: sublist, fieldId: "amount", line: i});
    const sourceGroupSetup = sourceRec.getSublistValue({sublistId: sublist, fieldId: "groupsetup", line: i});
    const sourceTaxCode = sourceRec.getSublistValue({sublistId: sublist, fieldId: "taxcode", line: i});
    const sourceTaxRate = sourceRec.getSublistValue({sublistId: sublist, fieldId: "taxrate", line: i});
    const inGroupVal = sourceRec.getSublistValue({sublistId: sublist, fieldId: "ingroup", line: i});
    const isInGroup = ["T",true].includes(inGroupVal);
    log.debug(`Processing line ${i} of sublist ${sublist} on source record`, `inGroupVal: ${inGroupVal}, isInGroup: ${isInGroup}`);
    if ("EndGroup" == sourceItemType){
        log.debug(`Skipping setting or adding EndGroup line from source line ${i}`);
        continue;

    }else if ("Group" == sourceItemType){
        log.debug(`Adding Group source line ${i} as new line to target record`);

        targetRec.setSublistValue({sublistId: sublist, fieldId: "item", line: i, value: sourceItem});
        targetRec.setSublistValue({sublistId: sublist, fieldId: "quantity", line: i, value: sourceQuantity});

        log.debug(`Committed Group line from source line ${i}`, `sourceItem: ${sourceItem}, sourceQuantity: ${sourceQuantity}, sourceAmount: ${sourceAmount}`);
        continue;

    }else if(isInGroup){

        continue;

    }else{
        log.debug(`Adding Regular source line ${i} as new line to target record`);
        //targetRec.selectNewLine({sublistId: sublist});
    }

    commonFields.forEach(field => {
        const sourceItem = sourceRec.getSublistValue({sublistId: sublist, fieldId: "item", line: i});
        const sourceItemType = sourceRec.getSublistValue({sublistId: sublist, fieldId: "itemtype", line: i});
        if ("EndGroup" == sourceItemType && "amount" == field){
            log.debug(`Line ${i} skipping amount field because it's an EndGroup line`);
            return;
        }

        log.debug("Checking sublist field", `Sublist: ${sublist}, Field: ${field}, Line: ${i}`);
        const sourceValue = sourceRec.getSublistValue({sublistId: sublist, fieldId: field, line: i});

        try{
            targetRec.setSublistValue({sublistId: sublist, fieldId: field, line: i, value: sourceValue});

            log.debug("syncSublistFields", `Value for sublist ${sublist} field "${field}" from source line ${i} set to (${typeof sourceValue}) ${JSON.stringify(sourceValue)}`);
            changes.anyChange = true;

            const checkAmount = targetRec.getSublistValue({sublistId: sublist, line: i, fieldId: "amount"});
            log.debug(`After setting ${field} to ${sourceValue}, checking amount field for any sourcing changes`, `Source Item (${sourceItemType}): ${sourceItem}, Sublist: ${sublist}, Line: ${i}, Amount Value: ${checkAmount}`);

        }catch(syncSublistFieldErr){
            log.error(`Error setting value for sublist ${sublist} field ${field} from source line ${i} to (${typeof sourceValue}) ${JSON.stringify(sourceValue)}`);
        }
    });
}
b
I dont think it would cause your error, but the code shared would not work in general
you don't remove all the lines, which leaves the end of group line
that makes the count on the target record different than the source record, which would mean that the i variable that you use to loop over the source record will not match the new line of the target record
You will need another variable to account for that difference
I would not set any of the fields related to amounts like units, quantity, price, rate, amount, taxcode, taxrate. worry about adding the items correctly, then get the amounts to match so that you can more slowly add fields to find which one causes the problem
You will also want to make sure that the accounts on all the items are correct. Its especially common in new netsuite accounts to have missing accounts which can cause unbalanced transactions
a
The code I shared works as expected on Estimates and Opportunities that do not have Item Groups. If I don't skip the End Group line when removing, it halts with an error saying that End Group line cannot be removed - remove the Item Group line instead. If I skip it in Dynamic Mode, then it gets removed when the Item Group line is removed. The loop to remove the Opportunity lines is separate and happens before the loop to populate and update the Opportunity lines. Good idea on adding the items without setting anything else on those lines first, then one by one seeing which ones and in what order I can overwrite them to make them match the Estimate. We recently refreshed our Sandbox from a Production environment that Accounting has been using heavily since 2017. So I don't think any Accounts would be wrong, but it's worth checking. Thanks for the tip.
b
the problem with the end group item is specific to item groups. dont expect it to occur without item groups. Im not telling you to remove the end group, im telling you to account that it still exists when you calculate how many lines there are. You should be able to see the problem from the logs you already have, you log the item line count after removing the lines.
a
Okay, I see. Well, I did get the linecount again after removing everything except End Group, and it returned 0, which is why I believe that in Dynamic Mode at least, removing the Item Group line removes its corresponding End Group line.
b
its not really a problem in dynamic mode, you dont need to keep track of the new line in dynamic mode since there is a method that does it for you. its specific to the way standard mode adds items to sublists