NetSuite Import - Transfer Order - Inventory Details

Hey,

Can anyone point me in the right direction of being able to assign Inventory Detail to item lines on a Transfer Order Import?

The transfer order type doesn't allow for me to add a subrrecord like it does on Item Fulfilments. Our requirement is that Inventory Details are assigned at the Transfer Order level first before creating an Item Fulfilment.

Transfer Order:

Item Fulfillment:

You would need to use subrecords: https://docs.celigo.com/hc/en-us/articles/360016510891-Configure-and-map-NetSuite-subrecords

Hi Tyler,

Thanks, the subrecord import is not available for me to add to Transfer Order via the UI.

Looks like this would need an enhancement then. For now, you would need to use a postMap SuiteScript hook:

@praveenbasani @muneswarathotakura I see TransferOrderItem has a commitInventory subrecord, and it looks like we don't have it available here: https://www.netsuite.com/help/helpcenter/en_US/srbrowser/Browser2016_1/schema/other/transferorderitem.html?mode=package.

Thanks Tyler.

For anyone who lands on this looking for a solution, I used a SuiteScript Hook...

I had to change my NetSuite import to SuiteScript version 1.

Updated my mapping to include the lot and qty for the batches in the item lines.

The following the example at SuiteScript-Hooks/postMap/itemLotNumbersImport.js at master · CeligoServices/SuiteScript-Hooks · GitHub adjusted the script as follows:

:small_blue_diamond: How the Script Works:

  1. Receives the options.postMapData Input
  • This data contains the Transfer Order details, including item lines with quantities and lot numbers.
  • Logs the raw input data before processing.
  1. Loops Through All Items in the Transfer Order
  • Extracts each item’s lot number and quantity.
  • If no lot number exists, logs an error and adds an error message.
  • Otherwise, it creates inventory detail lines for each lot number.
  1. Adds Inventory Detail to Each Item
  • Creates an inventorydetail object for items that need it.
  • Attaches the lot number(s) and quantities under inventoryassignment.
  1. Final Validation Before Submission
  • Checks that all items have inventorydetail properly mapped.
  • If any item is missing its inventory detail, it logs an error and throws an exception to prevent incorrect data from being submitted.
  1. Returns the Modified Data
  • If everything is correct, the script returns the modified Transfer Order data with the correct inventory details attached.

:small_blue_diamond: Key Features & Benefits

:white_check_mark: Handles Multiple Items – Ensures that every item in a Transfer Order gets the correct inventory details.
:white_check_mark: Error Logging & Debugging – Logs missing lot numbers and inventory detail issues for easy debugging.
:white_check_mark: Prevents Data Loss – Ensures that NetSuite retains inventory details before submitting the Transfer Order.
:white_check_mark: Ensures Compliance – If NetSuite ignores inventory details, the script stops the process and alerts the user.

var itemLotNumbersImport = function (options) {
var response = ;

nlapiLogExecution('AUDIT', '🔄 PostMap Options (Before Processing)', JSON.stringify(options));

for (var i = 0; i < options.postMapData.length; i++) {
    response.push({
        data: JSON.parse(JSON.stringify(options.postMapData[i])),
        errors: []
    });
}

try {
    nlapiLogExecution('AUDIT', '🔄 PostMap Data Before Modification', JSON.stringify(response));

    for (var i = 0; i < response.length; i++) {
        var configObject = response[i].data;
        var errors = response[i].errors;
        var itemLinesObject = configObject.nlobjSublistIds.item || {};
        var itemLines = itemLinesObject.lines || [];

        for (var j = 0; j < itemLines.length; ++j) {
            var isLotItem = itemLines[j].custcol_is_lot_item || 'T'; // Assume 'T' if missing
            var lotNumbers = itemLines[j].lot ? [{ lot: itemLines[j].lot, qty: itemLines[j].qty }] : [];

            nlapiLogExecution('DEBUG', '🔍 Lot Numbers Extracted for Item ' + j, JSON.stringify(lotNumbers));

            if (isLotItem === 'T') {
                if (!lotNumbers.length) {
                    errors.push({
                        code: 'NO_LOT_NUMBER',
                        message: 'No Lot Numbers found to import for item ' + j
                    });
                    nlapiLogExecution('ERROR', '❌ Missing Lot Number', 'Item ' + j + ': ' + JSON.stringify(itemLines[j]));
                } else {
                    var inventoryDetailLines = [];

                    for (var k = 0; k < lotNumbers.length; k++) {
                        inventoryDetailLines.push({
                            "quantity": lotNumbers[k].qty,
                            "issueinventorynumber": lotNumbers[k].lot
                        });
                        nlapiLogExecution('DEBUG', '✅ Processing Lot Number for Item ' + j, JSON.stringify(lotNumbers[k]));
                    }

                    itemLines[j].celigo_inventorydetail = {
                        "nlobjRecordType": "inventorydetail",
                        "nlobjFieldIds": { "celigo_recordmode_dynamic": true },
                        "nlobjSublistIds": {
                            "inventoryassignment": { "lines": inventoryDetailLines }
                        }
                    };

                    nlapiLogExecution('AUDIT', '✅ Mapped Inventory Detail for Item ' + j, JSON.stringify(itemLines[j].celigo_inventorydetail));
                }
            }

            delete itemLines[j].custcol_is_lot_item;
        }
    }

    nlapiLogExecution('AUDIT', '✅ PostMap Data After Modification', JSON.stringify(response));

} catch (e) {
    nlapiLogExecution('ERROR', '❌ Script Error', e.name + ': ' + e.message);
    for (var i = 0; i < response.length; i++) {
        response[i].data = null;
        response[i].errors.push({
            code: e.name,
            message: e.message
        });
    }
}

// 🚨 Final check before submission for all items
for (var i = 0; i < response.length; i++) {
    var record = response[i].data;
    var itemLines = record.nlobjSublistIds.item.lines || [];

    for (var j = 0; j < itemLines.length; j++) {
        if (!itemLines[j].celigo_inventorydetail || 
            !itemLines[j].celigo_inventorydetail.nlobjSublistIds.inventoryassignment.lines.length) {
            nlapiLogExecution('ERROR', '❌ NetSuite Ignored Inventory Detail Before Submission for Item ' + j, JSON.stringify(itemLines[j]));
            throw new Error('NetSuite did not retain inventorydetail on Transfer Order.');
        }
    }
}

return response;

};

There are also SS 2.0 examples here: SuiteScript-Hooks/SuiteScript2/PostMap at master · CeligoServices/SuiteScript-Hooks · GitHub. Glad you got this working!