From 810625e0a98126886ede4c82827170f889da72b9 Mon Sep 17 00:00:00 2001 From: van Date: Sat, 16 May 2026 02:27:52 +0800 Subject: [PATCH] 1 --- .../jarvis/ErpGoofishOrderController.java | 8 + .../jarvis/dto/GoofishShipPreviewVo.java | 50 ++++++ .../service/IErpGoofishOrderService.java | 6 + .../service/goofish/GoofishOrderPipeline.java | 154 +++++++++++++++--- .../impl/ErpGoofishOrderServiceImpl.java | 14 +- 5 files changed, 208 insertions(+), 24 deletions(-) create mode 100644 ruoyi-system/src/main/java/com/ruoyi/jarvis/dto/GoofishShipPreviewVo.java diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/ErpGoofishOrderController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/ErpGoofishOrderController.java index 936e4d6..bad6719 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/ErpGoofishOrderController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/ErpGoofishOrderController.java @@ -9,6 +9,7 @@ import com.ruoyi.jarvis.config.JarvisGoofishProperties; import com.ruoyi.jarvis.domain.ErpGoofishOrder; import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLog; import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLogQuery; +import com.ruoyi.jarvis.dto.GoofishShipPreviewVo; import com.ruoyi.jarvis.service.IErpGoofishOrderService; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @@ -47,6 +48,13 @@ public class ErpGoofishOrderController extends BaseController { return getDataTable(list); } + @PreAuthorize("@ss.hasPermi('jarvis:erpGoofishOrder:query')") + @GetMapping("/shipPreview/{id}") + public AjaxResult shipPreview(@PathVariable Long id) { + GoofishShipPreviewVo vo = erpGoofishOrderService.shipPreview(id); + return AjaxResult.success(vo); + } + @PreAuthorize("@ss.hasPermi('jarvis:erpGoofishOrder:query')") @GetMapping("/{id}") public AjaxResult getInfo(@PathVariable Long id) { diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/dto/GoofishShipPreviewVo.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/dto/GoofishShipPreviewVo.java new file mode 100644 index 0000000..d5b53eb --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/dto/GoofishShipPreviewVo.java @@ -0,0 +1,50 @@ +package com.ruoyi.jarvis.dto; + +import lombok.Data; + +/** + * 闲管家订单发货预览:闲鱼订单侧收件信息 vs 拟提交开放平台发货参数(可含京东兜底) + */ +@Data +public class GoofishShipPreviewVo { + + private String orderNo; + + /** 管家 goods.title(表或详情) */ + private String goodsTitle; + + /** 关联京东单的型号 */ + private String jdModelNumber; + + /** 关联京东单的整段收件地址快照 */ + private String jdAddressFull; + + /** 仅从闲鱼开放平台落库的收件人姓名 */ + private String goofishOrderReceiverName; + + private String goofishOrderReceiverMobile; + + /** + * 闲鱼订单侧完整收件串:省市区镇 + detail(与发货流水线中 receiverFieldsToShipParts 一致,不含京东) + */ + private String goofishOrderFullAddress; + + /** 拟调用发货接口使用的收件人(可能由京东兜底补姓名) */ + private String shipReceiverName; + + private String shipReceiverMobile; + + /** 拟提交的发货详情地址字符串 */ + private String shipFullAddress; + + /** + * 发货收件地址是否与「闲鱼订单侧」归一化后一致(订单侧任一字段为空时为 null) + */ + private Boolean receiverAddressMatchesGoofish; + + /** 是否与京东整段地址归一化后一致(无视京东或未维护京东地址时为 null) */ + private Boolean receiverAddressMatchesJd; + + /** 简短说明比对结论或数据来源 */ + private String compareHint; +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IErpGoofishOrderService.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IErpGoofishOrderService.java index 351045a..bbfe339 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IErpGoofishOrderService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IErpGoofishOrderService.java @@ -4,6 +4,7 @@ import com.alibaba.fastjson2.JSONObject; import com.ruoyi.jarvis.domain.ErpGoofishOrder; import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLog; import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLogQuery; +import com.ruoyi.jarvis.dto.GoofishShipPreviewVo; import java.util.List; @@ -31,6 +32,11 @@ public interface IErpGoofishOrderService { void refreshDetail(Long id); + /** + * 发货预览:闲鱼订单收件串 vs 即将提交开放平台地址(可与京东快照比对)。 + */ + GoofishShipPreviewVo shipPreview(Long id); + void retryShip(Long id); int syncWaybillAndTryShipBatch(int limit); diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/goofish/GoofishOrderPipeline.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/goofish/GoofishOrderPipeline.java index 7565f60..8e1be54 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/goofish/GoofishOrderPipeline.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/goofish/GoofishOrderPipeline.java @@ -14,6 +14,7 @@ import com.ruoyi.jarvis.config.JarvisGoofishProperties; import com.ruoyi.jarvis.domain.ErpGoofishOrder; import com.ruoyi.jarvis.domain.ErpOpenConfig; import com.ruoyi.jarvis.domain.JDOrder; +import com.ruoyi.jarvis.dto.GoofishShipPreviewVo; import com.ruoyi.jarvis.mapper.ErpGoofishOrderMapper; import com.ruoyi.jarvis.service.IJDOrderService; import com.ruoyi.jarvis.service.IErpOpenConfigService; @@ -310,6 +311,10 @@ public class GoofishOrderPipeline { patch.setReceiverAddress(recv.getString("address")); } } + // 开放平台常拆分为省市区 + 明文 detail;明细列为空时用行政区拼接兜底,便于列表与发货解析 + if (StringUtils.isEmpty(patch.getReceiverAddress()) && StringUtils.isNotEmpty(regionJoined)) { + patch.setReceiverAddress(regionJoined.trim()); + } Integer os = firstInt(data, "order_status", "orderStatus"); if (os != null) { patch.setOrderStatus(os); @@ -527,25 +532,7 @@ public class GoofishOrderPipeline { row.getAppKey(), row.getOrderNo()); return; } - ShipAddressParts addr = parseShipAddress(row.getDetailJson()); - if (addr == null) { - addr = new ShipAddressParts(); - } - enrichShipAddressFromJd(row, addr); - if (StringUtils.isEmpty(addr.shipAddress)) { - ShipAddressParts fromRow = receiverFieldsToShipParts(row); - if (fromRow != null) { - if (StringUtils.isEmpty(addr.shipName)) { - addr.shipName = fromRow.shipName; - } - if (StringUtils.isEmpty(addr.shipMobile)) { - addr.shipMobile = fromRow.shipMobile; - } - if (StringUtils.isEmpty(addr.shipAddress)) { - addr.shipAddress = fromRow.shipAddress; - } - } - } + ShipAddressParts addr = resolveShippingAddressParts(row); if (StringUtils.isEmpty(addr.shipName) || StringUtils.isEmpty(addr.shipMobile) || StringUtils.isEmpty(addr.shipAddress)) { patchShipError(row, "缺少收货人/手机/地址:详情无字段时请关联京东单并维护地址,或等平台返回收货字段"); @@ -704,9 +691,6 @@ public class GoofishOrderPipeline { p.shipCityName = firstNonEmpty(data, "ship_city_name", "receiver_city", "city", "city_name"); p.shipAreaName = firstNonEmpty(data, "ship_area_name", "receiver_area", "area", "district", "area_name"); String shipTown = firstNonEmpty(data, "town_name", "ship_town_name", "receiver_town"); - if (StringUtils.isNotEmpty(shipTown) && StringUtils.isEmpty(p.shipAddress)) { - p.shipAddress = shipTown; - } JSONObject recv = data.getJSONObject("receiver"); if (recv != null) { if (StringUtils.isEmpty(p.shipName)) { @@ -722,9 +706,135 @@ public class GoofishOrderPipeline { p.shipAddress = recv.getString("address"); } } + mergeRegionIntoShipAddress(p, shipTown); return p; } + /** + * 与列表/落库拆分字段对齐:开放平台若只给省市区镇 + detail,拼成单行供发货调用。 + * 已在 detail 中包含省名开头或与整段行政区一致时不重复前缀。 + */ + private static void mergeRegionIntoShipAddress(ShipAddressParts p, String shipTownName) { + if (p == null) { + return; + } + String regionLine = joinWithSpace(p.shipProvName, p.shipCityName, p.shipAreaName, shipTownName); + if (StringUtils.isEmpty(regionLine)) { + return; + } + String detail = p.shipAddress; + if (StringUtils.isEmpty(detail)) { + p.shipAddress = regionLine; + return; + } + detail = detail.trim(); + String prov = StringUtils.isNotEmpty(p.shipProvName) ? p.shipProvName.trim() : ""; + if (StringUtils.isNotEmpty(prov) && detail.startsWith(prov)) { + return; + } + if (detail.length() >= regionLine.length() && detail.startsWith(regionLine.trim())) { + return; + } + p.shipAddress = regionLine + " " + detail; + } + + /** + * 与 {@link #tryAutoShip(ErpGoofishOrder, boolean)} 对齐:detail_json → 京东兜底 → 列表/库字段补全。 + */ + private ShipAddressParts resolveShippingAddressParts(ErpGoofishOrder row) { + ShipAddressParts addr = parseShipAddress(row != null ? row.getDetailJson() : null); + if (addr == null) { + addr = new ShipAddressParts(); + } + enrichShipAddressFromJd(row, addr); + if (StringUtils.isEmpty(addr.shipAddress)) { + ShipAddressParts fromRow = receiverFieldsToShipParts(row); + if (fromRow != null) { + if (StringUtils.isEmpty(addr.shipName)) { + addr.shipName = fromRow.shipName; + } + if (StringUtils.isEmpty(addr.shipMobile)) { + addr.shipMobile = fromRow.shipMobile; + } + if (StringUtils.isEmpty(addr.shipAddress)) { + addr.shipAddress = fromRow.shipAddress; + } + } + } + return addr; + } + + /** + * 管理端发货预览:闲鱼收件串 vs 实际将提交开放平台地址(可对齐运单回填前自检)。 + */ + public GoofishShipPreviewVo buildShipPreview(ErpGoofishOrder row) { + GoofishShipPreviewVo vo = new GoofishShipPreviewVo(); + if (row == null) { + vo.setCompareHint("订单不存在"); + return vo; + } + vo.setOrderNo(row.getOrderNo()); + vo.setGoodsTitle(row.getGoodsTitle()); + vo.setJdModelNumber(row.getJdModelNumber()); + vo.setJdAddressFull(row.getJdAddress()); + + ShipAddressParts orderSide = receiverFieldsToShipParts(row); + if (orderSide != null) { + vo.setGoofishOrderReceiverName(orderSide.shipName); + vo.setGoofishOrderReceiverMobile(orderSide.shipMobile); + vo.setGoofishOrderFullAddress(orderSide.shipAddress != null ? orderSide.shipAddress.trim() : ""); + } + + ShipAddressParts ship = resolveShippingAddressParts(row); + vo.setShipReceiverName(ship.shipName); + vo.setShipReceiverMobile(ship.shipMobile); + vo.setShipFullAddress(StringUtils.isNotEmpty(ship.shipAddress) ? ship.shipAddress.trim() : ""); + + String nGoofishAddr = normalizeAddrComparable(vo.getGoofishOrderFullAddress()); + String nShipAddr = normalizeAddrComparable(vo.getShipFullAddress()); + + boolean goofishHasAddr = StringUtils.isNotEmpty(nGoofishAddr); + boolean goofishHasContact = StringUtils.isNotEmpty(vo.getGoofishOrderReceiverName()) + || StringUtils.isNotEmpty(vo.getGoofishOrderReceiverMobile()); + vo.setReceiverAddressMatchesGoofish(goofishHasAddr ? Objects.equals(nGoofishAddr, nShipAddr) : null); + + String nJd = normalizeAddrComparable(vo.getJdAddressFull()); + boolean jdHasAddr = StringUtils.isNotEmpty(nJd); + vo.setReceiverAddressMatchesJd(jdHasAddr ? Objects.equals(nJd, nShipAddr) : null); + + StringBuilder hint = new StringBuilder(); + if (!goofishHasAddr && StringUtils.isNotEmpty(vo.getShipFullAddress())) { + hint.append("闲鱼侧未合并到收件地址文本,发货参数可能仅靠详情解析或京东补全"); + } + if (jdHasAddr && Boolean.FALSE.equals(vo.getReceiverAddressMatchesJd()) + && StringUtils.isNotEmpty(vo.getShipFullAddress())) { + if (hint.length() > 0) { + hint.append(';'); + } + hint.append("与京东单整段地址不完全一致(请核对)"); + } + if (Boolean.FALSE.equals(vo.getReceiverAddressMatchesGoofish()) + && goofishHasAddr && StringUtils.isNotEmpty(vo.getShipFullAddress())) { + if (hint.length() > 0) { + hint.append(';'); + } + hint.append("与闲鱼订单收件串不完全一致(可能已用京东补全或未刷新详情)"); + } + if (hint.length() == 0) { + hint.append(goofishHasContact || goofishHasAddr ? "可对照下方两项文本核对收件信息" : "暂无闲鱼收件信息,请先刷新详情或关联京东单"); + } + vo.setCompareHint(hint.toString()); + return vo; + } + + private static String normalizeAddrComparable(String addr) { + if (addr == null) { + return ""; + } + return addr.trim().replaceAll("\\s+", " "); + } + + /** 开放平台 goods.images 首元素,或其它常见单图字段 */ private static String firstGoodsCoverUrl(JSONObject goods) { if (goods == null) { diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/ErpGoofishOrderServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/ErpGoofishOrderServiceImpl.java index d3a3493..11954a6 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/ErpGoofishOrderServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/ErpGoofishOrderServiceImpl.java @@ -6,9 +6,8 @@ import com.ruoyi.jarvis.config.JarvisGoofishProperties; import com.ruoyi.jarvis.domain.ErpGoofishOrder; import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLog; import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLogQuery; -import com.ruoyi.jarvis.domain.ErpOpenConfig; -import com.ruoyi.jarvis.domain.JDOrder; import com.ruoyi.jarvis.dto.GoofishNotifyMessage; +import com.ruoyi.jarvis.dto.GoofishShipPreviewVo; import com.ruoyi.jarvis.mapper.ErpGoofishOrderEventLogMapper; import com.ruoyi.jarvis.mapper.ErpGoofishOrderMapper; import com.ruoyi.jarvis.service.IErpGoofishOrderService; @@ -142,6 +141,17 @@ public class ErpGoofishOrderServiceImpl implements IErpGoofishOrderService { } } + @Override + public GoofishShipPreviewVo shipPreview(Long id) { + ErpGoofishOrder row = erpGoofishOrderMapper.selectById(id); + if (row == null) { + GoofishShipPreviewVo empty = new GoofishShipPreviewVo(); + empty.setCompareHint("订单不存在"); + return empty; + } + return goofishOrderPipeline.buildShipPreview(row); + } + @Override public void retryShip(Long id) { erpGoofishOrderMapper.resetShipForRetry(id);