提交
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
package com.baiyue.lastleg.export;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** 导出按任务类型及字段转换中文;仓码、SKU、币种与自由文本原样保留。 */
|
||||
final class LastlegExportLabels {
|
||||
/** 中文导出映射仅提供静态转换。 */
|
||||
private LastlegExportLabels() { }
|
||||
private static final Map<String, String> WORKFLOW = Map.ofEntries(
|
||||
Map.entry("PENDING_INBOUND", "待入库"),
|
||||
Map.entry("PENDING_REVIEW", "待审核"),
|
||||
Map.entry("PENDING_RECEIPT", "待签收入库"),
|
||||
Map.entry("PARTIALLY_RECEIVED", "部分入库"),
|
||||
Map.entry("INBOUND_COMPLETED", "全部入库"),
|
||||
Map.entry("WAIT_PDA_RECEIVING", "入库测量中"),
|
||||
Map.entry("INBOUND_MEASURING", "入库测量中"),
|
||||
Map.entry("WAIT_CUSTOMER_CONFIRMATION", "待客户确认实测"),
|
||||
Map.entry("APPROVED", "审核通过"),
|
||||
Map.entry("PENDING_ASSIGNMENT", "待分配"),
|
||||
Map.entry("WAIT_RELABEL", "待换标"),
|
||||
Map.entry("WAIT_FBA_DESTINATION", "待确认目的仓"),
|
||||
Map.entry("READY_FOR_DISPATCH", "待创建出库单"),
|
||||
Map.entry("WAIT_OUTBOUND", "待出库"),
|
||||
Map.entry("ALLOCATED", "已分配库存"),
|
||||
Map.entry("OUTBOUNDED", "已出库"),
|
||||
Map.entry("RECEIVED", "已收货"),
|
||||
Map.entry("IN_TRANSIT", "运输中"),
|
||||
Map.entry("DESTINATION_ASSIGNED", "已分配目的仓"),
|
||||
Map.entry("INBOUND_PLANNED", "已生成入库计划"),
|
||||
Map.entry("PENDING_PROCESSING", "待处理"),
|
||||
Map.entry("PROCESSING", "处理中"),
|
||||
Map.entry("COMPLETED", "已完成"),
|
||||
Map.entry("REJECTED", "已驳回"),
|
||||
Map.entry("CANCELLED", "已取消"),
|
||||
Map.entry("DRAFT", "草稿"),
|
||||
Map.entry("PARTIAL_INBOUND", "部分入库"),
|
||||
Map.entry("SIGNED", "已签收"),
|
||||
Map.entry("PICKING", "拣货中"),
|
||||
Map.entry("PICKED", "已拣货"),
|
||||
Map.entry("SHIPPED", "已出库"),
|
||||
Map.entry("WAIT_DELIVERY", "待派送"),
|
||||
Map.entry("PENDING_DELIVERY", "待派送"),
|
||||
Map.entry("DELIVERING", "派送中"),
|
||||
Map.entry("DELIVERED", "派送完成"),
|
||||
Map.entry("RETURNED", "已退回"),
|
||||
Map.entry("CUSTOMER_CONFIRM_PENDING", "待客户确认"),
|
||||
Map.entry("READY_FOR_OUTBOUND", "待出库"),
|
||||
Map.entry("NOT_REQUIRED", "无需换标"));
|
||||
private static final Map<String, String> INVENTORY_BUSINESS = Map.ofEntries(
|
||||
Map.entry("IN_TRANSIT", "在途入账"),
|
||||
Map.entry("STOCK_IN", "入库"),
|
||||
Map.entry("STOCK_IN_TRANSIT_RELEASE", "在途库存转入"),
|
||||
Map.entry("HOLD", "库存暂扣"),
|
||||
Map.entry("RELEASE", "解除暂扣"),
|
||||
Map.entry("ALLOCATE", "库存分配"),
|
||||
Map.entry("UNALLOCATE", "取消分配"),
|
||||
Map.entry("PICK", "拣货"),
|
||||
Map.entry("SHIP", "出库"),
|
||||
Map.entry("ADJUST", "库存调整"),
|
||||
Map.entry("STOCKTAKE_ADJUST", "盘点调整"),
|
||||
Map.entry("RECEIVING_WAREHOUSE_CHANGED", "变更接仓点"),
|
||||
Map.entry("INTERNAL_MOVE", "库内移库"),
|
||||
Map.entry("RELABEL_PALLET_DISASSEMBLE", "换标拆托"),
|
||||
Map.entry("PALLET_ASSEMBLE", "组托"),
|
||||
Map.entry("PALLET_DISASSEMBLE", "拆托"),
|
||||
Map.entry("PALLET_SWAP", "换托"),
|
||||
Map.entry("PALLET_UNBIND", "解托"),
|
||||
Map.entry("RETURN_STOCK_IN", "退回入库"),
|
||||
Map.entry("DISCARD", "货物废弃"));
|
||||
private static final Map<String, String> ORDER_TYPES = Map.ofEntries(
|
||||
Map.entry("ONE_CLICK_DROPSHIP", "一件代发"),
|
||||
Map.entry("FIRST_LEG", "头程订单"),
|
||||
Map.entry("RELABEL_APPLICATION", "换标下单"),
|
||||
Map.entry("WAREHOUSE_TRANSFER", "仓储转运"),
|
||||
Map.entry("RETURN_RELABEL", "退货换标"),
|
||||
Map.entry("DELIVERY_RETURN", "派送异常退回"),
|
||||
Map.entry("LOCAL_LOOSEBOARD", "本土散板"),
|
||||
Map.entry("CARGO_DISCARD", "货物废弃订单"),
|
||||
Map.entry("REJECTED", "拒收订单"),
|
||||
Map.entry("EXPRESS", "快递订单"));
|
||||
private static final Map<String, String> NODES = Map.ofEntries(
|
||||
Map.entry("WAIT_LOADING", "待装柜"),
|
||||
Map.entry("LOADING", "装柜中"),
|
||||
Map.entry("WAIT_CUSTOMS", "待出口报关"),
|
||||
Map.entry("WAIT_OUTBOUND", "待国内出仓"),
|
||||
Map.entry("DEPARTED_WAREHOUSE", "已离开国内仓库"),
|
||||
Map.entry("IN_TRANSIT", "海运途中"),
|
||||
Map.entry("ARRIVED_DESTINATION_PORT", "船舶已到目的港"),
|
||||
Map.entry("WAITING_FOR_ARRIVAL", "待到港"),
|
||||
Map.entry("ARRIVED", "已到港"),
|
||||
Map.entry("PICKUP_READY", "已清关、可提柜"),
|
||||
Map.entry("PICKUP_SCHEDULED", "已预约提柜"),
|
||||
Map.entry("PICKED_UP", "已提柜"),
|
||||
Map.entry("UNLOADED", "待还柜"),
|
||||
Map.entry("EMPTY_RETURNED", "已还柜"),
|
||||
Map.entry("AT_ORIGIN_PORT", "重箱已进入起运港"),
|
||||
Map.entry("LOADED_ON_VESSEL", "已装船"),
|
||||
Map.entry("DEPARTED_ORIGIN_PORT", "船舶已离开起运港"),
|
||||
Map.entry("DISCHARGED_FROM_VESSEL", "已卸船、未清关"));
|
||||
private static final Map<String, String> INVENTORY_STATUS = Map.ofEntries(
|
||||
Map.entry("OUTSIDE", "仓外"),
|
||||
Map.entry("UNBOUND", "未绑托"),
|
||||
Map.entry("IN_TRANSIT", "在途"),
|
||||
Map.entry("RECEIVING", "收货中"),
|
||||
Map.entry("AVAILABLE", "可用"),
|
||||
Map.entry("HOLD", "暂扣"),
|
||||
Map.entry("HELD", "暂扣"),
|
||||
Map.entry("ALLOCATED", "已分配"),
|
||||
Map.entry("PICKED", "已拣货"),
|
||||
Map.entry("SHIPPED", "已出库"),
|
||||
Map.entry("DISCARDED", "已废弃"),
|
||||
Map.entry("STOCK_IN", "已入库"),
|
||||
Map.entry("EMPTY", "空置"),
|
||||
Map.entry("OCCUPIED", "占用"));
|
||||
|
||||
/** 只转换明确的业务枚举列,避免将编号、币种和备注误当作状态。 */
|
||||
static Object value(String type, String key, Object value) {
|
||||
if (!(value instanceof String text) || text.isBlank()) return value;
|
||||
return switch (type) {
|
||||
case "LAST_MILE_ORDER_LIST" -> switch (key) {
|
||||
case "currentStatus" -> label(text, WORKFLOW, "未知状态");
|
||||
case "orderTypeName", "businessSource.label" -> label(text, ORDER_TYPES, "未知业务类型");
|
||||
case "containerNode" -> label(text, NODES, "未知货柜节点");
|
||||
case "relabelStatus" -> label(text, WORKFLOW, "未知换标状态");
|
||||
case "palletMergeFlag" -> label(text, Map.of("REQUIRED", "待合托", "MERGED", "已合托"), "未标记");
|
||||
case "subOrderStatus" -> subOrders(text);
|
||||
default -> value;
|
||||
};
|
||||
case "OVERSEAS_INVENTORY_LEDGER" -> switch (key) {
|
||||
case "businessType" -> label(text, INVENTORY_BUSINESS, "未知业务类型");
|
||||
case "statusFrom", "statusTo" -> label(text, INVENTORY_STATUS, "未知库存状态");
|
||||
default -> value;
|
||||
};
|
||||
case "OVERSEAS_QUOTE_LIST", "MONTHLY_EXCHANGE_RATE_LIST", "FLEET_QUOTE_LIST" -> switch (key) {
|
||||
case "status" -> label(text, Map.of("ACTIVE", "启用", "INACTIVE", "停用", "DELETED", "已删除"), "未知状态");
|
||||
case "details.kind" -> label(text, Map.of("PURCHASE", "车队采购报价", "SALE", "外接销售报价"), "未知用途");
|
||||
default -> value;
|
||||
};
|
||||
case "VEHICLE_LIST" -> switch (key) {
|
||||
case "status" -> label(text, Map.of("IN_SERVICE", "在役", "DISPATCHED", "外派中", "MAINTENANCE", "维修中", "INACTIVE", "停用", "RETURNED", "已归还"), "未知状态");
|
||||
case "ownership" -> label(text, Map.of("OWNED", "自有", "DISPATCHED", "外派"), "未知车辆归属");
|
||||
case "region" -> label(text, Map.of("DOMESTIC", "国内", "OVERSEAS", "海外"), "未知区域");
|
||||
default -> value;
|
||||
};
|
||||
case "TRIP_OPERATIONS_LIST" -> "status".equals(key)
|
||||
? label(text, Map.of("PLANNED", "计划中", "IN_TRANSIT", "运输中", "COMPLETED", "货物已结案", "CANCELLED", "已取消"), "未知状态") : value;
|
||||
default -> value;
|
||||
};
|
||||
}
|
||||
|
||||
/** 子单状态可能由多个“业务类型:状态”组成,分别翻译而不破坏分组。 */
|
||||
private static String subOrders(String value) {
|
||||
Map<String, String> kinds = Map.of("INBOUND_PLAN", "入库单", "OUTBOUND", "出库单", "DELIVERY", "派送单", "RELABEL", "换标单", "WAREHOUSE_TRANSFER", "仓储转运", "RETURN_RELABEL", "退货换标");
|
||||
return Arrays.stream(value.split("[;;]"))
|
||||
.map(part -> {
|
||||
String[] pair = part.split(":", 2);
|
||||
return pair.length == 2 ? label(pair[0], kinds, "子单") + ":" + label(pair[1], WORKFLOW, "未知状态")
|
||||
: label(part, WORKFLOW, "未知状态");
|
||||
}).collect(Collectors.joining(";"));
|
||||
}
|
||||
|
||||
/** 中文历史快照保持原文,未知英文枚举使用中文占位。 */
|
||||
private static String label(String value, Map<String, String> labels, String fallback) {
|
||||
return labels.getOrDefault(value.trim().toUpperCase(Locale.ROOT), value.matches(".*[一-鿿].*") ? value : fallback);
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -47,6 +47,7 @@ public class LastlegExportProviderService {
|
||||
@Resource private Validator validator;
|
||||
@Value("${baiyue.security.internal-service-token}") private String signingKey;
|
||||
|
||||
/** 校验平台导出快照,复用列表筛选并生成中文业务数据批次。 */
|
||||
public ExportBatchResponse batch(String type, ExportBatchRequest request) {
|
||||
if (request == null || !"PLATFORM".equals(request.appCode()) || !"0".equals(request.tenantId())) {
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "仅支持平台导出");
|
||||
@@ -81,7 +82,7 @@ public class LastlegExportProviderService {
|
||||
case "TRIP_OPERATIONS_LIST" -> operations.report(query(parameters, TripOperationsModels.Query.class), costVisible).page();
|
||||
default -> throw invalid("不支持该导出类型");
|
||||
};
|
||||
List<Map<String, Object>> rows = page.records().stream().map(value -> row(value, columns)).toList();
|
||||
List<Map<String, Object>> rows = page.records().stream().map(value -> row(type, value, columns)).toList();
|
||||
int next = offset + rows.size();
|
||||
if (rows.isEmpty() && next < page.total()) throw invalid("导出分页未推进");
|
||||
return new ExportBatchResponse(1, hash(columns), snapshot, first ? columns : List.of(), rows,
|
||||
@@ -98,7 +99,8 @@ public class LastlegExportProviderService {
|
||||
} catch (IllegalArgumentException e) { throw invalid("导出筛选条件无效"); }
|
||||
}
|
||||
|
||||
private Map<String, Object> row(Object value, List<ExportColumn> columns) {
|
||||
/** 按显式列投影业务数据,仅对指定枚举字段进行中文转换。 */
|
||||
private Map<String, Object> row(String type, Object value, List<ExportColumn> columns) {
|
||||
JsonNode source = json.valueToTree(value);
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
for (ExportColumn column : columns) {
|
||||
@@ -106,12 +108,11 @@ public class LastlegExportProviderService {
|
||||
for (String part : column.key().split("\\.")) node = node.path(part);
|
||||
Object cell = node.isMissingNode() || node.isNull() ? null
|
||||
: node.isBoolean() ? node.booleanValue() : node.isNumber() ? node.decimalValue() : node.asText();
|
||||
result.put(column.key(), cell);
|
||||
result.put(column.key(), LastlegExportLabels.value(type, column.key(), cell));
|
||||
}
|
||||
if (value instanceof com.baiyue.lastleg.dto.LastlegOrderSummary order) {
|
||||
result.put("abnormal", Boolean.TRUE.equals(order.abnormal()) ? "是" : "否");
|
||||
result.put("detained", Boolean.TRUE.equals(order.detained()) ? "是" : "否");
|
||||
result.put("currentStatus", com.baiyue.lastleg.model.LastlegOrderStatus.labelOf(order.currentStatus()));
|
||||
}
|
||||
if (value instanceof TripOperationsModels.Row trip) {
|
||||
result.put("destinations", trip.destinations().stream().map(TripOperationsModels.Destination::label).collect(Collectors.joining(" / ")));
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.baiyue.lastleg.export;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class LastlegExportLabelsTest {
|
||||
@Test void translatesEveryEnumColumnOfAllLastlegTaskTypes() {
|
||||
Object[][] examples = {
|
||||
{"LAST_MILE_ORDER_LIST", "currentStatus", "WAIT_PDA_RECEIVING", "入库测量中"},
|
||||
{"LAST_MILE_ORDER_LIST", "orderTypeName", "FIRST_LEG", "头程订单"},
|
||||
{"LAST_MILE_ORDER_LIST", "businessSource.label", "LOCAL_LOOSEBOARD", "本土散板"},
|
||||
{"LAST_MILE_ORDER_LIST", "containerNode", "PICKUP_READY", "已清关、可提柜"},
|
||||
{"LAST_MILE_ORDER_LIST", "relabelStatus", "CUSTOMER_CONFIRM_PENDING", "待客户确认"},
|
||||
{"LAST_MILE_ORDER_LIST", "palletMergeFlag", "REQUIRED", "待合托"},
|
||||
{"OVERSEAS_INVENTORY_LEDGER", "businessType", "RETURN_STOCK_IN", "退回入库"},
|
||||
{"OVERSEAS_INVENTORY_LEDGER", "statusFrom", "PICKED", "已拣货"},
|
||||
{"OVERSEAS_INVENTORY_LEDGER", "statusTo", "SHIPPED", "已出库"},
|
||||
{"OVERSEAS_QUOTE_LIST", "status", "ACTIVE", "启用"},
|
||||
{"MONTHLY_EXCHANGE_RATE_LIST", "status", "INACTIVE", "停用"},
|
||||
{"FLEET_QUOTE_LIST", "status", "ACTIVE", "启用"},
|
||||
{"FLEET_QUOTE_LIST", "details.kind", "PURCHASE", "车队采购报价"},
|
||||
{"FLEET_QUOTE_LIST", "details.kind", "SALE", "外接销售报价"},
|
||||
{"VEHICLE_LIST", "status", "DISPATCHED", "外派中"},
|
||||
{"VEHICLE_LIST", "ownership", "DISPATCHED", "外派"},
|
||||
{"VEHICLE_LIST", "region", "OVERSEAS", "海外"},
|
||||
{"TRIP_OPERATIONS_LIST", "status", "COMPLETED", "货物已结案"}
|
||||
};
|
||||
for (Object[] example : examples) {
|
||||
assertEquals(example[3], LastlegExportLabels.value((String) example[0], (String) example[1], example[2]), example[0] + ":" + example[1]);
|
||||
assertTrue(LastlegExportColumns.columns((String) example[0]).stream().anyMatch(c -> c.key().equals(example[1])), "映射必须对应实际导出列");
|
||||
}
|
||||
}
|
||||
|
||||
@Test void translatesCompositeSubOrdersAndAllVehicleStatuses() {
|
||||
assertEquals("出库单:已出库;派送单:待派送", LastlegExportLabels.value("LAST_MILE_ORDER_LIST", "subOrderStatus", "OUTBOUND:OUTBOUNDED;DELIVERY:WAIT_DELIVERY"));
|
||||
assertEquals("入库单:部分入库", LastlegExportLabels.value("LAST_MILE_ORDER_LIST", "subOrderStatus", "INBOUND_PLAN:PARTIAL_INBOUND"));
|
||||
for (String code : List.of("IN_SERVICE", "DISPATCHED", "MAINTENANCE", "INACTIVE", "RETURNED")) {
|
||||
String label = (String) LastlegExportLabels.value("VEHICLE_LIST", "status", code);
|
||||
assertTrue(label.matches(".*[一-鿿].*")); assertFalse(label.contains("未知"));
|
||||
}
|
||||
for (String code : List.of("OUTSIDE", "UNBOUND", "IN_TRANSIT", "RECEIVING", "AVAILABLE", "HOLD", "ALLOCATED", "PICKED", "SHIPPED")) {
|
||||
assertFalse(LastlegExportLabels.value("OVERSEAS_INVENTORY_LEDGER", "statusTo", code).toString().contains("未知"));
|
||||
}
|
||||
for (String code : List.of("RECEIVING_WAREHOUSE_CHANGED", "INTERNAL_MOVE", "RELABEL_PALLET_DISASSEMBLE")) {
|
||||
assertFalse(LastlegExportLabels.value("OVERSEAS_INVENTORY_LEDGER", "businessType", code).toString().contains("未知"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test void preservesBusinessIdentifiersAmountsDatesCurrenciesAndFreeText() {
|
||||
for (String type : List.of("LAST_MILE_ORDER_LIST", "OVERSEAS_INVENTORY_BALANCE", "OVERSEAS_INVENTORY_LEDGER", "OVERSEAS_QUOTE_LIST", "MONTHLY_EXCHANGE_RATE_LIST", "VEHICLE_LIST", "FLEET_QUOTE_LIST", "TRIP_OPERATIONS_LIST", "SELFOP_INVENTORY_TURNOVER")) {
|
||||
assertEquals("ACTIVE", LastlegExportLabels.value(type, "remark", "ACTIVE"));
|
||||
assertEquals("ONT8", LastlegExportLabels.value(type, "warehouseCode", "ONT8"));
|
||||
assertEquals("USD", LastlegExportLabels.value(type, "currency", "USD"));
|
||||
assertEquals(BigDecimal.TEN, LastlegExportLabels.value(type, "amount", BigDecimal.TEN));
|
||||
assertNull(LastlegExportLabels.value(type, "status", null));
|
||||
}
|
||||
assertEquals("已完成", LastlegExportLabels.value("LAST_MILE_ORDER_LIST", "currentStatus", "已完成"));
|
||||
assertEquals("未知状态", LastlegExportLabels.value("TRIP_OPERATIONS_LIST", "status", "NEW_INTERNAL_STATE"));
|
||||
}
|
||||
}
|
||||
+11
@@ -46,6 +46,17 @@ class LastlegExportProviderServiceTest {
|
||||
}
|
||||
@AfterEach void close() { validators.close(); }
|
||||
|
||||
@Test void rowProjectionTranslatesOnlyBusinessEnumsBeforeWritingBatches() {
|
||||
Map<String, Object> row = ReflectionTestUtils.invokeMethod(service, "row", "FLEET_QUOTE_LIST",
|
||||
Map.of("status", "ACTIVE", "quoteNo", "ACTIVE", "details", Map.of("kind", "SALE", "currency", "USD", "remark", "SALE")),
|
||||
LastlegExportColumns.columns("FLEET_QUOTE_LIST"));
|
||||
assertEquals("启用", row.get("status"));
|
||||
assertEquals("外接销售报价", row.get("details.kind"));
|
||||
assertEquals("ACTIVE", row.get("quoteNo"));
|
||||
assertEquals("USD", row.get("details.currency"));
|
||||
assertEquals("SALE", row.get("details.remark"));
|
||||
}
|
||||
|
||||
@Test void batchesAllRowsAndBindsSnapshotToFiltersRequesterAndAttempt() {
|
||||
var row = new MonthlyExchangeRateDtos.Response("2026-10", "USD", "CNY", new BigDecimal("7.12"), "ACTIVE", 1, "operator", LocalDateTime.of(2026,10,8,12,0), null);
|
||||
when(rates.page(any())).thenAnswer(call -> {
|
||||
|
||||
Reference in New Issue
Block a user