本页约定(每个示例统一结构)
- 场景:这段脚本解决什么问题
- 配置位置 / 触发时机:在哪里配、什么时候执行
- 脚本:可直接粘贴的代码
- 说明:注意事项 / 可调整点
通用红线(所有脚本都适用)
- 脚本是 JavaScript(GraalJS 引擎):循环用
for (var i = 0; ...),不要写 Java 的int i或增强 forfor (var x : list),否则报语法错。- 不要在最外层声明具名函数(
function foo(){})。引擎上下文按线程复用、直到本次请求结束才清理,最外层声明的函数和最外层的var变量一样会残留,可能被同一线程后续执行的别的业务脚本看到,导致数据错乱。要复用就内联重复写。- 需要提前退出、或想让变量不残留时,用立即执行函数(IIFE):
(function () { ... })()。它是表达式、不产生任何全局绑定,内部变量执行完即消失,比在最外层写var更干净。- 绝不能在最外层直接写
return(return "xxx";写在脚本第一层)。引擎不允许,会直接SyntaxError: Invalid return statement;而这个语法错误又会被框架吞掉——脚本等于没执行,业务照常放行,且没有任何提示。要return就必须包在上面那种 IIFE 里。(例外:第四节的仓租脚本是用return返回金额的,见该节说明。)- 上述几条均已在实际环境实测:最外层
function foo(){}与最外层var会被下一次脚本读到;IIFE 内部变量读不到;最外层return报语法错。- 集合遍历用
list.size()/list.get(i),不要用includes()(用indexOf(...) !== -1)。- 改字段用 getter/setter(如
$waybill.getShenBaoXinXiList())。
配置入口:转单 API → 执行脚本。分两个触发点:
- 打单前(推单前):取号前执行,对象是
$waybill,用来补全 / 校验 / 改写要推给代理的数据;返回非空字符串可中断打单。- 打单后(推单后):取号成功后执行,可拿到
$succeed、换单号等,用来回填 / 生成轨迹 / 推送,返回值不影响打单结果。
// 预报保额传 0;若要用收货的值,把 YuBao 改成 ShouHuo
$waybill.getWaybillExtraEntity().getExtraJson().put("insuranceCharges", 0);
+1 814-537-7154→8145377154。if ($waybill.getShouJianRenDianHua() != null) {
var phone = $waybill.getShouJianRenDianHua();
var digitsOnly = phone.replaceAll("[^0-9]", ""); // 移除非数字字符
if (digitsOnly.length() == 10) {
$waybill.setShouJianRenDianHua(digitsOnly);
} else if (digitsOnly.length() == 11) {
$waybill.setShouJianRenDianHua(digitsOnly.substring(1)); // 取后 10 位
} else if (digitsOnly.length() == 16) {
$waybill.setShouJianRenDianHua(digitsOnly.substring(1, 11)); // 取第 2–11 位
} else if (digitsOnly.length() == 15) {
$waybill.setShouJianRenDianHua(digitsOnly.substring(0, 10)); // 取前 10 位
} else {
$waybill.setShouJianRenDianHua(digitsOnly); // 其他长度保留数字
}
}
var SplitterUtils = Java.type('com.itdida.core.utils.SplitterUtils');
var StringUtils = Java.type('com.itdida.core.utils.StringUtils');
var originalRecipientAddress =
StringUtils.defaultString($waybill.getShouJianRenDiZhi1()) + " " +
StringUtils.defaultString($waybill.getShouJianRenDiZhi2()) + " " +
StringUtils.defaultString($waybill.getShouJianRenDiZhi3());
originalRecipientAddress = originalRecipientAddress.trim();
if (StringUtils.isNotBlank(originalRecipientAddress)) {
var addressList = SplitterUtils.partition2List(originalRecipientAddress, 50, true);
$waybill.setShouJianRenDiZhi1(addressList[0]);
$waybill.setShouJianRenDiZhi2(addressList.length > 1 ? addressList[1] : null);
$waybill.setShouJianRenDiZhi3(addressList.length > 2 ? addressList[2] : null);
}
if ($waybill.getShouJianRenDiZhi3() != null && !$waybill.getShouJianRenDiZhi3().isEmpty()) {
$waybill.setShouJianRenDiZhi2($waybill.getShouJianRenDiZhi2() + " " + $waybill.getShouJianRenDiZhi3());
}
var rep = $waybill.getWaybillExtraEntity();
rep.setRecipientAddress1(rep.getRecipientAddress3());
rep.setRecipientAddress2(null);
rep.setRecipientAddress3(null);
// 智能分析并二次规范日本地址
var result = $jsService.getBean('addressService').japanVerify($waybill);
$yunDanService.verificationAddressByJapan(null, $waybill, result.getData());
$waybill.setRecipientInfo(null);
// 地址 1 超 45 的部分转地址 2,地址 2 超长再转地址 3
var rep = $waybill.getRecipientInfo();
rep.setAddress1(rep.getAddress3());
rep.setAddress2(null);
rep.setAddress3(null);
if (rep.getAddress1() != null && rep.getAddress1().length() > 45) {
rep.setAddress2(rep.getAddress1().substring(45));
rep.setAddress1(rep.getAddress1().substring(0, 45));
}
if (rep.getAddress2() != null && rep.getAddress2().length() > 45) {
rep.setAddress3(rep.getAddress2().substring(45));
rep.setAddress2(rep.getAddress2().substring(0, 45));
}
// 地址 1 半角数字转全角
var halfToFullWidthMap = {
'0': '0', '1': '1', '2': '2', '3': '3', '4': '4',
'5': '5', '6': '6', '7': '7', '8': '8', '9': '9'
};
var fullWidthStr = '';
var addr1 = rep.getAddress1();
for (var i = 0; i < addr1.length(); i++) {
var ch = addr1.charAt(i);
fullWidthStr += halfToFullWidthMap[ch] || ch;
}
rep.setAddress1(fullWidthStr);
// 邮编去横杠
if (rep.getPostcode() != null) {
rep.setPostcode(rep.getPostcode().replaceAll("-", ""));
}
// 运费支付方式固定为寄付(预付)
$waybill.setYunFeiZhiFuFangShi(1);
// 电话规范化(去 +81/0081、按号段补足位数)
var util = $jsService.getBean('commonFunctionService');
rep.setTelNo(util.processPhoneNumber(rep.getTelNo()));
说明:原示例此处
rep.getAddress1()[i](数组下标)和rep.getTelNo(漏括号)在 GraalJS 下会出错,已改为charAt(i)与getTelNo()。
var halfToFullWidthMap = {
'0': '0', '1': '1', '2': '2', '3': '3', '4': '4',
'5': '5', '6': '6', '7': '7', '8': '8', '9': '9'
};
var waybillExtra = $waybill.getWaybillExtraEntity();
// 通用半角转全角(内联,不抽函数)
var addr1 = '';
var s1 = waybillExtra.getRecipientAddress1();
for (var i = 0; i < s1.length(); i++) { var c = s1.charAt(i); addr1 += halfToFullWidthMap[c] || c; }
waybillExtra.setRecipientAddress1(addr1);
var addr2 = '';
var s2 = waybillExtra.getRecipientAddress2();
for (var i = 0; i < s2.length(); i++) { var c = s2.charAt(i); addr2 += halfToFullWidthMap[c] || c; }
waybillExtra.setRecipientAddress2(addr2);
var st = '';
var s3 = waybillExtra.getRecipientState();
for (var i = 0; i < s3.length(); i++) { var c = s3.charAt(i); st += halfToFullWidthMap[c] || c; }
waybillExtra.setRecipientState(st);
var ct = '';
var s4 = waybillExtra.getRecipientCity();
for (var i = 0; i < s4.length(); i++) { var c = s4.charAt(i); ct += halfToFullWidthMap[c] || c; }
waybillExtra.setRecipientCity(ct);
// 二次校验并改写
var result = $jsService.getBean('addressService').japanVerify($waybill);
$yunDanService.verificationAddressByJapan(null, $waybill, result.getData());
$waybill.setRecipientInfo(null);
waybillExtra.setRecipientAddress1(waybillExtra.getRecipientAddress3());
waybillExtra.setRecipientAddress2(null);
waybillExtra.setRecipientAddress3(null);
// 不推省州、城市
waybillExtra.setRecipientState(null);
waybillExtra.setRecipientCity(null);
// 邮编去横杠
if (waybillExtra.getRecipientPostcode() != null) {
waybillExtra.setRecipientPostcode(waybillExtra.getRecipientPostcode().replaceAll("-", ""));
}
// 运费支付方式固定为预付
$waybill.setYunFeiZhiFuFangShi(1);
// 电话规范化
var util = $jsService.getBean('commonFunctionService');
waybillExtra.setRecipientTel(util.processPhoneNumber(waybillExtra.getRecipientTel()));
var ObjConvertUtils = Packages.com.itdida.core.utils.ObjConvertUtils;
var chang = ObjConvertUtils.toBigDecimal("1");
var kuan = ObjConvertUtils.toBigDecimal("10");
var gao = ObjConvertUtils.toBigDecimal("15");
var list = $waybill.getYunDanDanJianEntityList();
for (var i = 0; i < list.size(); i++) {
var dj = list.get(i);
if (dj.getApiLength() != null && dj.getApiLength().doubleValue() == 0) {
dj.setApiLength(dj.getYuBaoChang());
if (dj.getApiLength() != null && dj.getApiLength().doubleValue() == 0) { dj.setApiLength(chang); }
}
if (dj.getApiWidth() != null && dj.getApiWidth().doubleValue() == 0) {
dj.setApiWidth(dj.getYuBaoKuan());
if (dj.getApiWidth() != null && dj.getApiWidth().doubleValue() == 0) { dj.setApiWidth(kuan); }
}
if (dj.getApiHeight() != null && dj.getApiHeight().doubleValue() == 0) {
dj.setApiHeight(dj.getYuBaoGao());
if (dj.getApiHeight() != null && dj.getApiHeight().doubleValue() == 0) { dj.setApiHeight(gao); }
}
if (dj.getApiWeight() != null && dj.getApiWeight().doubleValue() == 0) {
dj.setApiWeight(dj.getYuBaoShiZhong());
}
}
说明:原示例用了 Java 增强 for
for (var x : list),GraalJS 不支持,已改为下标遍历。
var ObjConvertUtils = Packages.com.itdida.core.utils.ObjConvertUtils;
var length = ObjConvertUtils.toBigDecimal(19);
var width = ObjConvertUtils.toBigDecimal(15);
var height = ObjConvertUtils.toBigDecimal(10);
var list = $waybill.getYunDanDanJianEntityList();
for (var i = 0; i < list.size(); i++) {
list.get(i).setApiLength(length);
list.get(i).setApiWidth(width);
list.get(i).setApiHeight(height);
}
说明:原示例标题写「19×15×10」,但代码里实际赋的是 15/1/11,自相矛盾。此处按标题取 长 19、宽 15、高 10;请按你的实际需求改这三个值。
var list = $waybill.getYunDanDanJianEntityList();
for (var i = 0; i < list.size(); i++) {
var dj = list.get(i);
dj.setApiLength(null);
dj.setApiWidth(null);
dj.setApiHeight(null);
}
if ($waybill.getBaoGuoLeiXing() == 0 || $waybill.getBaoGuoLeiXing() == 2) {
$waybill.getYunDanDanJianEntityList().clear();
}
$waybill.setLabelJiFeiZhong(进位($waybill.getLabelJiFeiZhong(), 0.5));
说明:
进位(值, 步进)是公共脚本库函数,可直接调用。
var baoXinXiList = $waybill.getShenBaoXinXiList();
var shenBaoSize = Packages.com.itdida.core.utils.ObjConvertUtils.toBigDecimal(baoXinXiList.size());
var avg = $waybill.getYuBaoShiZhong().divide(shenBaoSize);
for (var i = 0; i < baoXinXiList.size(); i++) {
baoXinXiList.get(i).setUnitNetWeight(avg);
}
说明:若要按预报计费重而非实重分摊,把
getYuBaoShiZhong()换成getYuBaoJiFeiZhong()。
var list = $waybill.getShenBaoXinXiList();
for (var i = 0; i < list.size(); i++) {
var sd = list.get(i);
sd.setNameEn(sd.getSku());
}
var list = $waybill.getShenBaoXinXiList();
for (var i = 0; i < list.size(); i++) {
var item = list.get(i);
item.setSku(item.getNameCn());
}
var list = $waybill.getShenBaoXinXiList();
for (var i = 0; i < list.size(); i++) {
var sd = list.get(i);
if ("磷酸铁锂电池".equals(sd.getNameCn())) { sd.setNameCn("文件盒"); }
if ("Lithium iron phosphate battery".equals(sd.getNameEn())) { sd.setNameEn("File box"); }
}
var list = $waybill.getShenBaoXinXiList();
for (var i = 0; i < list.size(); i++) {
var sb = list.get(i);
if (sb.getDeclarationHsCode() == null || sb.getDeclarationHsCode() == '') {
sb.setDeclarationHsCode('3307410000');
}
}
var list = $waybill.getShenBaoXinXiList();
for (var i = 0; i < list.size(); i++) {
var sd = list.get(i);
var danjia = sd.getDanJia();
sd.setDanJia(Packages.com.itdida.core.utils.ObjConvertUtils.toBigDecimal(danjia < 10 ? 1000 : danjia * 100));
}
var fbaReferenceNo = $waybill.getExtraJson().getString("fbaReferenceNo");
var list = $waybill.getShenBaoXinXiList();
if (fbaReferenceNo != null && fbaReferenceNo.trim() != '') {
for (var i = 0; i < list.size(); i++) {
list.get(i).setPurchaseOrderNumber(fbaReferenceNo);
}
}
$waybill.setShenBaoBiZhong("EUR");
$waybill.setShenBaoJiaZhi(Packages.com.itdida.core.utils.ObjConvertUtils.toBigDecimal(20));
$yunDanService.setShenBaoXinXiSku($waybill, "0");
+1 123-444-4259 ext.06595,只取前 15 位 +1 123-444-4259(含空格)。if ($waybill.getShouJianRenDianHua() != null && $waybill.getShouJianRenDianHua().length() > 15) {
$waybill.setShouJianRenDianHua($waybill.getShouJianRenDianHua().substring(0, 15));
}
+81 和空格自动清除。if ($waybill.getShouJianRenDianHua() != null) {
$waybill.setShouJianRenDianHua($waybill.getShouJianRenDianHua().replaceAll("\\+81", "").replaceAll(" ", ""));
}
说明:
replaceAll第一个参数是正则,+是元字符,需转义成\\+81。
$waybill.setPeiHuoXinXi(null);
if ($waybill.getFuShuiJinType() == 2) {
$waybill.setDiSanFangFuShuiJinZhangHao("xxxx");
$waybill.getTaxPersonInfo().setCountryCode("US");
}
var clientCode = $waybill.getKeHuCode();
var country = $waybill.getGuoJiaDaiMa();
var sold = $waybill.getWaybillExtraEntity();
if (clientCode != null && clientCode == 'F002208' && country == 'us') {
sold.setSoldPersonName("XXXX");
}
// 完整进口商字段见下一条
var sold = $waybill.getWaybillExtraEntity();
sold.setSoldPersonName("XXXX");
sold.setSoldCompanyName("xxx");
sold.setSoldTaxNo("111");
sold.setSoldTelNo("22233445");
sold.setSoldAddress1("2220 xxxxx xxxx");
sold.setSoldAddress2("Unit 688-AP/Back door 68,22,22");
sold.setSoldAddress3("111");
sold.setSoldCountryCode("Canada");
sold.setSoldProvinceCode("ON"); // 安大略省
sold.setSoldCity("Scarborough");
sold.setSoldPostalCode("M1xxxx");
sold.getExtraJson().put("soldToEmail", "111@qq.com");
// 同时不推配货信息
$waybill.setPeiHuoXinXi(null);
var CollUtil = Java.type('cn.hutool.core.collection.CollUtil');
var waybill = $waybill;
var reLoad = true;
if (waybill.getJianShu() == 1 && waybill.getZhuanDanApiEntity().getMoBanDataMap().intValue("service_type") == 0) {
var list = waybill.getShenBaoXinXiList();
var skuSet = {}; // 用对象当集合判重,避免 new Set()
var skuKinds = 0;
for (var j = 0; j < list.size(); j++) {
var sbxx = list.get(j);
if (!skuSet[sbxx.getSku()]) { skuSet[sbxx.getSku()] = true; skuKinds++; }
if (sbxx.getUnitGrossWeight() < 10) { reLoad = false; } // 有单件 <10KG 不满足
}
if (skuKinds > 1) { reLoad = false; } // 混装不满足
if (reLoad) {
waybill.setJianShu(1);
waybill.setShenBaoXinXiList(CollUtil.sub(list, 0, 1)); // 只留第一条
waybill.getShenBaoXinXiList().get(0).setShuLiang(1);
}
}
说明:
CollUtil需先Java.type引入;原示例用了 JSnew Set()/.size,引擎下不稳,已改为对象判重。请在测试环境验证后再上线。
consigneeNameEng;转写失败直接拦截打单,不回退到普通机器翻译、也不把日文原样发出去。// 日文收件人姓名 → 英文名(罗马字),失败时返回错误信息以拦截打单
(function () {
try {
var name = $waybill.getShouJianRenXingMing();
if (name == null || String(name).trim() == "") {
return "";
}
$apiEntity.put("consigneeNameEng",
$jsService.getBean("japaneseNameRomanizeService").romanize(String(name).trim()));
return "";
} catch (e) {
return "收件人姓名转写失败,已拦截打单:" + e.message;
}
})()
说明:这是本页顶部红线里「用 IIFE 包住、靠
return中断」的标准写法。取姓名、取服务这两句也必须放在try里——框架会吞掉脚本抛出的异常只记一条运单日志就继续打单,只有返回非空字符串才拦得住。同一个收件人姓名默认缓存 3 天,重复打单不会重复调用 AI。
if ($succeed) {
var StringUtils = Java.type("com.itdida.core.utils.StringUtils");
var desc = 'United States, in transit via fedex tracking number ' + StringUtils.blankToEmpty($waybill.getHuanDanHao());
自动添加轨迹(desc);
}
说明:
自动添加轨迹(描述)是公共脚本库函数。
$commonFunctionService.swapDanHaoAndWriteInZiZhuanDanHao($yunDanEntity);
$model)配置入口:抓单 / 收货渠道对接配置 → 接单前执行脚本(如:菜单-抓单对接配置 → 双击对应渠道条目 → 执行脚本)。
触发时机:电商平台 / 客户 token 通过 API 把订单推进来、建 / 更新运单时执行。对象是$model(客户推送的运单数据)。
⚠️ 接单前脚本里throw异常会被系统吞掉、拦不住推单,只能用来加工数据。
$model.setIoss($model.getRecipientVat());
var list = $model.getShenBaoXinXiList();
for (var i = 0; i < list.size(); i++) {
var item = list.get(i);
item.setFnsku(item.getSku());
item.setSku(null);
}
var list = $model.getDanJianList();
for (var i = 0; i < list.size(); i++) {
var dj = list.get(i);
if (dj.getChang() == null || dj.getChang().doubleValue() < 15) { dj.setChang(new java.math.BigDecimal(15)); }
if (dj.getKuan() == null || dj.getKuan().doubleValue() < 11) { dj.setKuan(new java.math.BigDecimal(11)); }
if (dj.getGao() == null || dj.getGao().doubleValue() < 1) { dj.setGao(new java.math.BigDecimal(1)); }
}
说明:若要改预报的值,把
ShouHuo相关 getter/setter 换成YuBao系列。原示例用了 Java 的int i,已改为var i。
配置入口:对应价格 → 查价前执行。对象是
$request(查价请求参数)。
拒绝查价用内联throw new com.itdida.core.exception.ScriptRejectException('提示')。
if ($request.getParamsModel().getPieceCount() > 5 || $request.getParamsModel().getTotalPieceCount() > 5) {
throw new com.itdida.core.exception.ScriptRejectException('itdidaRejectedExecutionException');
}
说明:原示例声明了
function throwCustomException(){...}再调用——这是错误写法。脚本里声明函数会串到别的业务脚本导致数据错乱,正确做法是直接内联throw(如上)。
var unitModelList = $request.getParamsModel().getUnitModelList();
if (unitModelList != null) {
for (var i = 0; i < unitModelList.size(); i++) {
var u = unitModelList.get(i);
var ZERO = Packages.java.math.BigDecimal.ZERO;
if (u.getLength() == null || u.getLength().compareTo(ZERO) <= 0) { u.setLength(Packages.com.itdida.core.utils.ObjConvertUtils.toBigDecimal(1)); }
if (u.getWidth() == null || u.getWidth().compareTo(ZERO) <= 0) { u.setWidth(Packages.com.itdida.core.utils.ObjConvertUtils.toBigDecimal(1)); }
if (u.getHeight() == null || u.getHeight().compareTo(ZERO) <= 0) { u.setHeight(Packages.com.itdida.core.utils.ObjConvertUtils.toBigDecimal(1)); }
}
}
return 仓租金额)配置入口:仓租计划任务 → 计算公式,并在计划里设置币种。
与其他脚本不同:仓租脚本用return返回金额。
⚠️ 体积函数返回的是 mm³(立方毫米),换算成方数(m³)要/1000000。计划任务按天跑,库龄只用于分价 / 判断免租。
var age = 库龄();
var volume = Math.max(运单外箱体积M3() / 1000000, 1);
var price = 200;
return price * volume;
var age = 库龄();
var price = 150;
var volume = Math.ceil(箱内产品体积M3() / 1000000);
return price * volume;
var age = 库龄();
var volume = 箱内产品体积M3() / 1000000;
var price = 55;
var includeClient = ['实际客户编号'];
if (includeClient.indexOf(clientCode) == -1) {
return 0;
}
return price * volume;
var age = 库龄();
var volume = 箱内产品体积M3() / 1000000;
var price = 0;
if (age > 30) { price = 0.6; }
if (age > 90) { price = 1; }
if (age > 180) { price = 2; }
return price * volume;
var age = 库龄();
var volume = 整箱外体积M3() / 1000000;
var price = 200;
return price * volume;
说明:免租判断由计划任务的免租设置控制;脚本只算计费部分。
var age = 库龄();
var price = 0;
var volume = Math.ceil(箱内产品体积M3() / 1000000); // 不足 1 方按 1 方
if (age > 30 && age <= 90) { price = 1.7; }
else if (age > 90 && age <= 180) { price = 1.95; }
else if (age > 180 && age <= 270) { price = 2.30; }
else if (age > 270) { price = 3; }
return price * volume;
这几项常被误放进「脚本」,其实是自定义功能 / 模板表达式(单行表达式,不是多行 JS 脚本)。若有独立的「公式」文档,建议迁移过去。
keHuDanHao.replace("+","")
T(com.itdida.core.utils.StringUtils).join(shenBaoSkuList,'-')
运单属性('(shouJianRenDiZhi1 != null && shouJianRenDiZhi1.length()>20)||(shouJianRenDiZhi2 != null && shouJianRenDiZhi2.length()>20)')
T(org.apache.commons.lang.StringUtils).defaultString(jieSuanKeHuEntity != null ? jieSuanKeHuEntity.code : "") + '-' +
T(org.apache.commons.lang.StringUtils).defaultString(jieSuanKeHuEntity != null ? jieSuanKeHuEntity.name : "") + '-' +
(billStartTime != null ? T(org.apache.commons.lang.time.DateFormatUtils).format(billStartTime, "yyyyMMdd") : "")
var action = Packages.com.itdida.core.models.waybill.WaybillActionEnum.推送出仓单;
var checkStatus = $constant.CAO_ZUO_ZHUANG_TAI_DAI_FA_HUO;
if (action == $waybill.getCurrentAction() && $waybill.getCaoZuoZhuangTai() > checkStatus) {
'运单已发货,终止同步出仓单';
}
说明:返回非空字符串即中断推单,提示语就是返回的字符串。