空运报价明细改从 departureCharges 回填,并按计费重补全单价数量与金额。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+8
-4
@@ -21,7 +21,6 @@ import org.jeecg.modules.inquiry.template.QuoteTemplateExcelSupport;
|
||||
import org.jeecg.modules.inquiry.template.QuoteTemplateMatcher;
|
||||
import org.jeecg.modules.inquiry.template.QuoteTemplateObjectStore;
|
||||
import org.jeecg.modules.inquiry.template.QuoteTemplateSnapshotRenderer;
|
||||
import org.jeecg.modules.inquiry.tms.InquiryTmsQueryService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -866,6 +865,12 @@ public class InquiryQuoteTemplateServiceImpl extends ServiceImpl<InquiryQuoteTem
|
||||
if (rows.isEmpty()) {
|
||||
rows = QuoteTemplateExcelSupport.parseFeeRows(q.get("fee_rows"));
|
||||
}
|
||||
// 空运:已落库 quote 常只有一条 fee_lines,完整明细在 airOptions.departureCharges
|
||||
JSONArray fromAir = QuoteTemplateExcelSupport.parseAirDepartureChargeRows(
|
||||
q.get("airOptions"), q.get("air_options"), chargeable, currency);
|
||||
if (!fromAir.isEmpty() && (rows.isEmpty() || rows.size() < fromAir.size())) {
|
||||
rows = fromAir;
|
||||
}
|
||||
if (rows.isEmpty()) {
|
||||
String freight = first(q, "freight");
|
||||
if (StringUtils.isNotBlank(freight) && !"-".equals(freight)) {
|
||||
@@ -903,9 +908,8 @@ public class InquiryQuoteTemplateServiceImpl extends ServiceImpl<InquiryQuoteTem
|
||||
}
|
||||
lineMaps.add(m);
|
||||
}
|
||||
Map<String, Object> enrichCtx = new LinkedHashMap<>();
|
||||
enrichCtx.put("chargeableWeightKg", chargeable);
|
||||
InquiryTmsQueryService.enrichAirWeightFeeLines(lineMaps, enrichCtx);
|
||||
// 出单路径自带重算(单位/单价/数量/金额)
|
||||
QuoteTemplateExcelSupport.enrichAirWeightFeeRows(lineMaps, chargeable);
|
||||
JSONArray enriched = new JSONArray();
|
||||
for (Map<String, String> m : lineMaps) {
|
||||
// Fastjson 只要 Map<String,Object>,不能直接塞 Map<String,String>。
|
||||
|
||||
+240
-22
@@ -771,11 +771,12 @@ public final class QuoteTemplateExcelSupport {
|
||||
log.warn("费用区行数仍越过 stop,截断 region={} wrote={}/{}", region.getString("id"), r, rows.size());
|
||||
break;
|
||||
}
|
||||
// 单行黄底:保留模板占位文案,不覆盖
|
||||
if (rowLooksYellowFixed(sheet, rowIdx)) {
|
||||
continue;
|
||||
}
|
||||
// 不整行跳过:备注「发生时收取」常标黄,整行跳过会把费用名留成模板样例(如 ZUH)。
|
||||
// 黄格仍由 writeFeeCol 逐格跳过。
|
||||
JSONObject rowObj = rows.getJSONObject(r);
|
||||
writeFeeCol(sheet, rowIdx, columns.getString("project"),
|
||||
firstNonBlank(rowObj.getString("project"), rowObj.getString("segment")),
|
||||
yellow, sheet.getSheetName());
|
||||
writeFeeCol(sheet, rowIdx, columns.getString("name"), rowObj.getString("name"), yellow, sheet.getSheetName());
|
||||
writeFeeCol(sheet, rowIdx, columns.getString("unit"), rowObj.getString("unit"), yellow, sheet.getSheetName());
|
||||
writeFeeCol(sheet, rowIdx, columns.getString("quantity"), rowObj.getString("quantity"), yellow, sheet.getSheetName());
|
||||
@@ -812,6 +813,29 @@ public final class QuoteTemplateExcelSupport {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 明细行数少于模板占位时,清掉后面非黄底样例行(避免 ZUH/文件费残留)
|
||||
clearFeeRowsAfter(sheet, startRow + rows.size(), stopRow, columns, yellow, sheet.getSheetName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空 [fromRow, stopRow) 费用列;黄底固定行保留(如「发生时收取」占位)。
|
||||
*/
|
||||
private static void clearFeeRowsAfter(Sheet sheet, int fromRow, int stopRow, JSONObject columns,
|
||||
Set<String> yellow, String sheetName) {
|
||||
if (sheet == null || columns == null || fromRow >= stopRow) {
|
||||
return;
|
||||
}
|
||||
// 备注列常黄底「发生时收取」:不清 remark,只清费用数据列;黄格由 writeFeeCol 跳过
|
||||
String[] keys = {"project", "name", "unit", "quantity", "unit_price", "amount", "currency", "container_rate"};
|
||||
for (int r = fromRow; r < stopRow; r++) {
|
||||
for (String key : keys) {
|
||||
String colLetter = columns.getString(key);
|
||||
if (StringUtils.isBlank(colLetter)) {
|
||||
continue;
|
||||
}
|
||||
writeFeeCol(sheet, r, colLetter, "", yellow, sheetName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1466,7 +1490,8 @@ public final class QuoteTemplateExcelSupport {
|
||||
boolean needUnit = StringUtils.isBlank(out.getString("unit"));
|
||||
boolean needPrice = StringUtils.isBlank(out.getString("unit_price"));
|
||||
boolean needQty = StringUtils.isBlank(out.getString("quantity"));
|
||||
if (!needUnit && !needPrice && !needQty) {
|
||||
boolean needProject = StringUtils.isBlank(out.getString("project"));
|
||||
if (!needUnit && !needPrice && !needQty && !needProject) {
|
||||
return out;
|
||||
}
|
||||
Map<String, Integer> header = findFeeTableColumnsAbove(sheet, Math.max(dataStartRow, 0));
|
||||
@@ -1485,9 +1510,157 @@ public final class QuoteTemplateExcelSupport {
|
||||
if (needQty && header.get("quantity") != null) {
|
||||
out.put("quantity", CellReference.convertNumToColString(header.get("quantity")));
|
||||
}
|
||||
if (needProject && header.get("project") != null) {
|
||||
out.put("project", CellReference.convertNumToColString(header.get("project")));
|
||||
}
|
||||
if (StringUtils.isBlank(out.getString("name")) && header.get("name") != null) {
|
||||
out.put("name", CellReference.convertNumToColString(header.get("name")));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 空运 airOptions[].departureCharges → 费用行(出单用)。
|
||||
* 已落库 payload 常把 airOptions 存成 JSON 字符串。
|
||||
*/
|
||||
public static JSONArray parseAirDepartureChargeRows(Object airOptions, Object airOptionsAlt,
|
||||
String chargeableWeightKg, String headerCurrency) {
|
||||
JSONArray out = new JSONArray();
|
||||
Object raw = airOptions != null ? airOptions : airOptionsAlt;
|
||||
JSONArray opts = parseFeeRows(raw);
|
||||
if (opts.isEmpty()) {
|
||||
return out;
|
||||
}
|
||||
List<Map<String, String>> lines = new ArrayList<>();
|
||||
for (int i = 0; i < opts.size(); i++) {
|
||||
JSONObject opt = opts.getJSONObject(i);
|
||||
if (opt == null) {
|
||||
continue;
|
||||
}
|
||||
JSONArray charges = parseFeeRows(firstNonNull(opt.get("departureCharges"), opt.get("departure_charges"),
|
||||
opt.get("charges"), opt.get("feeItems")));
|
||||
String optCcy = firstNonBlank(opt.getString("currency"), headerCurrency);
|
||||
for (int c = 0; c < charges.size(); c++) {
|
||||
JSONObject ch = charges.getJSONObject(c);
|
||||
if (ch == null) {
|
||||
continue;
|
||||
}
|
||||
String name = firstNonBlank(ch.getString("chargeName"), ch.getString("feeName"), ch.getString("name"));
|
||||
String unitPrice = firstNonBlank(ch.getString("unitPrice"), ch.getString("unit_price"),
|
||||
ch.getString("salePrice"), ch.getString("amount"));
|
||||
if (StringUtils.isBlank(name) || StringUtils.isBlank(unitPrice)) {
|
||||
continue;
|
||||
}
|
||||
Map<String, String> line = new LinkedHashMap<>();
|
||||
line.put("name", name);
|
||||
line.put("amount", unitPrice);
|
||||
line.put("unit_price", unitPrice);
|
||||
// 空运模板「项目」列:运费归空运段,其它归起运港
|
||||
line.put("project", isAirWeightFeeName(name) ? "空运段" : "起运港");
|
||||
String unit = firstNonBlank(ch.getString("unit"), ch.getString("unitName"));
|
||||
if (StringUtils.isNotBlank(unit)) {
|
||||
line.put("unit", unit);
|
||||
}
|
||||
String ccy = firstNonBlank(ch.getString("currency"), optCcy);
|
||||
if (StringUtils.isNotBlank(ccy)) {
|
||||
line.put("currency", ccy);
|
||||
}
|
||||
lines.add(line);
|
||||
}
|
||||
if (!lines.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
enrichAirWeightFeeRows(lines, chargeableWeightKg);
|
||||
for (Map<String, String> line : lines) {
|
||||
out.add(JSON.parseObject(JSON.toJSONString(line)));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static Object firstNonNull(Object... vals) {
|
||||
if (vals == null) {
|
||||
return null;
|
||||
}
|
||||
for (Object v : vals) {
|
||||
if (v != null) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 空运运输费:单价 × 计费重 = 行金额。
|
||||
* 出单时调用;有单价+数量则重算金额(数值比较,避免 11.00≠11.0 漏算)。
|
||||
*/
|
||||
public static void enrichAirWeightFeeRows(List<Map<String, String>> lines, String chargeableWeightKg) {
|
||||
if (lines == null || lines.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
String chargeable = StringUtils.trimToEmpty(chargeableWeightKg);
|
||||
if (chargeable.isEmpty() || "-".equals(chargeable)) {
|
||||
return;
|
||||
}
|
||||
for (Map<String, String> line : lines) {
|
||||
if (line == null || !isAirWeightFeeName(line.get("name"))) {
|
||||
continue;
|
||||
}
|
||||
String unitPrice = firstNonBlank(line.get("unit_price"), line.get("unitPrice"));
|
||||
String qty = StringUtils.trimToEmpty(line.get("quantity"));
|
||||
String amount = StringUtils.trimToEmpty(line.get("amount"));
|
||||
String unit = StringUtils.trimToEmpty(line.get("unit"));
|
||||
if (qty.isEmpty()) {
|
||||
line.put("quantity", chargeable);
|
||||
qty = chargeable;
|
||||
}
|
||||
// TMS 常给「板数」;按计费重计价统一 KG
|
||||
line.put("unit", "KG");
|
||||
if (unitPrice.isEmpty() && !amount.isEmpty()) {
|
||||
line.put("unit_price", amount);
|
||||
unitPrice = amount;
|
||||
}
|
||||
if (!unitPrice.isEmpty() && !qty.isEmpty()) {
|
||||
String product = multiplyDecimal(unitPrice, qty);
|
||||
if (!product.isEmpty() && (amount.isEmpty() || !sameDecimal(amount, product))) {
|
||||
line.put("amount", product);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isAirWeightFeeName(String name) {
|
||||
String n = StringUtils.trimToEmpty(name);
|
||||
if (n.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (n.contains("提") || n.contains("送") || n.contains("人工") || n.contains("报关")
|
||||
|| n.contains("操作") || n.contains("文件") || n.contains("杂费")) {
|
||||
return false;
|
||||
}
|
||||
return n.contains("空运") || n.contains("运输费") || "运费".equals(n);
|
||||
}
|
||||
|
||||
static boolean sameDecimal(String left, String right) {
|
||||
try {
|
||||
java.math.BigDecimal a = new java.math.BigDecimal(left.trim().replace(",", ""));
|
||||
java.math.BigDecimal b = new java.math.BigDecimal(right.trim().replace(",", ""));
|
||||
return a.compareTo(b) == 0;
|
||||
} catch (Exception e) {
|
||||
return StringUtils.equals(left, right);
|
||||
}
|
||||
}
|
||||
|
||||
static String multiplyDecimal(String left, String right) {
|
||||
try {
|
||||
java.math.BigDecimal a = new java.math.BigDecimal(left.trim().replace(",", ""));
|
||||
java.math.BigDecimal b = new java.math.BigDecimal(right.trim().replace(",", ""));
|
||||
return a.multiply(b).stripTrailingZeros().toPlainString();
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 金额/数量/单价写成数字,SUM 才能加算;名称等仍写文本。
|
||||
*/
|
||||
@@ -1821,7 +1994,15 @@ public final class QuoteTemplateExcelSupport {
|
||||
}
|
||||
for (int c = 0; c < last; c++) {
|
||||
String text = StringUtils.trimToEmpty(readCellText(row.getCell(c)));
|
||||
if (isFeeNameHeader(text)) {
|
||||
String primary = text;
|
||||
int slash = text.indexOf('/');
|
||||
if (slash > 0) {
|
||||
primary = text.substring(0, slash).trim();
|
||||
}
|
||||
// 空运表头「项目 | 费用名称」两列:项目单独记,勿盖掉费用名称
|
||||
if ("项目".equals(primary) || "项目".equals(text) || text.startsWith("项目/")) {
|
||||
cols.put("project", c);
|
||||
} else if (isFeeNameHeader(text)) {
|
||||
cols.put("name", c);
|
||||
} else if (isContainerTypeHeader(text)) {
|
||||
cols.put(normalizeContainerType(text), c);
|
||||
@@ -2351,24 +2532,29 @@ public final class QuoteTemplateExcelSupport {
|
||||
if (row == null) {
|
||||
continue;
|
||||
}
|
||||
boolean feeHeader = !parseFeeHeaderRow(sheet, r).isEmpty();
|
||||
short lastCol = row.getLastCellNum();
|
||||
for (int c = 0; c < lastCol; c++) {
|
||||
Cell cell = row.getCell(c);
|
||||
if (cell == null) {
|
||||
continue;
|
||||
}
|
||||
String text = StringUtils.trimToEmpty(fmt.formatCellValue(cell));
|
||||
if (text.isEmpty() || isSectionTitle(sheet, cell) || isFeeTableHeader(text)) {
|
||||
continue;
|
||||
}
|
||||
String code = matchLabel(text.replace(":", ":").trim(), FILL_BY_LABEL);
|
||||
if (StringUtils.isBlank(code)) {
|
||||
continue;
|
||||
}
|
||||
if ("currency".equals(code) && feeHeader) {
|
||||
boolean feeHeader = !parseFeeHeaderRow(sheet, r).isEmpty();
|
||||
// 费用明细数据行:「起运港/目的港」是项目列值,不是询价标签;
|
||||
// 否则会把 pol=ZUH 写进右侧「费用名称」,盖掉提送货费/人工费。
|
||||
if (!feeHeader && isFeeDetailDataRow(sheet, r)) {
|
||||
continue;
|
||||
}
|
||||
short lastCol = row.getLastCellNum();
|
||||
for (int c = 0; c < lastCol; c++) {
|
||||
Cell cell = row.getCell(c);
|
||||
if (cell == null) {
|
||||
continue;
|
||||
}
|
||||
String text = StringUtils.trimToEmpty(fmt.formatCellValue(cell));
|
||||
if (text.isEmpty() || isSectionTitle(sheet, cell) || isFeeTableHeader(text)) {
|
||||
continue;
|
||||
}
|
||||
String code = matchLabel(text.replace(":", ":").trim(), FILL_BY_LABEL);
|
||||
if (StringUtils.isBlank(code)) {
|
||||
continue;
|
||||
}
|
||||
if ("currency".equals(code) && feeHeader) {
|
||||
continue;
|
||||
}
|
||||
String val = firstFact(facts, code, text.replace(":", "").replace(":", "").trim());
|
||||
if (StringUtils.isBlank(val) || "-".equals(val)) {
|
||||
continue;
|
||||
@@ -2404,6 +2590,38 @@ public final class QuoteTemplateExcelSupport {
|
||||
return text != null && (text.contains(":") || text.contains(":"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 费用明细表数据行(表头「费用名称/金额」之下、汇总行之上)。
|
||||
* 这些行的「起运港」是项目分段,不是询价单「起运港」标签。
|
||||
*/
|
||||
static boolean isFeeDetailDataRow(Sheet sheet, int rowIdx) {
|
||||
if (sheet == null || rowIdx < 0) {
|
||||
return false;
|
||||
}
|
||||
if (!parseFeeHeaderRow(sheet, rowIdx).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
Row row = sheet.getRow(rowIdx);
|
||||
if (row != null) {
|
||||
for (Cell cell : row) {
|
||||
if (cell == null) {
|
||||
continue;
|
||||
}
|
||||
String t = StringUtils.trimToEmpty(readCellText(cell));
|
||||
if (t.contains("预估费用") || t.contains("预估总价") || t.contains("费用总计")
|
||||
|| t.contains("报价合计") || t.contains("总销售价")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int r = rowIdx - 1; r >= Math.max(0, rowIdx - 40); r--) {
|
||||
if (!parseFeeHeaderRow(sheet, r).isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String matchLabel(String normalized, Map<String, String> dict) {
|
||||
if (dict == null || dict.isEmpty() || StringUtils.isBlank(normalized)) {
|
||||
return null;
|
||||
|
||||
+81
-7
@@ -527,6 +527,12 @@ public class InquiryTmsQueryService {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 空运:明细在 airOptions[].departureCharges(chargeName/unitPrice),不是顶层 feeItems
|
||||
List<Map<String, String>> airCharges = extractAirDepartureCharges(data, headerCurrency);
|
||||
if (!airCharges.isEmpty() && (lines.isEmpty() || lines.size() < airCharges.size())) {
|
||||
lines.clear();
|
||||
lines.addAll(airCharges);
|
||||
}
|
||||
if (lines.isEmpty()) {
|
||||
addFeeLine(lines, "空运费", firstText(data.get("airFreight"), data.get("freight"), freight), headerCurrency, null);
|
||||
addFeeLine(lines, "操作费", firstText(data.get("operateFee"), data.get("operationFee"), data.get("handlingFee")), headerCurrency, null);
|
||||
@@ -541,6 +547,64 @@ public class InquiryTmsQueryService {
|
||||
return JSON.toJSONString(lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* 空运舱位选项上的 departureCharges → 扁平费用行。
|
||||
* TMS 字段:chargeName / unitPrice / unit / currency;行金额出单时再按计费重补。
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static List<Map<String, String>> extractAirDepartureCharges(Map<String, Object> data, String headerCurrency) {
|
||||
List<Map<String, String>> out = new ArrayList<>();
|
||||
if (data == null) {
|
||||
return out;
|
||||
}
|
||||
Object raw = first(data.get("airOptions"), data.get("air_options"));
|
||||
if (raw instanceof String) {
|
||||
try {
|
||||
raw = JSON.parse(String.valueOf(raw));
|
||||
} catch (Exception e) {
|
||||
return out;
|
||||
}
|
||||
}
|
||||
if (!(raw instanceof List)) {
|
||||
return out;
|
||||
}
|
||||
for (Object optObj : (List<?>) raw) {
|
||||
if (!(optObj instanceof Map)) {
|
||||
continue;
|
||||
}
|
||||
Map<?, ?> opt = (Map<?, ?>) optObj;
|
||||
Object charges = first(opt.get("departureCharges"), opt.get("departure_charges"),
|
||||
opt.get("charges"), opt.get("feeItems"));
|
||||
if (!(charges instanceof List)) {
|
||||
continue;
|
||||
}
|
||||
String optCcy = firstText(opt.get("currency"), headerCurrency);
|
||||
for (Object one : (List<?>) charges) {
|
||||
if (!(one instanceof Map)) {
|
||||
continue;
|
||||
}
|
||||
Map<?, ?> ch = (Map<?, ?>) one;
|
||||
String name = firstText(ch.get("chargeName"), ch.get("feeName"), ch.get("name"));
|
||||
String unitPrice = firstText(ch.get("unitPrice"), ch.get("unit_price"), ch.get("salePrice"), ch.get("amount"));
|
||||
if (StringUtils.isBlank(name) || StringUtils.isBlank(unitPrice)) {
|
||||
continue;
|
||||
}
|
||||
String lineCcy = firstText(ch.get("currency"), optCcy, headerCurrency);
|
||||
Map<String, String> extras = new LinkedHashMap<>();
|
||||
putExtra(extras, "unit", firstText(ch.get("unit"), ch.get("unitName")));
|
||||
putExtra(extras, "unit_price", unitPrice);
|
||||
putExtra(extras, "project", isAirWeightFeeName(name) ? "空运段" : "起运港");
|
||||
// 先把单价写入 amount;运输费会在 enrich 里改成 单价×计费重
|
||||
addFeeLine(out, name, unitPrice, lineCcy, extras);
|
||||
}
|
||||
// 通常只用首个舱位选项的费用明细
|
||||
if (!out.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 空运运输费:单价 × 计费重 = 行金额。
|
||||
* <p>
|
||||
@@ -568,25 +632,35 @@ public class InquiryTmsQueryService {
|
||||
line.put("quantity", chargeable);
|
||||
qty = chargeable;
|
||||
}
|
||||
if (StringUtils.isBlank(unit)) {
|
||||
line.put("unit", "KG");
|
||||
}
|
||||
// TMS 常给单位「板数」;按计费重计价时统一写成 KG
|
||||
line.put("unit", "KG");
|
||||
// 有数量、无单价:当前 amount 当作单价(TMS 把费率塞进 salePrice/amount)
|
||||
if (StringUtils.isBlank(unitPrice) && StringUtils.isNotBlank(amount)) {
|
||||
line.put("unit_price", amount);
|
||||
unitPrice = amount;
|
||||
}
|
||||
// 单价与金额相同(或金额空):按 单价×数量 重算行金额
|
||||
if (StringUtils.isNotBlank(unitPrice) && StringUtils.isNotBlank(qty)
|
||||
&& (StringUtils.isBlank(amount) || amount.equals(unitPrice))) {
|
||||
// 有单价+数量:行金额必须是 单价×数量(勿用字符串 equals:11.00 ≠ 11.0 会漏算)
|
||||
if (StringUtils.isNotBlank(unitPrice) && StringUtils.isNotBlank(qty)) {
|
||||
String product = multiplyDecimal(unitPrice, qty);
|
||||
if (StringUtils.isNotBlank(product)) {
|
||||
if (StringUtils.isNotBlank(product)
|
||||
&& (StringUtils.isBlank(amount) || !sameDecimal(amount, product))) {
|
||||
line.put("amount", product);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 数值相等(忽略尾零与小数位写法差异)。 */
|
||||
static boolean sameDecimal(String left, String right) {
|
||||
try {
|
||||
java.math.BigDecimal a = new java.math.BigDecimal(left.trim().replace(",", ""));
|
||||
java.math.BigDecimal b = new java.math.BigDecimal(right.trim().replace(",", ""));
|
||||
return a.compareTo(b) == 0;
|
||||
} catch (Exception e) {
|
||||
return StringUtils.equals(left, right);
|
||||
}
|
||||
}
|
||||
|
||||
/** 空运按计费重的费目:运输费 / 空运费;不含提送货、人工、报关等固定费。 */
|
||||
public static boolean isAirWeightFeeName(String name) {
|
||||
String n = StringUtils.trimToEmpty(name);
|
||||
|
||||
+71
@@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -260,4 +261,74 @@ public class TmsQuoteCurrencyTest {
|
||||
assertTrue(quote.get("fee_lines").contains("\"amount\":\"6.0\""));
|
||||
assertTrue(quote.get("fee_lines").contains("\"amount\":\"123.0\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toAgentQuoteUsesAirDepartureCharges() {
|
||||
Map<String, Object> c1 = new HashMap<>();
|
||||
c1.put("chargeName", "运输费");
|
||||
c1.put("unitPrice", 11.0);
|
||||
c1.put("unit", "板数");
|
||||
c1.put("currency", "CNY");
|
||||
Map<String, Object> c2 = new HashMap<>();
|
||||
c2.put("chargeName", "提送货费");
|
||||
c2.put("unitPrice", 6.0);
|
||||
c2.put("unit", "板数");
|
||||
c2.put("currency", "CNY");
|
||||
Map<String, Object> c3 = new HashMap<>();
|
||||
c3.put("chargeName", "人工费");
|
||||
c3.put("unitPrice", 123.0);
|
||||
c3.put("unit", "柜");
|
||||
c3.put("currency", "CNY");
|
||||
Map<String, Object> option = new HashMap<>();
|
||||
option.put("optionNo", "AIR-OPT01");
|
||||
option.put("salePrice", 183370.0);
|
||||
option.put("currency", "CNY");
|
||||
option.put("chargeableWeightKg", 16670.0);
|
||||
option.put("departureCharges", java.util.Arrays.asList(c1, c2, c3));
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("quoteFound", true);
|
||||
data.put("totalSalePrice", "183370.0");
|
||||
data.put("currency", "CNY");
|
||||
data.put("chargeableWeightKg", "16670.0000");
|
||||
data.put("feeItems", Collections.emptyList());
|
||||
data.put("airOptions", Collections.singletonList(option));
|
||||
Map<String, Object> tms = new HashMap<>();
|
||||
tms.put("code", 1);
|
||||
tms.put("tmsData", data);
|
||||
Map<String, String> quote = InquiryTmsQueryService.toAgentQuote(tms);
|
||||
String fees = quote.get("fee_lines");
|
||||
assertTrue(fees.contains("运输费"));
|
||||
assertTrue(fees.contains("提送货费"));
|
||||
assertTrue(fees.contains("人工费"));
|
||||
assertTrue(fees.contains("\"amount\":\"183370\""));
|
||||
assertTrue(fees.contains("\"amount\":\"6.0\"") || fees.contains("\"amount\":\"6\""));
|
||||
assertTrue(fees.contains("\"amount\":\"123.0\"") || fees.contains("\"amount\":\"123\""));
|
||||
assertTrue(fees.contains("\"unit\":\"KG\""));
|
||||
assertTrue(fees.contains("\"quantity\":\"16670.0000\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enrichAirWeightRecalculatesWhenAmountLooksLikeRateWithDecimals() {
|
||||
// 单价已是 11.00、数量已是计费重,但金额仍是 11.00(字符串不等于 11.0 也要重算)
|
||||
Map<String, String> freight = new LinkedHashMap<>();
|
||||
freight.put("name", "运输费");
|
||||
freight.put("unit", "KG");
|
||||
freight.put("unit_price", "11.00");
|
||||
freight.put("quantity", "16670.00");
|
||||
freight.put("amount", "11.00");
|
||||
freight.put("currency", "CNY");
|
||||
Map<String, String> labor = new LinkedHashMap<>();
|
||||
labor.put("name", "人工费");
|
||||
labor.put("amount", "123.00");
|
||||
labor.put("currency", "CNY");
|
||||
List<Map<String, String>> lines = new ArrayList<>();
|
||||
lines.add(freight);
|
||||
lines.add(labor);
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("chargeableWeightKg", "16670.0000");
|
||||
InquiryTmsQueryService.enrichAirWeightFeeLines(lines, data);
|
||||
assertEquals("183370", lines.get(0).get("amount"));
|
||||
assertEquals("11.00", lines.get(0).get("unit_price"));
|
||||
assertEquals("123.00", lines.get(1).get("amount"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user