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:
How the Script Works:
- 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.
- 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.
- Adds Inventory Detail to Each Item
- Creates an
inventorydetail
object for items that need it.
- Attaches the lot number(s) and quantities under
inventoryassignment
.
- 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.
- Returns the Modified Data
- If everything is correct, the script returns the modified Transfer Order data with the correct inventory details attached.
Key Features & Benefits
Handles Multiple Items – Ensures that every item in a Transfer Order gets the correct inventory details.
Error Logging & Debugging – Logs missing lot numbers and inventory detail issues for easy debugging.
Prevents Data Loss – Ensures that NetSuite retains inventory details before submitting the Transfer Order.
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;
};