NetSuite SuiteLet复制销售订单行项报错:请选择要添加的项目
问题背景
我需要在销售订单中复制/克隆行项,目标是复制订单items子列表的首个行项。已通过UserEventScript添加了一个SuiteLet按钮来执行此操作,执行步骤如下:
- 获取销售订单
- 获取订单items子列表的字段
- 通过排除字段数组过滤不需要复制的字段,构建存储待复制属性的辅助对象
- 定义多个数组标记字段类型,以便复制值时进行对应处理
- 使用
salesOrder.selectNewLine({ sublistId: 'item' })添加新行 - 遍历辅助对象复制属性
- 尝试提交新行
错误信息
{"type":"error.SuiteScriptError","name":"USER_ERROR","message":"请选择要添加的项目","id":"","stack":["Error\n at RecordInvoker.commitLine (suitescript/resources/javascript/record/serverRecordService.js:278:5)\n at NetSuiteObject.thenableFunction() (suitescript/resources/javascript/record/proxy.js:115:24)\n at Object.onRequest (/SuiteScripts/Sales Orders/Suitelets/prepopulateLineItem.js:193:18)"],"cause":{"type":"internal error","code":"USER_ERROR","details":"请选择要添加的项目","userEvent":null,"stackTrace":["Error\n at RecordInvoker.commitLine (suitescript/resources/javascript/record/serverRecordService.js:278:5)\n at NetSuiteObject.thenableFunction() (suitescript/resources/javascript/record/proxy.js:115:24)\n at Object.onRequest (/SuiteScripts/Sales Orders/Suitelets/prepopulateLineItem.js:193:18)"],"notifyOff":false},"notifyOff":false,"userFacing":true}
现有代码
/** * @NApiVersion 2.1 * @NScriptType Suitelet * @NModuleScope SameAccount */ define(['N/record', 'N/log', 'N/redirect'], function (record, log, redirect) { function onRequest(scriptContext) { try { const request = scriptContext.request; const salesOrderID = request.parameters.id; const salesOrder = record.load({ type: record.Type.SALES_ORDER, id: salesOrderID, isDynamic: true }); const itemFields = salesOrder.getSublistFields({ sublistId: 'item' }); const excludeFields = [ 'commitinventory', 'createpo', 'custcol_rbs_date_auto_close', 'custcol_ship_date_act', 'custcolcust_linenum', 'id', 'item', 'line', 'linenumber', 'lineuniquekey', 'olditemid', 'sys_id', 'sys_parentid', 'unitconversionrate', 'units', 'unitslist' ]; var firstItemKeyValue = {}; itemFields.forEach((fieldId) => { if (excludeFields.indexOf(fieldId) !== -1) return; const value = salesOrder.getSublistValue({ sublistId: 'item', fieldId: fieldId, line: 0 }); if (value === null || value === undefined || value === '') return; log.debug({ title: 'fieldId - ' + fieldId, details: value }); firstItemKeyValue[fieldId] = value; }); const textFields = [ 'class', 'class_display', 'createdpo', 'custcol_4601_witaxamount', 'custcol_4601_witaxbaseamount', 'custcol_category_salesforce_name', 'custcol_item_type', 'custcol_material_quality', 'description', 'item_display', 'itemtype', 'location_display', 'pocurrency', 'units_display' ]; const dateFields = [ 'createddate', 'ddistrib', 'prevdate', 'recordcreateddate', 'saleseffectivedate' ]; const booleanFields = [ 'amounthasbeenset', 'backordered', 'billvariancestatusallbook', 'custcol_2663_isperson', 'custcol_2663_isperson', 'custcol_4601_witaxapplies', 'dropshiporderhasbeenshiprecv', 'dropshipwarningdisplayed', 'fulfillable', 'generateaccruals', 'groupclosed', 'includegroupwrapper', 'isclosed', 'isdropshiporderline', 'islinefulfilled', 'islinefulfilled', 'isnoninventory', 'isopen', 'isposting', 'isspecialorderline', 'istaxable', 'itempacked', 'itempicked', 'linked', 'linked', 'linkeddropship', 'linkedordbill', 'linkedshiprcpt', 'noprint', 'pomarginal', 'povendor_display', 'printitems' ]; const integerFields = [ 'amount', 'createdpo', 'custcol_freight_estimate_amount', 'custcol_material_form', 'custcol_material_non_material', 'custcol_material_quality', 'custcol_material_type', 'custcol_statistical_value_base_curr', 'initquantity', 'location', 'onorder', 'origlocation', 'origquantity', 'origunits', 'poid', 'povendor', 'quantity', 'quantityavailable', 'quantitybilled', 'quantityfulfilled', 'rate' ]; const floatFields = ['porate']; const filteredItemFields = Object.keys(firstItemKeyValue); salesOrder.selectNewLine({ sublistId: 'item' }); salesOrder.setCurrentSublistValue({ sublistId: 'item', fieldId: 'description', value: 'PLEASE UPDATE DESCRIPTION' }); filteredItemFields.forEach((fieldId) => { if (textFields.indexOf(fieldId) !== -1) { salesOrder.setCurrentSublistValue({ sublistId: 'item', fieldId: fieldId, value: firstItemKeyValue[fieldId] }); } else if (dateFields.indexOf(fieldId) !== -1) { salesOrder.setCurrentSublistValue({ sublistId: 'item', fieldId: fieldId, value: new Date(firstItemKeyValue[fieldId]) }); } else if (booleanFields.indexOf(fieldId) !== -1) { salesOrder.setCurrentSublistValue({ sublistId: 'item', fieldId: fieldId, value: String(firstItemKeyValue[fieldId]).toLowerCase() === 'true' || String(firstItemKeyValue[fieldId]).toLowerCase() === 't' }); } else if (integerFields.indexOf(fieldId) !== -1) { salesOrder.setCurrentSublistValue({ sublistId: 'item', fieldId: fieldId, value: parseInt(firstItemKeyValue[fieldId]) }); } else if (floatFields.indexOf(fieldId) !== -1) { salesOrder.setCurrentSublistValue({ sublistId: 'item', fieldId: fieldId, value: parseFloat(firstItemKeyValue[fieldId]) }); } else { log.debug({ title: 'without a type fieldId - ' + fieldId, details: "'" + firstItemKeyValue[fieldId] + "'" }); salesOrder.setCurrentSublistValue({ sublistId: 'item', fieldId: fieldId, value: "'" + firstItemKeyValue[fieldId] + "'" }); } }); log.debug({ title: 'salesOrder', details: salesOrder }); salesOrder.commitLine({ sublistId: 'item' }); salesOrder.save({ enableSourcing: false, ignoreMandatoryFields: false }); const lastItem = salesOrder.getSublistValue({ sublistId: 'item', fieldId: 'item', line: salesOrder.getLineCount({ sublistId: 'item' }) - 1 }); redirect.redirect({ url: '/app/common/item/item.nl?id=' + lastItem.id + '&e=T' }); } catch (error) { log.debug({ title: 'error', details: JSON.stringify(error) }); } } return { onRequest }; });
问题原因及修复方案
核心原因
错误提示的本质是新行未设置必填的item字段——你在excludeFields数组里把item字段排除了,导致复制过程中没有把原行的项目ID同步到新行,NetSuite要求每一行必须指定项目才能提交。
修复步骤
移除排除字段中的
item:
从excludeFields数组中删除'item',让代码可以复制原行的项目ID到新行:const excludeFields = [ 'commitinventory', 'createpo', 'custcol_rbs_date_auto_close', 'custcol_ship_date_act', 'custcolcust_linenum', 'id', // 移除'item'字段 'line', 'linenumber', 'lineuniquekey', 'olditemid', 'sys_id', 'sys_parentid', 'unitconversionrate', 'units', 'unitslist' ];修正默认分支的赋值逻辑:
默认分支中你给值添加了多余的单引号包裹,会导致数值类型(比如项目ID)被转成字符串,NetSuite无法识别,需要去掉包裹:} else { log.debug({ title: '未指定类型的字段ID - ' + fieldId, details: firstItemKeyValue[fieldId] }); salesOrder.setCurrentSublistValue({ sublistId: 'item', fieldId: fieldId, value: firstItemKeyValue[fieldId] // 去掉多余的单引号 }); }可选优化:字段类型判断效率
可以用Set替代数组存储各类字段,提升查找效率,比如:const excludeFields = new Set([ 'commitinventory', 'createpo', ... // 原数组内容 ]); // 判断时用excludeFields.has(fieldId)
内容的提问来源于stack exchange,提问作者agusgambina

