You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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要求每一行必须指定项目才能提交。

修复步骤

  1. 移除排除字段中的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'
    ];
    
  2. 修正默认分支的赋值逻辑:
    默认分支中你给值添加了多余的单引号包裹,会导致数值类型(比如项目ID)被转成字符串,NetSuite无法识别,需要去掉包裹:

    } else {
      log.debug({
        title: '未指定类型的字段ID - ' + fieldId,
        details: firstItemKeyValue[fieldId]
      });
      salesOrder.setCurrentSublistValue({
        sublistId: 'item',
        fieldId: fieldId,
        value: firstItemKeyValue[fieldId] // 去掉多余的单引号
      });
    }
    
  3. 可选优化:字段类型判断效率
    可以用Set替代数组存储各类字段,提升查找效率,比如:

    const excludeFields = new Set([
      'commitinventory', 'createpo', ... // 原数组内容
    ]);
    // 判断时用excludeFields.has(fieldId)
    

内容的提问来源于stack exchange,提问作者agusgambina

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 16:49:53