Compare commits

...

22 Commits

Author SHA1 Message Date
van
2558b91250 1 2026-06-22 14:28:31 +08:00
van
546cd8eb04 1 2026-06-10 19:35:54 +08:00
van
e890039dea 1 2026-06-10 17:09:05 +08:00
van
1f6625f296 1 2026-06-10 17:00:39 +08:00
van
90e6904b5d 1 2026-06-10 16:32:27 +08:00
van
0022a197cc 1 2026-06-10 14:35:25 +08:00
van
8d3f1337e9 1 2026-06-10 14:27:07 +08:00
van
67e6723685 1 2026-06-03 12:06:43 +08:00
van
6412168cc6 1 2026-06-03 11:42:59 +08:00
van
a8a6d57a72 1 2026-05-26 13:00:25 +08:00
van
8f8333e324 1 2026-05-22 17:41:47 +08:00
van
b107eece07 1 2026-05-21 16:27:39 +08:00
van
8fcd7be160 1 2026-05-20 00:02:23 +08:00
van
4aec0177dd 1 2026-05-19 23:15:05 +08:00
van
379a11ae63 1 2026-05-19 17:05:24 +08:00
van
0963b8af59 1 2026-05-19 16:47:02 +08:00
van
6fa6cf624e 1 2026-05-17 20:22:49 +08:00
van
7ef9c16ae7 1 2026-05-17 18:11:22 +08:00
van
77776802f2 1 2026-05-17 18:09:35 +08:00
van
8770ea92ed 1 2026-05-17 17:20:45 +08:00
van
0a3a9c46f6 1 2026-05-17 14:40:23 +08:00
van
17ec7fa131 1 2026-05-16 22:03:59 +08:00
44 changed files with 1322 additions and 121 deletions

View File

@@ -40,6 +40,12 @@ public class ErpGoofishOrderController extends BaseController {
return getDataTable(list); return getDataTable(list);
} }
@PreAuthorize("@ss.hasPermi('jarvis:erpGoofishOrder:list')")
@GetMapping("/stats/rrsLogistics")
public AjaxResult rrsLogisticsStats() {
return AjaxResult.success(erpGoofishOrderService.selectRrsJdLinkedLogisticsStats());
}
@PreAuthorize("@ss.hasPermi('jarvis:erpGoofishOrder:list')") @PreAuthorize("@ss.hasPermi('jarvis:erpGoofishOrder:list')")
@GetMapping("/list") @GetMapping("/list")
public TableDataInfo list(ErpGoofishOrder query) { public TableDataInfo list(ErpGoofishOrder query) {

View File

@@ -15,6 +15,7 @@ import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.jarvis.domain.OrderRows; import com.ruoyi.jarvis.domain.OrderRows;
import com.ruoyi.jarvis.service.IGoodsInfoService;
import com.ruoyi.jarvis.service.IOrderRowsService; import com.ruoyi.jarvis.service.IOrderRowsService;
import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
@@ -32,6 +33,9 @@ public class OrderRowsController extends BaseController
@Autowired @Autowired
private IOrderRowsService orderRowsService; private IOrderRowsService orderRowsService;
@Autowired
private IGoodsInfoService goodsInfoService;
@Autowired @Autowired
private SuperAdminService superAdminService; private SuperAdminService superAdminService;
@@ -127,7 +131,11 @@ public class OrderRowsController extends BaseController
@GetMapping(value = "/{id}") @GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") String id) public AjaxResult getInfo(@PathVariable("id") String id)
{ {
return success(orderRowsService.selectOrderRowsById(id)); OrderRows orderRows = orderRowsService.selectOrderRowsById(id);
if (orderRows != null && orderRows.getGoodsInfoId() != null) {
orderRows.setGoodsInfo(goodsInfoService.selectGoodsInfoById(orderRows.getGoodsInfoId()));
}
return success(orderRows);
} }
/** /**

View File

@@ -0,0 +1,156 @@
package com.ruoyi.web.controller.publicapi;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.annotation.RateLimiter;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.enums.LimitType;
import com.ruoyi.jarvis.domain.GoodsInfo;
import com.ruoyi.jarvis.domain.OrderRows;
import com.ruoyi.jarvis.domain.dto.PromoterOrderInfoVO;
import com.ruoyi.jarvis.enums.ValidCodeConverter;
import com.ruoyi.jarvis.service.IGoodsInfoService;
import com.ruoyi.jarvis.service.IOrderRowsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 京东开放接口(免登录,路径统一 /open/jd/
*/
@Anonymous
@RestController
@RequestMapping("/open/jd")
public class PublicPromoterOrderController {
private static final String DEFAULT_SHOP_LOGO = "https://www.jd.com/favicon.ico";
private static final SimpleDateFormat DATE_FMT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Autowired
private IOrderRowsService orderRowsService;
@Autowired
private IGoodsInfoService goodsInfoService;
/**
* 跟团订单查询(兼容 jiadiantemai 接口格式)
*/
@GetMapping("/queryTkOrder")
@RateLimiter(key = CacheConstants.RATE_LIMIT_KEY, time = 60, count = 30, limitType = LimitType.IP)
public Map<String, Object> queryTkOrder(
@RequestParam String orderId,
@RequestParam(required = false) String t) {
Map<String, Object> result = new HashMap<>();
String trimmedOrderId = orderId != null ? orderId.trim() : "";
if (trimmedOrderId.isEmpty()) {
result.put("code", 200);
result.put("count", 0);
result.put("msg", "请输入订单编号");
return result;
}
List<OrderRows> rows = orderRowsService.selectOrderRowsByOrderNo(trimmedOrderId);
if (rows == null || rows.isEmpty()) {
result.put("code", 200);
result.put("count", 0);
result.put("msg", "未查找到对应订单信息~");
return result;
}
Set<Long> goodsInfoIds = new HashSet<>();
for (OrderRows row : rows) {
if (row.getGoodsInfoId() != null) {
goodsInfoIds.add(row.getGoodsInfoId());
}
}
Map<Long, GoodsInfo> goodsInfoMap = goodsInfoService.selectGoodsInfoMapByIds(goodsInfoIds);
List<PromoterOrderInfoVO> orderInfoList = new ArrayList<>();
ValidCodeConverter validCodeConverter = new ValidCodeConverter();
for (OrderRows row : rows) {
GoodsInfo goodsInfo = row.getGoodsInfoId() != null
? goodsInfoMap.get(row.getGoodsInfoId()) : null;
orderInfoList.add(toPromoterOrderInfo(row, goodsInfo, validCodeConverter));
}
if (orderInfoList.isEmpty()) {
result.put("code", 200);
result.put("count", 0);
result.put("msg", "未查找到对应订单信息~");
return result;
}
result.put("code", 200);
result.put("orderInfoList", orderInfoList);
result.put("msg", "查询成功");
return result;
}
private PromoterOrderInfoVO toPromoterOrderInfo(OrderRows row, GoodsInfo goodsInfo,
ValidCodeConverter validCodeConverter) {
PromoterOrderInfoVO vo = new PromoterOrderInfoVO();
String shopName = goodsInfo != null && goodsInfo.getShopName() != null && !goodsInfo.getShopName().isEmpty()
? goodsInfo.getShopName() : "京东商城";
vo.setShopName(shopName);
vo.setShopLogo(DEFAULT_SHOP_LOGO);
vo.setOrderSource("京东");
vo.setTraceTypeStr(row.getTraceType() != null && row.getTraceType() == 2 ? "同店" : "跨店");
if (row.getParentId() != null && row.getParentId() > 0) {
vo.setParentId(String.valueOf(row.getParentId()));
}
if (row.getOrderId() != null) {
vo.setOrderId(String.valueOf(row.getOrderId()));
}
vo.setValidCodeMsg(validCodeConverter.getCodeDescription(row.getValidCode()));
if (goodsInfo != null && goodsInfo.getImageUrl() != null && !goodsInfo.getImageUrl().isEmpty()) {
vo.setSkuImageUrl(goodsInfo.getImageUrl());
} else if (row.getSkuId() != null) {
vo.setSkuImageUrl("https://img14.360buyimg.com/n5/s450x450_" + row.getSkuId() + ".jpg");
}
vo.setSkuName(row.getSkuName());
if (row.getSkuId() != null) {
vo.setSkuId(String.valueOf(row.getSkuId()));
}
vo.setItemId(row.getItemId());
vo.setSkuNum(row.getSkuNum() != null ? row.getSkuNum() : 1);
vo.setCosPrice(formatPrice(row));
vo.setOrderTime(formatDate(row.getOrderTime()));
vo.setFinishTime(formatDate(row.getFinishTime()));
Double proPrice = row.getProPriceAmount();
if (proPrice != null && proPrice > 0) {
vo.setProPriceAmount(String.format("%.2f", proPrice));
} else {
vo.setProPriceAmount("0.00");
}
return vo;
}
private String formatPrice(OrderRows row) {
Double price = row.getActualCosPrice();
if (price == null || price <= 0) {
price = row.getEstimateCosPrice();
}
if (price == null) {
return "0.00";
}
return String.format("%.2f", price);
}
private String formatDate(java.util.Date date) {
if (date == null) {
return "";
}
synchronized (DATE_FMT) {
return DATE_FMT.format(date);
}
}
}

View File

@@ -9,13 +9,20 @@ import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.jarvis.domain.GroupRebateExcelUpload; import com.ruoyi.jarvis.domain.GroupRebateExcelUpload;
import com.ruoyi.jarvis.domain.OrderRows; import com.ruoyi.jarvis.domain.OrderRows;
import com.ruoyi.jarvis.service.IGroupRebateExcelUploadService; import com.ruoyi.jarvis.service.IGroupRebateExcelUploadService;
import com.ruoyi.jarvis.service.impl.GroupRebateExcelImportService; import com.ruoyi.jarvis.service.impl.GroupRebateExcelImportService;
import com.ruoyi.jarvis.service.IOrderRowsService; import com.ruoyi.jarvis.service.IOrderRowsService;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.common.utils.http.HttpUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@@ -27,7 +34,11 @@ import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.jarvis.domain.JDOrder; import com.ruoyi.jarvis.domain.JDOrder;
import com.ruoyi.jarvis.domain.dto.JDOrderSimpleDTO; import com.ruoyi.jarvis.domain.dto.JDOrderSimpleDTO;
import com.ruoyi.jarvis.domain.dto.QuickRecordModelOption; import com.ruoyi.jarvis.domain.dto.QuickRecordModelOption;
import com.ruoyi.jarvis.domain.dto.QuickRecordModelShopOption;
import com.ruoyi.jarvis.util.QuickRecordModelShopOptionUtil;
import com.ruoyi.jarvis.service.IJDOrderModelShopService;
import com.ruoyi.jarvis.service.IJDOrderProfitService; import com.ruoyi.jarvis.service.IJDOrderProfitService;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.jarvis.service.IJDOrderService; import com.ruoyi.jarvis.service.IJDOrderService;
import com.ruoyi.jarvis.service.IInstructionService; import com.ruoyi.jarvis.service.IInstructionService;
import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.utils.poi.ExcelUtil;
@@ -43,6 +54,14 @@ import com.ruoyi.common.core.page.TableDataInfo;
public class JDOrderListController extends BaseController public class JDOrderListController extends BaseController
{ {
private static final String JARVIS_JAVA_SKEY = "2192057370ef8140c201079969c956a3";
@Value("${jarvis.server.jarvis-java.base-url:http://127.0.0.1:6666}")
private String jarvisJavaBaseUrl;
@Value("${jarvis.server.jarvis-java.jd-api-path:/jd}")
private String jdApiPath;
private final IJDOrderService jdOrderService; private final IJDOrderService jdOrderService;
private final IJDOrderProfitService jdOrderProfitService; private final IJDOrderProfitService jdOrderProfitService;
private final IOrderRowsService orderRowsService; private final IOrderRowsService orderRowsService;
@@ -50,17 +69,27 @@ public class JDOrderListController extends BaseController
private final GroupRebateExcelImportService groupRebateExcelImportService; private final GroupRebateExcelImportService groupRebateExcelImportService;
private final IGroupRebateExcelUploadService groupRebateExcelUploadService; private final IGroupRebateExcelUploadService groupRebateExcelUploadService;
private final ObjectMapper objectMapper;
private final ISysConfigService sysConfigService;
private final IJDOrderModelShopService jdOrderModelShopService;
public JDOrderListController(IJDOrderService jdOrderService, IJDOrderProfitService jdOrderProfitService, public JDOrderListController(IJDOrderService jdOrderService, IJDOrderProfitService jdOrderProfitService,
IOrderRowsService orderRowsService, IOrderRowsService orderRowsService,
IInstructionService instructionService, IInstructionService instructionService,
GroupRebateExcelImportService groupRebateExcelImportService, GroupRebateExcelImportService groupRebateExcelImportService,
IGroupRebateExcelUploadService groupRebateExcelUploadService) { IGroupRebateExcelUploadService groupRebateExcelUploadService,
ObjectMapper objectMapper,
ISysConfigService sysConfigService,
IJDOrderModelShopService jdOrderModelShopService) {
this.jdOrderService = jdOrderService; this.jdOrderService = jdOrderService;
this.jdOrderProfitService = jdOrderProfitService; this.jdOrderProfitService = jdOrderProfitService;
this.orderRowsService = orderRowsService; this.orderRowsService = orderRowsService;
this.instructionService = instructionService; this.instructionService = instructionService;
this.groupRebateExcelImportService = groupRebateExcelImportService; this.groupRebateExcelImportService = groupRebateExcelImportService;
this.groupRebateExcelUploadService = groupRebateExcelUploadService; this.groupRebateExcelUploadService = groupRebateExcelUploadService;
this.objectMapper = objectMapper;
this.sysConfigService = sysConfigService;
this.jdOrderModelShopService = jdOrderModelShopService;
} }
/** /**
@@ -108,6 +137,8 @@ public class JDOrderListController extends BaseController
query.getParams().put("rebateWithoutUploadLink", true); query.getParams().put("rebateWithoutUploadLink", true);
} }
applyModelNumberExcludeFilter(query);
java.util.List<JDOrder> list; java.util.List<JDOrder> list;
if (orderBy != null && !orderBy.isEmpty()) { if (orderBy != null && !orderBy.isEmpty()) {
// 设置排序参数 // 设置排序参数
@@ -130,10 +161,12 @@ public class JDOrderListController extends BaseController
jdOrder.setProPriceAmount(orderRows.getProPriceAmount()); jdOrder.setProPriceAmount(orderRows.getProPriceAmount());
jdOrder.setFinishTime(orderRows.getFinishTime()); jdOrder.setFinishTime(orderRows.getFinishTime());
jdOrder.setOrderStatus(orderRows.getValidCode()); jdOrder.setOrderStatus(orderRows.getValidCode());
jdOrder.setExpressStatus(orderRows.getExpressStatus());
} else { } else {
jdOrder.setProPriceAmount(0.0); jdOrder.setProPriceAmount(0.0);
jdOrder.setFinishTime(null); jdOrder.setFinishTime(null);
jdOrder.setOrderStatus(null); jdOrder.setOrderStatus(null);
jdOrder.setExpressStatus(null);
} }
} }
// 过滤掉完成时间为空的订单 // 过滤掉完成时间为空的订单
@@ -148,9 +181,11 @@ public class JDOrderListController extends BaseController
jdOrder.setProPriceAmount(orderRows.getProPriceAmount()); jdOrder.setProPriceAmount(orderRows.getProPriceAmount());
jdOrder.setFinishTime(orderRows.getFinishTime()); jdOrder.setFinishTime(orderRows.getFinishTime());
jdOrder.setOrderStatus(orderRows.getValidCode()); jdOrder.setOrderStatus(orderRows.getValidCode());
jdOrder.setExpressStatus(orderRows.getExpressStatus());
} else { } else {
jdOrder.setProPriceAmount(0.0); jdOrder.setProPriceAmount(0.0);
jdOrder.setOrderStatus(null); jdOrder.setOrderStatus(null);
jdOrder.setExpressStatus(null);
} }
} }
} }
@@ -167,6 +202,26 @@ public class JDOrderListController extends BaseController
return AjaxResult.success(options); return AjaxResult.success(options);
} }
/**
* 快捷录单页:型号后缀店铺下拉(系统参数 quickRecord.modelShopOptions每行格式短前缀完整店名
*/
@GetMapping("/quickRecord/shopOptions")
public AjaxResult quickRecordShopOptions() {
String raw = sysConfigService.selectConfigByKey(QuickRecordModelShopOptionUtil.CONFIG_KEY);
List<QuickRecordModelShopOption> options = QuickRecordModelShopOptionUtil.parseOptions(raw);
return AjaxResult.success(options);
}
/**
* 一键迁移:将 model_number 末尾匹配的店铺前缀写入 model_shop并从 model_number 截掉。
* 需先配置 quickRecord.modelShopOptions。
*/
@Log(title = "JD订单型号店铺拆分迁移", businessType = BusinessType.UPDATE)
@PostMapping("/migrateModelShop")
public AjaxResult migrateModelShop() {
return AjaxResult.success(jdOrderService.migrateModelShopSplit());
}
/** /**
* 导入跟团返现类 Excel按「单号/订单号」匹配系统订单,将「是否返现」「总共返现」等写入后返备注(可多次导入累加);文件落盘并记上传记录。 * 导入跟团返现类 Excel按「单号/订单号」匹配系统订单,将「是否返现」「总共返现」等写入后返备注(可多次导入累加);文件落盘并记上传记录。
*/ */
@@ -258,6 +313,8 @@ public class JDOrderListController extends BaseController
{ {
String fileName = "JD订单数据"; String fileName = "JD订单数据";
applyModelNumberExcludeFilter(jdOrder);
List<JDOrder> list = jdOrderService.selectJDOrderList(jdOrder); List<JDOrder> list = jdOrderService.selectJDOrderList(jdOrder);
// 设置响应头,指定文件名 // 设置响应头,指定文件名
@@ -292,15 +349,54 @@ public class JDOrderListController extends BaseController
} }
/** /**
* 修改JD订单 * 修改JD订单PUT body 使用 JsonNodeextraCost / extra_cost 从原始 JSON 显式写入实体,
* 避免个别 Jackson + Lombok 场景下 treeToValue 未映射导致重算利润仍按 0 计算)
*/ */
@Log(title = "JD订单", businessType = BusinessType.UPDATE) @Log(title = "JD订单", businessType = BusinessType.UPDATE)
@PutMapping @PutMapping
public AjaxResult edit(@RequestBody JDOrder jdOrder) public AjaxResult edit(@RequestBody JsonNode root) throws JsonProcessingException {
{ JDOrder jdOrder = objectMapper.treeToValue(root, JDOrder.class);
applyExtraCostFromPayload(root, jdOrder);
jdOrderModelShopService.normalizeOrder(jdOrder);
jdOrderProfitService.recalculate(jdOrder); jdOrderProfitService.recalculate(jdOrder);
jdOrder.getParams().put("applyProfitFields", Boolean.TRUE); jdOrder.getParams().put("applyProfitFields", Boolean.TRUE);
return toAjax(jdOrderService.updateJDOrder(jdOrder)); int rows = jdOrderService.updateJDOrder(jdOrder);
if (rows <= 0) {
return AjaxResult.error("更新失败或订单不存在");
}
return AjaxResult.success(jdOrderService.selectJDOrderById(jdOrder.getId()));
}
private static void applyExtraCostFromPayload(JsonNode root, JDOrder order) {
if (root == null || order == null) {
return;
}
JsonNode n = root.get("extraCost");
if (readExtraCostNumber(n, order)) {
return;
}
n = root.get("extra_cost");
readExtraCostNumber(n, order);
}
/** @return true 已从节点解析并写入 order */
private static boolean readExtraCostNumber(JsonNode n, JDOrder order) {
if (n == null || n.isNull()) {
return false;
}
if (n.isNumber()) {
order.setExtraCost(n.asDouble());
return true;
}
if (n.isTextual()) {
try {
order.setExtraCost(Double.parseDouble(n.asText().trim()));
return true;
} catch (NumberFormatException ignored) {
return false;
}
}
return false;
} }
/** /**
@@ -522,9 +618,7 @@ public class JDOrderListController extends BaseController
if (query.getModelNumber() != null && !query.getModelNumber().trim().isEmpty()) { if (query.getModelNumber() != null && !query.getModelNumber().trim().isEmpty()) {
query.setModelNumber(query.getModelNumber().trim()); query.setModelNumber(query.getModelNumber().trim());
} }
if (query.getModelNumberExclude() != null && !query.getModelNumberExclude().trim().isEmpty()) { applyModelNumberExcludeFilter(query);
query.setModelNumberExclude(query.getModelNumberExclude().trim());
}
if (query.getBuyer() != null && !query.getBuyer().trim().isEmpty()) { if (query.getBuyer() != null && !query.getBuyer().trim().isEmpty()) {
query.setBuyer(query.getBuyer().trim()); query.setBuyer(query.getBuyer().trim());
} }
@@ -575,8 +669,8 @@ public class JDOrderListController extends BaseController
String duoduoOrderNo = o.getThirdPartyOrderNo() != null && !o.getThirdPartyOrderNo().trim().isEmpty() String duoduoOrderNo = o.getThirdPartyOrderNo() != null && !o.getThirdPartyOrderNo().trim().isEmpty()
? o.getThirdPartyOrderNo() : (o.getRemark() != null ? o.getRemark() : ""); ? o.getThirdPartyOrderNo() : (o.getRemark() != null ? o.getRemark() : "");
// 型号 // 型号(对外完整型号 = 本体 + 店铺前缀)
String modelNumber = o.getModelNumber() != null ? o.getModelNumber() : ""; String modelNumber = QuickRecordModelShopOptionUtil.fullModelNumber(o);
// 数量固定为1 // 数量固定为1
String quantity = "1"; String quantity = "1";
@@ -639,4 +733,72 @@ public class JDOrderListController extends BaseController
return AjaxResult.error("生成失败: " + e.getMessage()); return AjaxResult.error("生成失败: " + e.getMessage());
} }
} }
/**
* 型号不含:支持逗号、中文逗号、空格、换行分隔的多个关键词,型号不得包含任一关键词。
*/
private void applyModelNumberExcludeFilter(JDOrder query) {
if (query == null) {
return;
}
List<String> terms = parseModelNumberExcludeTerms(query.getModelNumberExclude());
if (!terms.isEmpty()) {
query.getParams().put("modelNumberExcludeList", terms);
}
query.setModelNumberExclude(null);
}
private static List<String> parseModelNumberExcludeTerms(String raw) {
List<String> out = new ArrayList<>();
if (StringUtils.isEmpty(raw)) {
return out;
}
for (String part : raw.split("[,\\s\\n]+")) {
String t = part.trim();
if (!t.isEmpty() && !out.contains(t)) {
out.add(t);
}
}
return out;
}
/**
* 回填京粉订单 goodsInfo店铺名、商品图
* Body: { "orderIds": "352543902480387,3525433002460987" }
* 或: { "missing": true, "limit": 100 }
*/
@Log(title = "京粉订单goodsInfo回填", businessType = BusinessType.OTHER)
@PostMapping("/orderRows/backfillGoodsInfo")
public AjaxResult backfillGoodsInfo(@RequestBody Map<String, Object> body) {
try {
JSONObject param = new JSONObject();
param.put("skey", JARVIS_JAVA_SKEY);
if (body != null) {
if (body.get("orderIds") != null) {
param.put("orderIds", body.get("orderIds"));
}
if (body.get("missing") != null) {
param.put("missing", body.get("missing"));
}
if (body.get("limit") != null) {
param.put("limit", body.get("limit"));
}
}
String url = jarvisJavaBaseUrl + jdApiPath + "/backfillGoodsInfo";
String resp = HttpUtils.sendJsonPost(url, param.toJSONString());
if (StringUtils.isEmpty(resp)) {
return AjaxResult.error("Jarvis 服务无响应,请检查 jarvis.server.jarvis-java.base-url");
}
Object parsed = JSON.parse(resp);
if (parsed instanceof JSONObject) {
JSONObject obj = (JSONObject) parsed;
if (obj.containsKey("error")) {
return AjaxResult.error(String.valueOf(obj.get("error")));
}
}
return AjaxResult.success(parsed);
} catch (Exception e) {
return AjaxResult.error("回填失败: " + e.getMessage());
}
}
} }

View File

@@ -208,7 +208,7 @@ jarvis:
adhoc-pending-batch-size: 50 adhoc-pending-batch-size: 50
# 物流扫描LogisticsScanTask轮询 JD 单拉运单 + drain 分享链队列 # 物流扫描LogisticsScanTask轮询 JD 单拉运单 + drain 分享链队列
scan: scan:
cron: "0 */20 * * * ?" cron: "0 */5 * * * ?"
order-delay-ms: 250 order-delay-ms: 250
# 0=不限制;例如 40 可控制单轮最长耗时(余下下轮再扫) # 0=不限制;例如 40 可控制单轮最长耗时(余下下轮再扫)
max-orders-per-round: 0 max-orders-per-round: 0

View File

@@ -206,7 +206,7 @@ jarvis:
health-path: /health health-path: /health
adhoc-pending-batch-size: 50 adhoc-pending-batch-size: 50
scan: scan:
cron: "0 */20 * * * ?" cron: "0 */5 * * * ?"
order-delay-ms: 250 order-delay-ms: 250
max-orders-per-round: 0 max-orders-per-round: 0
# 获取评论接口服务地址(后端转发) # 获取评论接口服务地址(后端转发)

View File

@@ -114,6 +114,8 @@ public class SecurityConfig
requests.antMatchers("/login", "/register", "/captchaImage").permitAll() requests.antMatchers("/login", "/register", "/captchaImage").permitAll()
// 公开接口,允许匿名访问 // 公开接口,允许匿名访问
.antMatchers("/public/**").permitAll() .antMatchers("/public/**").permitAll()
// 开放页面接口(京东等),允许匿名访问
.antMatchers("/open/**").permitAll()
// 腾讯文档OAuth回调接口允许匿名访问 // 腾讯文档OAuth回调接口允许匿名访问
.antMatchers("/jarvis/tendoc/oauth/callback").permitAll() .antMatchers("/jarvis/tendoc/oauth/callback").permitAll()
// 腾讯文档OAuth回调接口备用路径允许匿名访问 // 腾讯文档OAuth回调接口备用路径允许匿名访问

View File

@@ -0,0 +1,71 @@
package com.ruoyi.jarvis.domain;
/**
* 京东订单商品信息 goods_info
*/
public class GoodsInfo {
private Long id;
private String owner;
private String mainSkuId;
private String productId;
private String imageUrl;
private String shopName;
private String shopId;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public String getMainSkuId() {
return mainSkuId;
}
public void setMainSkuId(String mainSkuId) {
this.mainSkuId = mainSkuId;
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
public String getShopName() {
return shopName;
}
public void setShopName(String shopName) {
this.shopName = shopName;
}
public String getShopId() {
return shopId;
}
public void setShopId(String shopId) {
this.shopId = shopId;
}
}

View File

@@ -3,6 +3,7 @@ package com.ruoyi.jarvis.domain;
import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.annotation.Excel.ColumnType; import com.ruoyi.common.annotation.Excel.ColumnType;
import com.fasterxml.jackson.annotation.JsonAlias;
import lombok.Data; import lombok.Data;
import org.springframework.data.annotation.Transient; import org.springframework.data.annotation.Transient;
@@ -26,11 +27,15 @@ public class JDOrder extends BaseEntity {
@Excel(name = "分销标记") @Excel(name = "分销标记")
private String distributionMark; private String distributionMark;
/** 型号 */ /** 型号(本体,不含店铺后缀) */
@Excel(name = "型号") @Excel(name = "型号")
private String modelNumber; private String modelNumber;
/** 列表筛选:型号不含此子串(对应 SQL NOT LIKE %值%),不入库 */ /** 型号店铺短前缀(如海尔厨房;完整型号 = modelNumber + modelShop */
@Excel(name = "型号店铺")
private String modelShop;
/** 列表筛选:型号不含这些子串(逗号/空格分隔,对应 SQL 多条 NOT LIKE不入库 */
@Transient @Transient
private String modelNumberExclude; private String modelNumberExclude;
@@ -89,6 +94,10 @@ public class JDOrder extends BaseEntity {
@Excel(name = "订单状态") @Excel(name = "订单状态")
private Integer orderStatus; private Integer orderStatus;
/** 发货状态(从 order_rows.express_status 查询10=待发货) */
@Transient
private Integer expressStatus;
/** 是否参与统计0否 1是 */ /** 是否参与统计0否 1是 */
@Excel(name = "参与统计") @Excel(name = "参与统计")
private Integer isCountEnabled; private Integer isCountEnabled;
@@ -173,6 +182,11 @@ public class JDOrder extends BaseEntity {
/** 利润是否手动锁定1 是:保存时不再自动重算) */ /** 利润是否手动锁定1 是:保存时不再自动重算) */
private Integer profitManual; private Integer profitManual;
/** 额外成本(计入自动利润:从 netReceipt 成本中再减去此项,默认 0 */
@JsonAlias({ "extra_cost" })
@Excel(name = "额外成本")
private Double extraCost;
} }

View File

@@ -229,4 +229,8 @@ public class OrderRows extends BaseEntity
@Excel(name = "订单标签") @Excel(name = "订单标签")
private String orderTag; private String orderTag;
/** 关联商品信息goods_info非表字段 */
@Transient
private GoodsInfo goodsInfo;
} }

View File

@@ -0,0 +1,153 @@
package com.ruoyi.jarvis.domain.dto;
/**
* 跟团查询页订单信息
*/
public class PromoterOrderInfoVO {
private String shopName;
private String shopLogo;
private String orderSource;
private String traceTypeStr;
private String parentId;
private String orderId;
private String validCodeMsg;
private String skuImageUrl;
private String skuId;
private String skuName;
private String itemId;
private Integer skuNum;
private String cosPrice;
private String orderTime;
private String finishTime;
/** 价保/赔付金额 */
private String proPriceAmount;
public String getShopName() {
return shopName;
}
public void setShopName(String shopName) {
this.shopName = shopName;
}
public String getShopLogo() {
return shopLogo;
}
public void setShopLogo(String shopLogo) {
this.shopLogo = shopLogo;
}
public String getOrderSource() {
return orderSource;
}
public void setOrderSource(String orderSource) {
this.orderSource = orderSource;
}
public String getTraceTypeStr() {
return traceTypeStr;
}
public void setTraceTypeStr(String traceTypeStr) {
this.traceTypeStr = traceTypeStr;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public String getValidCodeMsg() {
return validCodeMsg;
}
public void setValidCodeMsg(String validCodeMsg) {
this.validCodeMsg = validCodeMsg;
}
public String getSkuImageUrl() {
return skuImageUrl;
}
public void setSkuImageUrl(String skuImageUrl) {
this.skuImageUrl = skuImageUrl;
}
public String getSkuId() {
return skuId;
}
public void setSkuId(String skuId) {
this.skuId = skuId;
}
public String getSkuName() {
return skuName;
}
public void setSkuName(String skuName) {
this.skuName = skuName;
}
public String getItemId() {
return itemId;
}
public void setItemId(String itemId) {
this.itemId = itemId;
}
public Integer getSkuNum() {
return skuNum;
}
public void setSkuNum(Integer skuNum) {
this.skuNum = skuNum;
}
public String getCosPrice() {
return cosPrice;
}
public void setCosPrice(String cosPrice) {
this.cosPrice = cosPrice;
}
public String getOrderTime() {
return orderTime;
}
public void setOrderTime(String orderTime) {
this.orderTime = orderTime;
}
public String getFinishTime() {
return finishTime;
}
public void setFinishTime(String finishTime) {
this.finishTime = finishTime;
}
public String getProPriceAmount() {
return proPriceAmount;
}
public void setProPriceAmount(String proPriceAmount) {
this.proPriceAmount = proPriceAmount;
}
}

View File

@@ -10,6 +10,9 @@ public class QuickRecordModelOption {
private String modelNumber; private String modelNumber;
/** 型号店铺短前缀 */
private String modelShop;
/** 最近一次订单的下单付款金额 */ /** 最近一次订单的下单付款金额 */
private Double lastPaymentAmount; private Double lastPaymentAmount;

View File

@@ -0,0 +1,19 @@
package com.ruoyi.jarvis.domain.dto;
import lombok.Data;
/**
* 快捷录单:型号后缀店铺选项(前缀拼接到型号末尾,如 W5000PLUS2.0白 + 海尔厨房)
*/
@Data
public class QuickRecordModelShopOption {
/** 拼接到型号末尾的短前缀,如 海尔官旗 */
private String prefix;
/** 括号内完整店铺名,如 海尔官方旗舰店 */
private String fullName;
/** 展示文案,如 海尔官旗(海尔官方旗舰店) */
private String label;
}

View File

@@ -0,0 +1,16 @@
package com.ruoyi.jarvis.dto;
import lombok.Data;
/**
* 闲管家订单:日日顺承运且已关联京东单的出库统计口径(与前台「手动推送/hasPlatformShipped」基本一致
*/
@Data
public class GoofishRrsLogisticsStatsVo {
/** 已出库:有有效运单去重计数 + 无运单但已发货/完成或本系统发货成功的订单数 */
private long shippedOutboundCount;
/** 待出库:平台「待发货」且无运单、且非本系统发货成功 */
private long pendingOutboundCount;
}

View File

@@ -1,6 +1,7 @@
package com.ruoyi.jarvis.mapper; package com.ruoyi.jarvis.mapper;
import com.ruoyi.jarvis.domain.ErpGoofishOrder; import com.ruoyi.jarvis.domain.ErpGoofishOrder;
import com.ruoyi.jarvis.dto.GoofishRrsLogisticsStatsVo;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import java.util.List; import java.util.List;
@@ -25,4 +26,9 @@ public interface ErpGoofishOrderMapper {
List<ErpGoofishOrder> selectByGoofishOrderNo(@Param("orderNo") String orderNo); List<ErpGoofishOrder> selectByGoofishOrderNo(@Param("orderNo") String orderNo);
int resetShipForRetry(@Param("id") Long id); int resetShipForRetry(@Param("id") Long id);
/**
* 已关联 jd_order_id、承运为日日顺rrs 或名称含日日顺);排除已退款/已关闭。
*/
GoofishRrsLogisticsStatsVo selectRrsJdLinkedLogisticsStats();
} }

View File

@@ -0,0 +1,15 @@
package com.ruoyi.jarvis.mapper;
import com.ruoyi.jarvis.domain.GoodsInfo;
import java.util.List;
/**
* 京东订单商品信息 Mapper
*/
public interface GoodsInfoMapper {
GoodsInfo selectGoodsInfoById(Long id);
List<GoodsInfo> selectGoodsInfoByIds(Long[] ids);
}

View File

@@ -67,6 +67,13 @@ public interface JDOrderMapper {
* 每个型号取其主键最大的一条订单的付款 / 后返(用于快捷录单下拉回填) * 每个型号取其主键最大的一条订单的付款 / 后返(用于快捷录单下拉回填)
*/ */
List<QuickRecordModelOption> selectQuickRecordModelOptions(); List<QuickRecordModelOption> selectQuickRecordModelOptions();
/** 待拆分型号店铺的历史订单(全表扫描,由业务层按配置过滤) */
List<JDOrder> selectOrdersPendingModelShopMigration();
int updateModelShopFields(@org.apache.ibatis.annotations.Param("id") Long id,
@org.apache.ibatis.annotations.Param("modelNumber") String modelNumber,
@org.apache.ibatis.annotations.Param("modelShop") String modelShop);
} }

View File

@@ -87,4 +87,9 @@ public interface OrderRowsMapper
* @return 订单列表 * @return 订单列表
*/ */
public List<OrderRows> selectOrderRowsByGiftCouponKey(@Param("giftCouponKey") String giftCouponKey); public List<OrderRows> selectOrderRowsByGiftCouponKey(@Param("giftCouponKey") String giftCouponKey);
/**
* 根据订单号查询(匹配子单号 order_id 或父单号 parent_id
*/
public List<OrderRows> selectOrderRowsByOrderNo(@Param("orderNo") String orderNo);
} }

View File

@@ -5,6 +5,7 @@ import com.ruoyi.jarvis.domain.ErpGoofishOrder;
import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLog; import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLog;
import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLogQuery; import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLogQuery;
import com.ruoyi.jarvis.dto.GoofishShipPreviewVo; import com.ruoyi.jarvis.dto.GoofishShipPreviewVo;
import com.ruoyi.jarvis.dto.GoofishRrsLogisticsStatsVo;
import java.util.List; import java.util.List;
@@ -17,6 +18,9 @@ public interface IErpGoofishOrderService {
*/ */
void asyncPipelineAfterNotify(String appid, JSONObject notifyBody); void asyncPipelineAfterNotify(String appid, JSONObject notifyBody);
/** 日日顺 + 关联京东出库统计(侧边栏):已出库(去重运单口径)/待出库 */
GoofishRrsLogisticsStatsVo selectRrsJdLinkedLogisticsStats();
List<ErpGoofishOrder> selectList(ErpGoofishOrder query); List<ErpGoofishOrder> selectList(ErpGoofishOrder query);
ErpGoofishOrder selectById(Long id); ErpGoofishOrder selectById(Long id);

View File

@@ -0,0 +1,16 @@
package com.ruoyi.jarvis.service;
import com.ruoyi.jarvis.domain.GoodsInfo;
import java.util.Map;
import java.util.Set;
/**
* 京东订单商品信息 Service
*/
public interface IGoodsInfoService {
GoodsInfo selectGoodsInfoById(Long id);
Map<Long, GoodsInfo> selectGoodsInfoMapByIds(Set<Long> ids);
}

View File

@@ -0,0 +1,25 @@
package com.ruoyi.jarvis.service;
import java.util.Map;
import com.ruoyi.jarvis.domain.JDOrder;
import com.ruoyi.jarvis.domain.dto.QuickRecordModelShopOption;
import java.util.List;
/**
* 型号与店铺前缀拆分、归一化及历史数据迁移。
*/
public interface IJDOrderModelShopService {
List<QuickRecordModelShopOption> loadShopOptions();
void normalizeOrder(JDOrder order);
String fullModel(JDOrder order);
String lookupModelBase(String rawModel);
/** 一键迁移:将 model_number 末尾匹配的店铺前缀写入 model_shop 并从 model_number 截掉 */
Map<String, Object> migrateExistingOrders();
}

View File

@@ -11,8 +11,8 @@ public interface IJDOrderProfitService {
/** /**
* 根据分销标识、型号配置、手动标记等,填充售价(自动时)并计算利润。 * 根据分销标识、型号配置、手动标记等,填充售价(自动时)并计算利润。
* F / H-TF:利润 = 对客实收(直款=售价,闲鱼=扣点后的到账)-(下单付款 - 后返金额); * F利润 = 对客实收(直款=售价,闲鱼=扣点后的到账)-(下单付款 - 后返金额);
* H-TF 未配置型号直款价时回退为固定 15 / 凡- 开头 65 * H-TF:固定利润 15再扣额外成本
* 会修改传入的 {@code order}。 * 会修改传入的 {@code order}。
*/ */
void recalculate(JDOrder order); void recalculate(JDOrder order);

View File

@@ -52,6 +52,9 @@ public interface IJDOrderService {
/** 快捷录单:型号及最近一次单的付款 / 后返 */ /** 快捷录单:型号及最近一次单的付款 / 后返 */
List<QuickRecordModelOption> selectQuickRecordModelOptions(); List<QuickRecordModelOption> selectQuickRecordModelOptions();
/** 一键迁移:拆分 model_number 末尾店铺前缀到 model_shop */
java.util.Map<String, Object> migrateModelShopSplit();
} }

View File

@@ -72,4 +72,9 @@ public interface IOrderRowsService
public int deleteOrderRowsById(String id); public int deleteOrderRowsById(String id);
public OrderRows selectOrderRowsByOrderId(String orderId); public OrderRows selectOrderRowsByOrderId(String orderId);
/**
* 根据订单号查询(匹配子单号 order_id 或父单号 parent_id
*/
public List<OrderRows> selectOrderRowsByOrderNo(String orderNo);
} }

View File

@@ -5,8 +5,11 @@ import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.jarvis.config.JarvisGoofishProperties; import com.ruoyi.jarvis.config.JarvisGoofishProperties;
import com.ruoyi.jarvis.domain.ErpGoofishOrder; import com.ruoyi.jarvis.domain.ErpGoofishOrder;
import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLog; import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLog;
import com.ruoyi.jarvis.domain.JDOrder;
import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLogQuery; import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLogQuery;
import com.ruoyi.jarvis.domain.ErpOpenConfig;
import com.ruoyi.jarvis.dto.GoofishNotifyMessage; import com.ruoyi.jarvis.dto.GoofishNotifyMessage;
import com.ruoyi.jarvis.dto.GoofishRrsLogisticsStatsVo;
import com.ruoyi.jarvis.dto.GoofishShipPreviewVo; import com.ruoyi.jarvis.dto.GoofishShipPreviewVo;
import com.ruoyi.jarvis.mapper.ErpGoofishOrderEventLogMapper; import com.ruoyi.jarvis.mapper.ErpGoofishOrderEventLogMapper;
import com.ruoyi.jarvis.mapper.ErpGoofishOrderMapper; import com.ruoyi.jarvis.mapper.ErpGoofishOrderMapper;
@@ -81,6 +84,12 @@ public class ErpGoofishOrderServiceImpl implements IErpGoofishOrderService {
goofishOrderPipeline.runFullPipeline(appid, notifyBody); goofishOrderPipeline.runFullPipeline(appid, notifyBody);
} }
@Override
public GoofishRrsLogisticsStatsVo selectRrsJdLinkedLogisticsStats() {
GoofishRrsLogisticsStatsVo vo = erpGoofishOrderMapper.selectRrsJdLinkedLogisticsStats();
return vo != null ? vo : new GoofishRrsLogisticsStatsVo();
}
@Override @Override
public List<ErpGoofishOrder> selectList(ErpGoofishOrder query) { public List<ErpGoofishOrder> selectList(ErpGoofishOrder query) {
return erpGoofishOrderMapper.selectList(query); return erpGoofishOrderMapper.selectList(query);

View File

@@ -0,0 +1,45 @@
package com.ruoyi.jarvis.service.impl;
import com.ruoyi.jarvis.domain.GoodsInfo;
import com.ruoyi.jarvis.mapper.GoodsInfoMapper;
import com.ruoyi.jarvis.service.IGoodsInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Service
public class GoodsInfoServiceImpl implements IGoodsInfoService {
@Autowired
private GoodsInfoMapper goodsInfoMapper;
@Override
public GoodsInfo selectGoodsInfoById(Long id) {
if (id == null) {
return null;
}
return goodsInfoMapper.selectGoodsInfoById(id);
}
@Override
public Map<Long, GoodsInfo> selectGoodsInfoMapByIds(Set<Long> ids) {
if (ids == null || ids.isEmpty()) {
return Collections.emptyMap();
}
List<GoodsInfo> list = goodsInfoMapper.selectGoodsInfoByIds(ids.toArray(new Long[0]));
Map<Long, GoodsInfo> map = new HashMap<>();
if (list != null) {
for (GoodsInfo goodsInfo : list) {
if (goodsInfo != null && goodsInfo.getId() != null) {
map.put(goodsInfo.getId(), goodsInfo);
}
}
}
return map;
}
}

View File

@@ -7,6 +7,7 @@ import com.ruoyi.jarvis.domain.WeComShareLinkLogisticsJob;
import com.ruoyi.jarvis.service.IInstructionService; import com.ruoyi.jarvis.service.IInstructionService;
import com.ruoyi.jarvis.service.IOrderRowsService; import com.ruoyi.jarvis.service.IOrderRowsService;
import com.ruoyi.jarvis.service.IJDOrderService; import com.ruoyi.jarvis.service.IJDOrderService;
import com.ruoyi.jarvis.service.IJDOrderModelShopService;
import com.ruoyi.jarvis.service.IProductJdConfigService; import com.ruoyi.jarvis.service.IProductJdConfigService;
import com.ruoyi.jarvis.service.IPhoneReplaceConfigService; import com.ruoyi.jarvis.service.IPhoneReplaceConfigService;
import com.ruoyi.jarvis.service.SuperAdminService; import com.ruoyi.jarvis.service.SuperAdminService;
@@ -42,6 +43,8 @@ public class InstructionServiceImpl implements IInstructionService {
@Resource @Resource
private IJDOrderService jdOrderService; private IJDOrderService jdOrderService;
@Resource @Resource
private IJDOrderModelShopService jdOrderModelShopService;
@Resource
private SuperAdminService superAdminService; private SuperAdminService superAdminService;
@Resource @Resource
private StringRedisTemplate stringRedisTemplate; private StringRedisTemplate stringRedisTemplate;
@@ -358,13 +361,13 @@ public class InstructionServiceImpl implements IInstructionService {
// 统一截取分销标记 // 统一截取分销标记
list.forEach(order -> order.setDistributionMark(truncateDistributionMark(order.getDistributionMark()))); list.forEach(order -> order.setDistributionMark(truncateDistributionMark(order.getDistributionMark())));
String low = kw.toLowerCase(Locale.ROOT); String low = kw.toLowerCase(Locale.ROOT);
List<JDOrder> matched = list.stream().filter(o -> contains(o.getRemark(), low) || contains(o.getOrderId(), low) || contains(o.getModelNumber(), low) || contains(o.getAddress(), low) || contains(o.getBuyer(), low)).limit(50).collect(Collectors.toList()); List<JDOrder> matched = list.stream().filter(o -> contains(o.getRemark(), low) || contains(o.getOrderId(), low) || contains(o.getModelNumber(), low) || contains(o.getModelShop(), low) || contains(orderFullModel(o), low) || contains(o.getAddress(), low) || contains(o.getBuyer(), low)).limit(50).collect(Collectors.toList());
if (matched.isEmpty()) return Collections.singletonList("未找到匹配的订单"); if (matched.isEmpty()) return Collections.singletonList("未找到匹配的订单");
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
int i = 0; int i = 0;
for (JDOrder o : matched) { for (JDOrder o : matched) {
i++; i++;
sb.append(i).append(", 单:").append(nvl(o.getRemark())).append("\n分销标记").append(nvl(o.getDistributionMark())).append("\n型号").append(nvl(o.getModelNumber())).append("\n链接").append(nvl(o.getLink())).append("\n下单付款").append(nvl(o.getPaymentAmount())).append("\n后返金额").append(nvl(o.getRebateAmount())).append("\n地址").append(nvl(o.getAddress())).append("\n物流链接").append(nvl(o.getLogisticsLink())).append("\n订单号").append(nvl(o.getOrderId())).append("\n下单人").append(nvl(o.getBuyer())).append("\n下单时间").append(nvl(o.getOrderTime())).append("\n备注").append(nvl(o.getStatus())).append("\n━━━━━━━━━━━━\n"); sb.append(i).append(", 单:").append(nvl(o.getRemark())).append("\n分销标记").append(nvl(o.getDistributionMark())).append("\n型号").append(nvl(orderFullModel(o))).append("\n链接").append(nvl(o.getLink())).append("\n下单付款").append(nvl(o.getPaymentAmount())).append("\n后返金额").append(nvl(o.getRebateAmount())).append("\n地址").append(nvl(o.getAddress())).append("\n物流链接").append(nvl(o.getLogisticsLink())).append("\n订单号").append(nvl(o.getOrderId())).append("\n下单人").append(nvl(o.getBuyer())).append("\n下单时间").append(nvl(o.getOrderTime())).append("\n备注").append(nvl(o.getStatus())).append("\n━━━━━━━━━━━━\n");
} }
return Collections.singletonList(sb.toString()); return Collections.singletonList(sb.toString());
} }
@@ -388,7 +391,7 @@ public class InstructionServiceImpl implements IInstructionService {
List<JDOrder> sorted = filtered.stream().sorted(Comparator.comparing(JDOrder::getRemark, Comparator.nullsFirst(String::compareTo))).collect(Collectors.toList()); List<JDOrder> sorted = filtered.stream().sorted(Comparator.comparing(JDOrder::getRemark, Comparator.nullsFirst(String::compareTo))).collect(Collectors.toList());
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (JDOrder o : sorted) { for (JDOrder o : sorted) {
sb.append(nvl(o.getRemark())).append('\t').append(nvl(o.getOrderId())).append('\t').append(fmt(o.getOrderTime()).split(" ")[0]).append('\t').append(nvl(o.getModelNumber())).append('\t').append(nvl(o.getAddress())).append('\t').append(nvl(o.getLogisticsLink())).append('\t').append('\t').append(nvl(o.getBuyer())).append('\t').append(nvl(o.getPaymentAmount())).append('\t').append(nvl(o.getRebateAmount())).append('\t').append(mapDistribution(o.getDistributionMark())).append('\t').append(nvl(o.getStatus())).append("\n"); sb.append(nvl(o.getRemark())).append('\t').append(nvl(o.getOrderId())).append('\t').append(fmt(o.getOrderTime()).split(" ")[0]).append('\t').append(nvl(orderFullModel(o))).append('\t').append(nvl(o.getAddress())).append('\t').append(nvl(o.getLogisticsLink())).append('\t').append('\t').append(nvl(o.getBuyer())).append('\t').append(nvl(o.getPaymentAmount())).append('\t').append(nvl(o.getRebateAmount())).append('\t').append(mapDistribution(o.getDistributionMark())).append('\t').append(nvl(o.getStatus())).append("\n");
} }
return Collections.singletonList(sb.toString()); return Collections.singletonList(sb.toString());
} }
@@ -422,7 +425,7 @@ public class InstructionServiceImpl implements IInstructionService {
String dm = e.getKey() != null ? e.getKey() : "未提供"; String dm = e.getKey() != null ? e.getKey() : "未提供";
List<JDOrder> orders = e.getValue(); List<JDOrder> orders = e.getValue();
Map<String, Long> byModel = orders.stream().collect(Collectors.groupingBy(JDOrder::getModelNumber, Collectors.counting())); Map<String, Long> byModel = orders.stream().collect(Collectors.groupingBy(this::orderFullModel, Collectors.counting()));
int totalCount = 0; int totalCount = 0;
StringBuilder summary = new StringBuilder(); StringBuilder summary = new StringBuilder();
@@ -445,7 +448,7 @@ public class InstructionServiceImpl implements IInstructionService {
// 找到该型号价格最高的订单 // 找到该型号价格最高的订单
for (JDOrder order : orders) { for (JDOrder order : orders) {
if (model.equals(order.getModelNumber())) { if (model.equals(orderFullModel(order))) {
JDOrder currentMax = groupMaxPriceOrders.get(model); JDOrder currentMax = groupMaxPriceOrders.get(model);
if (currentMax == null || if (currentMax == null ||
(order.getPaymentAmount() != null && (order.getPaymentAmount() != null &&
@@ -467,7 +470,7 @@ public class InstructionServiceImpl implements IInstructionService {
.append("单:").append(o.getRemark() != null ? o.getRemark() : "未提供").append("\n") .append("单:").append(o.getRemark() != null ? o.getRemark() : "未提供").append("\n")
.append("备注:").append(o.getStatus() != null ? o.getStatus() : " ").append("\n") .append("备注:").append(o.getStatus() != null ? o.getStatus() : " ").append("\n")
.append("第三方单号:").append(o.getThirdPartyOrderNo() != null && !o.getThirdPartyOrderNo().isEmpty() ? o.getThirdPartyOrderNo() : "").append("\n") .append("第三方单号:").append(o.getThirdPartyOrderNo() != null && !o.getThirdPartyOrderNo().isEmpty() ? o.getThirdPartyOrderNo() : "").append("\n")
.append("型号:").append(o.getModelNumber() != null ? o.getModelNumber() : "未提供").append("\n") .append("型号:").append(o.getModelNumber() != null || o.getModelShop() != null ? orderFullModel(o) : "未提供").append("\n")
.append("地址:").append(o.getAddress() != null ? o.getAddress() : "未提供").append("\n") .append("地址:").append(o.getAddress() != null ? o.getAddress() : "未提供").append("\n")
.append("物流链接:\n").append(o.getLogisticsLink() != null ? o.getLogisticsLink() : ""); .append("物流链接:\n").append(o.getLogisticsLink() != null ? o.getLogisticsLink() : "");
} }
@@ -594,7 +597,7 @@ public class InstructionServiceImpl implements IInstructionService {
List<JDOrder> orders = e.getValue(); List<JDOrder> orders = e.getValue();
Map<String, Long> byModel = orders.stream() Map<String, Long> byModel = orders.stream()
.collect(Collectors.groupingBy(JDOrder::getModelNumber, Collectors.counting())); .collect(Collectors.groupingBy(this::orderFullModel, Collectors.counting()));
int totalCount = 0; int totalCount = 0;
StringBuilder summary = new StringBuilder(); StringBuilder summary = new StringBuilder();
@@ -616,7 +619,7 @@ public class InstructionServiceImpl implements IInstructionService {
// 找到该型号价格最高的订单 // 找到该型号价格最高的订单
for (JDOrder order : orders) { for (JDOrder order : orders) {
if (model.equals(order.getModelNumber())) { if (model.equals(orderFullModel(order))) {
JDOrder currentMax = buyerMaxPriceOrders.get(model); JDOrder currentMax = buyerMaxPriceOrders.get(model);
if (currentMax == null || if (currentMax == null ||
(order.getPaymentAmount() != null && (order.getPaymentAmount() != null &&
@@ -641,7 +644,7 @@ public class InstructionServiceImpl implements IInstructionService {
.append("单:").append(o.getRemark() != null ? o.getRemark() : "未提供").append("\n") .append("单:").append(o.getRemark() != null ? o.getRemark() : "未提供").append("\n")
.append("备注:").append(o.getStatus() != null ? o.getStatus() : " ").append("\n") .append("备注:").append(o.getStatus() != null ? o.getStatus() : " ").append("\n")
.append("第三方单号:").append(o.getThirdPartyOrderNo() != null && !o.getThirdPartyOrderNo().isEmpty() ? o.getThirdPartyOrderNo() : "").append("\n") .append("第三方单号:").append(o.getThirdPartyOrderNo() != null && !o.getThirdPartyOrderNo().isEmpty() ? o.getThirdPartyOrderNo() : "").append("\n")
.append("型号:").append(o.getModelNumber() != null ? o.getModelNumber() : "未提供").append("\n") .append("型号:").append(o.getModelNumber() != null || o.getModelShop() != null ? orderFullModel(o) : "未提供").append("\n")
.append("地址:").append(o.getAddress() != null ? o.getAddress() : "未提供").append("\n") .append("地址:").append(o.getAddress() != null ? o.getAddress() : "未提供").append("\n")
.append("物流链接:\n").append(o.getLogisticsLink() != null ? o.getLogisticsLink() : ""); .append("物流链接:\n").append(o.getLogisticsLink() != null ? o.getLogisticsLink() : "");
} }
@@ -874,7 +877,7 @@ public class InstructionServiceImpl implements IInstructionService {
} }
} }
String jf = productJdConfigService.getJdUrlByProductModel(modelNumber); String jf = productJdConfigService.getJdUrlByProductModel(jdOrderModelShopService.lookupModelBase(modelNumber));
StringBuilder order = new StringBuilder(); StringBuilder order = new StringBuilder();
order.append(""+phone).append("\n").append(fenxiaoInfo).append("\n").append(modelNumber).append("\n").append(jf).append("\n").append(quantityStr).append("\n").append(address); order.append(""+phone).append("\n").append(fenxiaoInfo).append("\n").append(modelNumber).append("\n").append(jf).append("\n").append(quantityStr).append("\n").append(address);
@@ -928,10 +931,11 @@ public class InstructionServiceImpl implements IInstructionService {
String rawModelToken = extractLastNonChineseToken(addressLine); String rawModelToken = extractLastNonChineseToken(addressLine);
String modelNumber = sanitizeModel(rawModelToken); String modelNumber = sanitizeModel(rawModelToken);
String lookupModel = jdOrderModelShopService.lookupModelBase(modelNumber);
String cleanedAddress = addressLine.replaceAll("\\[.*?]", "").replace(rawModelToken, "").replaceAll("\\s+", " ").trim(); String cleanedAddress = addressLine.replaceAll("\\[.*?]", "").replace(rawModelToken, "").replaceAll("\\s+", " ").trim();
String fullAddress = cleanedAddress + " 安装派送联系" + phone + (suffix.isEmpty() ? "" : "" + suffix); String fullAddress = cleanedAddress + " 安装派送联系" + phone + (suffix.isEmpty() ? "" : "" + suffix);
String jfLink = productJdConfigService.getJdUrlByProductModel(modelNumber); String jfLink = productJdConfigService.getJdUrlByProductModel(lookupModel);
StringBuilder sheng = new StringBuilder(); StringBuilder sheng = new StringBuilder();
sheng.append("\n").append(distributionMark).append("\n").append(modelNumber).append("\n").append(jfLink != null ? jfLink : "").append("\n") sheng.append("\n").append(distributionMark).append("\n").append(modelNumber).append("\n").append(jfLink != null ? jfLink : "").append("\n")
@@ -956,7 +960,7 @@ public class InstructionServiceImpl implements IInstructionService {
String distributionMark = split[1]; String distributionMark = split[1];
String link = sanitizeLink(split[3]); String link = sanitizeLink(split[3]);
if ((link == null || link.isEmpty()) && shouldAutoFillLink(distributionMark)) { if ((link == null || link.isEmpty()) && shouldAutoFillLink(distributionMark)) {
String fetched = productJdConfigService.getJdUrlByProductModel(split[2]); String fetched = productJdConfigService.getJdUrlByProductModel(jdOrderModelShopService.lookupModelBase(split[2]));
if (fetched != null && !fetched.isEmpty()) { if (fetched != null && !fetched.isEmpty()) {
link = fetched; link = fetched;
} }
@@ -1148,7 +1152,7 @@ public class InstructionServiceImpl implements IInstructionService {
} }
// 获取转链链接 // 获取转链链接
String jf = productJdConfigService.getJdUrlByProductModel(modelNumber); String jf = productJdConfigService.getJdUrlByProductModel(jdOrderModelShopService.lookupModelBase(modelNumber));
// 构建"生"指令格式 // 构建"生"指令格式
StringBuilder sheng = new StringBuilder(); StringBuilder sheng = new StringBuilder();
@@ -1453,7 +1457,7 @@ public class InstructionServiceImpl implements IInstructionService {
if (order == null) { if (order == null) {
return null; return null;
} }
String model = order.getModelNumber(); String model = jdOrderModelShopService.fullModel(order);
String address = order.getAddress(); String address = order.getAddress();
String logistics = extractOriginalLogisticsLinkNew(originalInput); String logistics = extractOriginalLogisticsLinkNew(originalInput);
if (logistics == null && order.getLogisticsLink() != null) { if (logistics == null && order.getLogisticsLink() != null) {
@@ -1515,6 +1519,7 @@ public class InstructionServiceImpl implements IInstructionService {
String originalInput = input.trim().replace("", ""); String originalInput = input.trim().replace("", "");
// 与 JDUtil.parseOrderFromText 一致的模板字段 // 与 JDUtil.parseOrderFromText 一致的模板字段
JDOrder order = parseOrderFromText(originalInput); JDOrder order = parseOrderFromText(originalInput);
jdOrderModelShopService.normalizeOrder(order);
// 字段校验并返回缺失项根据新模板格式单号存储在remark字段中 // 字段校验并返回缺失项根据新模板格式单号存储在remark字段中
StringBuilder missing = new StringBuilder(); StringBuilder missing = new StringBuilder();
@@ -1658,7 +1663,7 @@ public class InstructionServiceImpl implements IInstructionService {
sb.append("第三方单号:").append(order.getThirdPartyOrderNo().trim()).append("\n"); sb.append("第三方单号:").append(order.getThirdPartyOrderNo().trim()).append("\n");
} }
sb.append("分销标记:").append(nvl(order.getDistributionMark())).append("\n"); sb.append("分销标记:").append(nvl(order.getDistributionMark())).append("\n");
sb.append("型号:").append(nvl(order.getModelNumber())).append("\n"); sb.append("型号:").append(nvl(jdOrderModelShopService.fullModel(order))).append("\n");
sb.append("下单人:").append(nvl(order.getBuyer())).append("\n"); sb.append("下单人:").append(nvl(order.getBuyer())).append("\n");
sb.append("付款:").append(order.getPaymentAmount() != null ? order.getPaymentAmount().toString() : "").append("\n"); sb.append("付款:").append(order.getPaymentAmount() != null ? order.getPaymentAmount().toString() : "").append("\n");
sb.append("后返:").append(order.getRebateAmount() != null ? order.getRebateAmount().toString() : "").append("\n"); sb.append("后返:").append(order.getRebateAmount() != null ? order.getRebateAmount().toString() : "").append("\n");
@@ -1692,6 +1697,11 @@ public class InstructionServiceImpl implements IInstructionService {
return first; return first;
} }
/** 对外展示 / 录单文案用的完整型号(本体 + 店铺前缀) */
private String orderFullModel(JDOrder order) {
return order == null ? "" : jdOrderModelShopService.fullModel(order);
}
private boolean isEmpty(String s) { private boolean isEmpty(String s) {
return s == null || s.isEmpty(); return s == null || s.isEmpty();
} }
@@ -1730,7 +1740,7 @@ public class InstructionServiceImpl implements IInstructionService {
sb.append("下单地址(注意带分机):\n"); sb.append("下单地址(注意带分机):\n");
sb.append(order.getAddress() != null ? order.getAddress() : "").append("\n"); sb.append(order.getAddress() != null ? order.getAddress() : "").append("\n");
sb.append("—————————\n"); sb.append("—————————\n");
sb.append("型号:").append(order.getModelNumber() != null ? order.getModelNumber() : "").append("\n"); sb.append("型号:").append(orderFullModel(order)).append("\n");
sb.append("\n"); sb.append("\n");
sb.append("下单人(需填):\n"); sb.append("下单人(需填):\n");
sb.append(order.getBuyer() != null ? order.getBuyer() : "").append("\n"); sb.append(order.getBuyer() != null ? order.getBuyer() : "").append("\n");
@@ -1772,7 +1782,7 @@ public class InstructionServiceImpl implements IInstructionService {
sb.append("分销标记:").append(order.getDistributionMark() != null ? order.getDistributionMark() : "").append("\n"); sb.append("分销标记:").append(order.getDistributionMark() != null ? order.getDistributionMark() : "").append("\n");
sb.append("第三方单号:").append(order.getThirdPartyOrderNo() != null ? order.getThirdPartyOrderNo() : "").append("\n"); sb.append("第三方单号:").append(order.getThirdPartyOrderNo() != null ? order.getThirdPartyOrderNo() : "").append("\n");
sb.append("型号:\n"); sb.append("型号:\n");
sb.append(order.getModelNumber() != null ? order.getModelNumber() : "").append("\n"); sb.append(orderFullModel(order)).append("\n");
sb.append("链接:\n"); sb.append("链接:\n");
sb.append(order.getLink() != null ? order.getLink() : "").append("\n"); sb.append(order.getLink() != null ? order.getLink() : "").append("\n");
sb.append("下单付款:\n"); sb.append("下单付款:\n");

View File

@@ -0,0 +1,79 @@
package com.ruoyi.jarvis.service.impl;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.jarvis.domain.JDOrder;
import com.ruoyi.jarvis.domain.dto.QuickRecordModelShopOption;
import com.ruoyi.jarvis.mapper.JDOrderMapper;
import com.ruoyi.jarvis.service.IJDOrderModelShopService;
import com.ruoyi.jarvis.util.QuickRecordModelShopOptionUtil;
import com.ruoyi.system.service.ISysConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class JDOrderModelShopServiceImpl implements IJDOrderModelShopService {
@Autowired
private ISysConfigService sysConfigService;
@Autowired
private JDOrderMapper jdOrderMapper;
@Override
public List<QuickRecordModelShopOption> loadShopOptions() {
String raw = sysConfigService.selectConfigByKey(QuickRecordModelShopOptionUtil.CONFIG_KEY);
return QuickRecordModelShopOptionUtil.parseOptions(raw);
}
@Override
public void normalizeOrder(JDOrder order) {
QuickRecordModelShopOptionUtil.normalizeOrderModelShop(order, loadShopOptions());
}
@Override
public String fullModel(JDOrder order) {
return QuickRecordModelShopOptionUtil.fullModelNumber(order);
}
@Override
public String lookupModelBase(String rawModel) {
return QuickRecordModelShopOptionUtil.lookupModelBase(rawModel, loadShopOptions());
}
@Override
public Map<String, Object> migrateExistingOrders() {
List<QuickRecordModelShopOption> options = loadShopOptions();
Map<String, Object> result = new HashMap<>();
if (options.isEmpty()) {
result.put("updated", 0);
result.put("skipped", 0);
result.put("message", "未配置店铺选项quickRecord.modelShopOptions请先配置后再迁移");
return result;
}
List<JDOrder> candidates = jdOrderMapper.selectOrdersPendingModelShopMigration();
int updated = 0;
int skipped = 0;
for (JDOrder row : candidates) {
String beforeBase = StringUtils.trim(row.getModelNumber());
String beforeShop = StringUtils.trim(row.getModelShop());
normalizeOrder(row);
String afterBase = StringUtils.trim(row.getModelNumber());
String afterShop = StringUtils.trim(row.getModelShop());
if (beforeBase.equals(afterBase) && beforeShop.equals(afterShop)) {
skipped++;
continue;
}
jdOrderMapper.updateModelShopFields(row.getId(), row.getModelNumber(), row.getModelShop());
updated++;
}
result.put("updated", updated);
result.put("skipped", skipped);
result.put("total", candidates.size());
result.put("message", String.format("迁移完成:更新 %d 条,无变化 %d 条(共扫描 %d 条)", updated, skipped, candidates.size()));
return result;
}
}

View File

@@ -46,39 +46,25 @@ public class JDOrderProfitServiceImpl implements IJDOrderProfitService {
if ("H-TF".equals(mark)) { if ("H-TF".equals(mark)) {
if (!profitLocked) { if (!profitLocked) {
// 与 F 单一致:利润 = 对客实收 (下单付款 后返)。列表未填售价时默认直款并从型号配置取价。 // H-TF 固定利润 15再扣额外成本不走 F 单售价公式)
String type = order.getSellingPriceType(); order.setProfit(BigDecimal.valueOf(15.0 - extraCostOrZero(order))
if (type == null || type.isEmpty()) { .setScale(2, RoundingMode.HALF_UP).doubleValue());
order.setSellingPriceType("direct");
}
if (!sellingLocked && order.getSellingPrice() == null) {
fillSellingPriceFromConfig(order);
}
String effType = order.getSellingPriceType();
Double sp = order.getSellingPrice();
if (effType != null && !effType.isEmpty() && sp != null) {
computeProfitForF(order);
} else {
String buyer = order.getBuyer();
boolean fan = buyer != null && buyer.trim().startsWith("凡-");
order.setProfit(fan ? 65.0 : 15.0);
}
} }
return; return;
} }
if ("F".equals(mark) || mark.startsWith("F-")) { // 其余分销标记:与 F / F-* / PDD / H / … 共用同一利润公式(含额外成本);须有售价渠道 + 售价才自动算出利润,否则清空
if (!sellingLocked) { if (!sellingLocked) {
fillSellingPriceFromConfig(order); fillSellingPriceFromConfig(order);
}
if (!profitLocked) {
computeProfitForF(order);
}
return;
} }
if (!profitLocked) { if (!profitLocked) {
order.setProfit(null); String type = order.getSellingPriceType();
Double sp = order.getSellingPrice();
if (type != null && !type.trim().isEmpty() && sp != null) {
computeProfitForF(order);
} else {
order.setProfit(null);
}
} }
} }
@@ -127,13 +113,19 @@ public class JDOrderProfitServiceImpl implements IJDOrderProfitService {
order.setProfit(null); order.setProfit(null);
return; return;
} }
// 成本 = 下单付款 - 后返金额;利润 = 对客实收(直款=售价,闲鱼=扣点后的到账)- 成本 // 成本 = 下单付款 - 后返金额;利润 = 对客实收 成本 额外成本
double cost = BigDecimal.valueOf(pay).subtract(BigDecimal.valueOf(rebate)) double cost = BigDecimal.valueOf(pay).subtract(BigDecimal.valueOf(rebate))
.setScale(2, RoundingMode.HALF_UP).doubleValue(); .setScale(2, RoundingMode.HALF_UP).doubleValue();
order.setProfit(BigDecimal.valueOf(netReceipt - cost) double net = netReceipt - cost - extraCostOrZero(order);
order.setProfit(BigDecimal.valueOf(net)
.setScale(2, RoundingMode.HALF_UP).doubleValue()); .setScale(2, RoundingMode.HALF_UP).doubleValue());
} }
private static double extraCostOrZero(JDOrder order) {
Double e = order == null ? null : order.getExtraCost();
return e != null ? e : 0.0;
}
@Override @Override
public int syncAutoProfitIfChanged(List<Long> ids) { public int syncAutoProfitIfChanged(List<Long> ids) {
if (ids == null || ids.isEmpty()) { if (ids == null || ids.isEmpty()) {

View File

@@ -3,6 +3,7 @@ package com.ruoyi.jarvis.service.impl;
import com.ruoyi.jarvis.domain.JDOrder; import com.ruoyi.jarvis.domain.JDOrder;
import com.ruoyi.jarvis.domain.dto.QuickRecordModelOption; import com.ruoyi.jarvis.domain.dto.QuickRecordModelOption;
import com.ruoyi.jarvis.mapper.JDOrderMapper; import com.ruoyi.jarvis.mapper.JDOrderMapper;
import com.ruoyi.jarvis.service.IJDOrderModelShopService;
import com.ruoyi.jarvis.service.IJDOrderProfitService; import com.ruoyi.jarvis.service.IJDOrderProfitService;
import com.ruoyi.jarvis.service.IJDOrderService; import com.ruoyi.jarvis.service.IJDOrderService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -19,6 +20,9 @@ public class JDOrderServiceImpl implements IJDOrderService {
@Autowired @Autowired
private IJDOrderProfitService jdOrderProfitService; private IJDOrderProfitService jdOrderProfitService;
@Autowired
private IJDOrderModelShopService jdOrderModelShopService;
@Override @Override
public List<JDOrder> selectJDOrderList(JDOrder jdOrder) { public List<JDOrder> selectJDOrderList(JDOrder jdOrder) {
return jdOrderMapper.selectJDOrderList(jdOrder); return jdOrderMapper.selectJDOrderList(jdOrder);
@@ -36,12 +40,14 @@ public class JDOrderServiceImpl implements IJDOrderService {
@Override @Override
public int insertJDOrder(JDOrder jdOrder) { public int insertJDOrder(JDOrder jdOrder) {
jdOrderModelShopService.normalizeOrder(jdOrder);
jdOrderProfitService.recalculate(jdOrder); jdOrderProfitService.recalculate(jdOrder);
return jdOrderMapper.insertJDOrder(jdOrder); return jdOrderMapper.insertJDOrder(jdOrder);
} }
@Override @Override
public int updateJDOrder(JDOrder jdOrder) { public int updateJDOrder(JDOrder jdOrder) {
jdOrderModelShopService.normalizeOrder(jdOrder);
return jdOrderMapper.updateJDOrder(jdOrder); return jdOrderMapper.updateJDOrder(jdOrder);
} }
@@ -87,6 +93,11 @@ public class JDOrderServiceImpl implements IJDOrderService {
public List<QuickRecordModelOption> selectQuickRecordModelOptions() { public List<QuickRecordModelOption> selectQuickRecordModelOptions() {
return jdOrderMapper.selectQuickRecordModelOptions(); return jdOrderMapper.selectQuickRecordModelOptions();
} }
@Override
public java.util.Map<String, Object> migrateModelShopSplit() {
return jdOrderModelShopService.migrateExistingOrders();
}
} }

View File

@@ -63,13 +63,13 @@ public class OpenPhoneForwardService {
private static final String BOT_OPEN = "AJL05_bot"; private static final String BOT_OPEN = "AJL05_bot";
/** 「慢开」→ 对应 Bot 用户名 */ /** 「慢开」→ 对应 Bot 用户名 */
private static final String BOT_SLOW_OPEN = "QingBaoJuZQbot"; private static final String BOT_SLOW_OPEN = "QingBaoZhuQuebot";
private static final int REPLY_TAKE_NTH_OPEN_BOT = 2; private static final int REPLY_TAKE_NTH_OPEN_BOT = 2;
/** /**
* 情报局推广条Telegram 常为 Markdown * 情报局推广条Telegram 常为 Markdown
* {@code **👉‍**[**文案**](url)**👈**};另保留旧版纯文本。 * {@code **👉‍**[**文案**](url)**👈**}、{@code 👉‍[文案](url)👈};另保留旧版纯文本。
*/ */
private static final Pattern[] QINGBAO_REPLY_JUNK_PATTERNS = { private static final Pattern[] QINGBAO_REPLY_JUNK_PATTERNS = {
Pattern.compile( Pattern.compile(
@@ -78,6 +78,12 @@ public class OpenPhoneForwardService {
Pattern.compile( Pattern.compile(
"\\*\\*👉\u200D?\\*\\*\\[\\*\\*如机器人提示被注销点我防丢\\*\\*\\]" "\\*\\*👉\u200D?\\*\\*\\[\\*\\*如机器人提示被注销点我防丢\\*\\*\\]"
+ "\\(https?://telegra\\.ph/qingbaoju-10-01\\)\\*\\*👈\\*\\*"), + "\\(https?://telegra\\.ph/qingbaoju-10-01\\)\\*\\*👈\\*\\*"),
Pattern.compile(
"\uD83D\uDC49\u200D?\\[公安路线查询价格表\\]"
+ "\\(https?://t\\.me/\\+C20ADPmEKJU0ZGFl\\)\uD83D\uDC48"),
Pattern.compile(
"\uD83D\uDC49\u200D?\\[如机器人提示被注销点我防丢\\]"
+ "\\(https?://telegra\\.ph/qingbaoju-10-01\\)\uD83D\uDC48"),
}; };
private static final String[] QINGBAO_REPLY_JUNK_LITERAL = { private static final String[] QINGBAO_REPLY_JUNK_LITERAL = {
@@ -85,6 +91,10 @@ public class OpenPhoneForwardService {
"\uD83D\uDC49\u200D如机器人提示被注销点我防丢 (https://telegra.ph/qingbaoju-10-01)\uD83D\uDC48", "\uD83D\uDC49\u200D如机器人提示被注销点我防丢 (https://telegra.ph/qingbaoju-10-01)\uD83D\uDC48",
"\uD83D\uDC49公安路线查询价格表 (https://t.me/+C20ADPmEKJU0ZGFl)\uD83D\uDC48", "\uD83D\uDC49公安路线查询价格表 (https://t.me/+C20ADPmEKJU0ZGFl)\uD83D\uDC48",
"\uD83D\uDC49如机器人提示被注销点我防丢 (https://telegra.ph/qingbaoju-10-01)\uD83D\uDC48", "\uD83D\uDC49如机器人提示被注销点我防丢 (https://telegra.ph/qingbaoju-10-01)\uD83D\uDC48",
"\uD83D\uDC49\u200D[公安路线查询价格表](https://t.me/+C20ADPmEKJU0ZGFl)\uD83D\uDC48",
"\uD83D\uDC49\u200D[如机器人提示被注销点我防丢](https://telegra.ph/qingbaoju-10-01)\uD83D\uDC48",
"\uD83D\uDC49[公安路线查询价格表](https://t.me/+C20ADPmEKJU0ZGFl)\uD83D\uDC48",
"\uD83D\uDC49[如机器人提示被注销点我防丢](https://telegra.ph/qingbaoju-10-01)\uD83D\uDC48",
}; };
@Value("${jarvis.phone-forward.enabled:false}") @Value("${jarvis.phone-forward.enabled:false}")

View File

@@ -112,4 +112,9 @@ public class OrderRowsServiceImpl implements IOrderRowsService
public OrderRows selectOrderRowsByOrderId(String orderId) { public OrderRows selectOrderRowsByOrderId(String orderId) {
return orderRowsMapper.selectOrderRowsByOrderId(orderId); return orderRowsMapper.selectOrderRowsByOrderId(orderId);
} }
@Override
public List<OrderRows> selectOrderRowsByOrderNo(String orderNo) {
return orderRowsMapper.selectOrderRowsByOrderNo(orderNo);
}
} }

View File

@@ -1021,7 +1021,7 @@ public class SocialMediaServiceImpl implements ISocialMediaService
// 标题行 // 标题行
StringBuilder daixiadanBuilder = new StringBuilder(); StringBuilder daixiadanBuilder = new StringBuilder();
daixiadanBuilder.append("(一键代下) ").append(cleanTitle).append("\n"); daixiadanBuilder.append(cleanTitle).append("\n");
// 型号行(可选) // 型号行(可选)
if (StringUtils.isNotEmpty(cleanRemark)) { if (StringUtils.isNotEmpty(cleanRemark)) {
daixiadanBuilder.append("型号:").append(cleanRemark).append("\n"); daixiadanBuilder.append("型号:").append(cleanRemark).append("\n");
@@ -1030,11 +1030,10 @@ public class SocialMediaServiceImpl implements ISocialMediaService
// 教你下单版 // 教你下单版
StringBuilder jiaonixiadanBuilder = new StringBuilder(); StringBuilder jiaonixiadanBuilder = new StringBuilder();
jiaonixiadanBuilder.append("【教你下单】 ").append(cleanTitle).append("\n"); jiaonixiadanBuilder.append(cleanTitle).append("\n");
if (StringUtils.isNotEmpty(cleanRemark)) { if (StringUtils.isNotEmpty(cleanRemark)) {
jiaonixiadanBuilder.append("型号:").append(cleanRemark).append("\n"); jiaonixiadanBuilder.append("型号:").append(cleanRemark).append("\n");
} }
jiaonixiadanBuilder.append(wenanBase).append("\n\n");
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd"); java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd");
String dateStr = sdf.format(new java.util.Date()); String dateStr = sdf.format(new java.util.Date());

View File

@@ -15,7 +15,7 @@ import java.util.List;
/** /**
* 物流信息扫描定时任务 * 物流信息扫描定时任务
* 按配置周期(默认每 20 分钟)扫描分销标记为 F/PDD 等的订单(最近 30 天),拉物流并推送; * 按配置周期(默认每 5 分钟)扫描分销标记为 F/PDD 等的订单(最近 30 天),拉物流并推送;
* 结束后处理企微分享链 adhoc 队列。 * 结束后处理企微分享链 adhoc 队列。
*/ */
@Component @Component
@@ -39,7 +39,7 @@ public class LogisticsScanTask {
/** /**
* 只扫描最近 30 天的订单SQL 固定);周期与单轮上限见 jarvis.server.logistics.scan.* * 只扫描最近 30 天的订单SQL 固定);周期与单轮上限见 jarvis.server.logistics.scan.*
*/ */
@Scheduled(cron = "${jarvis.server.logistics.scan.cron:0 */20 * * * ?}") @Scheduled(cron = "${jarvis.server.logistics.scan.cron:0 */5 * * * ?}")
public void scanAndFetchLogistics() { public void scanAndFetchLogistics() {
long t0 = System.currentTimeMillis(); long t0 = System.currentTimeMillis();
int orderCandidates = 0; int orderCandidates = 0;

View File

@@ -0,0 +1,146 @@
package com.ruoyi.jarvis.util;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.jarvis.domain.dto.QuickRecordModelShopOption;
import com.ruoyi.jarvis.domain.JDOrder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 解析快捷录单型号店铺配置:每行/逗号分隔,格式「短前缀(完整店名)」。
*/
public final class QuickRecordModelShopOptionUtil {
public static final String CONFIG_KEY = "quickRecord.modelShopOptions";
private static final Pattern LINE_PATTERN = Pattern.compile("^(.+?)[(](.+)[)]$");
private QuickRecordModelShopOptionUtil() {
}
public static List<QuickRecordModelShopOption> parseOptions(String raw) {
if (StringUtils.isEmpty(raw)) {
return Collections.emptyList();
}
String[] parts = raw.split("[\\r\\n,]+");
List<QuickRecordModelShopOption> list = new ArrayList<>();
for (String part : parts) {
if (StringUtils.isEmpty(part)) {
continue;
}
QuickRecordModelShopOption opt = parseOneLine(part.trim());
if (opt != null && StringUtils.isNotEmpty(opt.getPrefix())) {
list.add(opt);
}
}
return list;
}
private static QuickRecordModelShopOption parseOneLine(String line) {
if (StringUtils.isEmpty(line)) {
return null;
}
Matcher m = LINE_PATTERN.matcher(line);
QuickRecordModelShopOption opt = new QuickRecordModelShopOption();
if (m.matches()) {
opt.setPrefix(m.group(1).trim());
opt.setFullName(m.group(2).trim());
} else {
opt.setPrefix(line.trim());
opt.setFullName("");
}
opt.setLabel(buildLabel(opt.getPrefix(), opt.getFullName()));
return opt;
}
private static String buildLabel(String prefix, String fullName) {
if (StringUtils.isEmpty(fullName)) {
return prefix;
}
return prefix + "" + fullName + "";
}
/**
* 从完整型号末尾匹配已知店铺前缀(长前缀优先),用于回显拆分。
*/
public static String[] splitModelSuffix(String fullModel, List<QuickRecordModelShopOption> options) {
String full = StringUtils.trim(fullModel);
if (StringUtils.isEmpty(full) || options == null || options.isEmpty()) {
return new String[] { full, "" };
}
List<QuickRecordModelShopOption> sorted = new ArrayList<>(options);
sorted.sort((a, b) -> {
int la = a.getPrefix() != null ? a.getPrefix().length() : 0;
int lb = b.getPrefix() != null ? b.getPrefix().length() : 0;
return Integer.compare(lb, la);
});
for (QuickRecordModelShopOption opt : sorted) {
String prefix = opt.getPrefix();
if (StringUtils.isEmpty(prefix)) {
continue;
}
if (full.endsWith(prefix) && full.length() > prefix.length()) {
return new String[] { full.substring(0, full.length() - prefix.length()), prefix };
}
}
return new String[] { full, "" };
}
public static String fullModelNumber(String base, String shop) {
String b = StringUtils.trim(base);
String s = StringUtils.trim(shop);
if (StringUtils.isEmpty(b)) {
return s;
}
if (StringUtils.isEmpty(s)) {
return b;
}
if (b.endsWith(s)) {
return b;
}
return b + s;
}
public static String fullModelNumber(JDOrder order) {
if (order == null) {
return "";
}
return fullModelNumber(order.getModelNumber(), order.getModelShop());
}
/**
* 落库前归一化model_number 仅存本体model_shop 存店铺前缀。
* 若 model_shop 已有值则仅纠正 model_number 仍带后缀的情况。
*/
public static void normalizeOrderModelShop(JDOrder order, List<QuickRecordModelShopOption> options) {
if (order == null) {
return;
}
String base = StringUtils.trim(order.getModelNumber());
String shop = StringUtils.trim(order.getModelShop());
if (StringUtils.isNotEmpty(shop)) {
if (StringUtils.isNotEmpty(base) && base.endsWith(shop) && base.length() > shop.length()) {
order.setModelNumber(base.substring(0, base.length() - shop.length()));
}
order.setModelShop(shop);
return;
}
if (StringUtils.isEmpty(base)) {
order.setModelShop("");
return;
}
String[] split = splitModelSuffix(base, options);
order.setModelNumber(split[0]);
order.setModelShop(split[1]);
}
/** 京粉/型号配置 lookup 用本体型号 */
public static String lookupModelBase(String rawModel, List<QuickRecordModelShopOption> options) {
String[] split = splitModelSuffix(StringUtils.trim(rawModel), options);
return split[0];
}
}

View File

@@ -182,4 +182,65 @@
update_time = now() update_time = now()
where id = #{id} where id = #{id}
</update> </update>
<!-- 日日顺 + 已关联京东:已出库/待出库口径与 erpGoofishOrder 列表「手动推送」一致(有单号或已发货状态) -->
<select id="selectRrsJdLinkedLogisticsStats"
resultType="com.ruoyi.jarvis.dto.GoofishRrsLogisticsStatsVo">
SELECT
(
IFNULL(COUNT(DISTINCT CASE
WHEN e.jd_order_id IS NOT NULL
AND (
LOWER(TRIM(IFNULL(e.detail_express_code, ''))) = 'rrs'
OR LOWER(TRIM(IFNULL(e.ship_express_code, ''))) = 'rrs'
OR IFNULL(e.detail_express_name, '') LIKE CONCAT('%', '日日顺', '%')
)
AND (e.order_status IS NULL OR e.order_status NOT IN (23, 24))
AND (
LENGTH(TRIM(IFNULL(e.local_waybill_no, ''))) &gt; 0
OR LENGTH(TRIM(IFNULL(e.detail_waybill_no, ''))) &gt; 0
)
THEN CASE
WHEN LENGTH(TRIM(IFNULL(e.local_waybill_no, ''))) &gt; 0
THEN TRIM(e.local_waybill_no)
ELSE TRIM(e.detail_waybill_no)
END
ELSE NULL
END), 0)
+
IFNULL(COUNT(CASE
WHEN e.jd_order_id IS NOT NULL
AND (
LOWER(TRIM(IFNULL(e.detail_express_code, ''))) = 'rrs'
OR LOWER(TRIM(IFNULL(e.ship_express_code, ''))) = 'rrs'
OR IFNULL(e.detail_express_name, '') LIKE CONCAT('%', '日日顺', '%')
)
AND (e.order_status IS NULL OR e.order_status NOT IN (23, 24))
AND LENGTH(TRIM(IFNULL(e.local_waybill_no, ''))) = 0
AND LENGTH(TRIM(IFNULL(e.detail_waybill_no, ''))) = 0
AND (
IFNULL(e.ship_status, 0) = 1
OR IFNULL(e.order_status, 0) IN (21, 22)
)
THEN 1
ELSE NULL
END), 0)
) AS shippedOutboundCount,
IFNULL(COUNT(CASE
WHEN e.jd_order_id IS NOT NULL
AND (
LOWER(TRIM(IFNULL(e.detail_express_code, ''))) = 'rrs'
OR LOWER(TRIM(IFNULL(e.ship_express_code, ''))) = 'rrs'
OR IFNULL(e.detail_express_name, '') LIKE CONCAT('%', '日日顺', '%')
)
AND (e.order_status IS NULL OR e.order_status NOT IN (23, 24))
AND IFNULL(e.order_status, 0) = 12
AND LENGTH(TRIM(IFNULL(e.local_waybill_no, ''))) = 0
AND LENGTH(TRIM(IFNULL(e.detail_waybill_no, ''))) = 0
AND (e.ship_status IS NULL OR e.ship_status != 1)
THEN 1
ELSE NULL
END), 0) AS pendingOutboundCount
FROM erp_goofish_order e
</select>
</mapper> </mapper>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.jarvis.mapper.GoodsInfoMapper">
<resultMap type="GoodsInfo" id="GoodsInfoResult">
<result property="id" column="id"/>
<result property="owner" column="owner"/>
<result property="mainSkuId" column="main_sku_id"/>
<result property="productId" column="product_id"/>
<result property="imageUrl" column="image_url"/>
<result property="shopName" column="shop_name"/>
<result property="shopId" column="shop_id"/>
</resultMap>
<sql id="selectGoodsInfoVo">
select id, owner, main_sku_id, product_id, image_url, shop_name, shop_id from goods_info
</sql>
<select id="selectGoodsInfoById" parameterType="Long" resultMap="GoodsInfoResult">
<include refid="selectGoodsInfoVo"/>
where id = #{id}
</select>
<select id="selectGoodsInfoByIds" resultMap="GoodsInfoResult">
<include refid="selectGoodsInfoVo"/>
where id in
<foreach collection="array" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
</mapper>

View File

@@ -7,6 +7,7 @@
<result property="remark" column="remark"/> <result property="remark" column="remark"/>
<result property="distributionMark" column="distribution_mark"/> <result property="distributionMark" column="distribution_mark"/>
<result property="modelNumber" column="model_number"/> <result property="modelNumber" column="model_number"/>
<result property="modelShop" column="model_shop"/>
<result property="link" column="link"/> <result property="link" column="link"/>
<result property="paymentAmount" column="payment_amount"/> <result property="paymentAmount" column="payment_amount"/>
<result property="rebateAmount" column="rebate_amount"/> <result property="rebateAmount" column="rebate_amount"/>
@@ -42,15 +43,16 @@
<result property="profit" column="profit"/> <result property="profit" column="profit"/>
<result property="sellingPriceManual" column="selling_price_manual"/> <result property="sellingPriceManual" column="selling_price_manual"/>
<result property="profitManual" column="profit_manual"/> <result property="profitManual" column="profit_manual"/>
<result property="extraCost" column="extra_cost"/>
</resultMap> </resultMap>
<sql id="selectJDOrderBase"> <sql id="selectJDOrderBase">
select id, remark, distribution_mark, model_number, link, payment_amount, rebate_amount, select id, remark, distribution_mark, model_number, model_shop, link, payment_amount, rebate_amount,
address, logistics_link, order_id, buyer, order_time, create_time, update_time, status, is_count_enabled, third_party_order_no, jingfen_actual_price, address, logistics_link, order_id, buyer, order_time, create_time, update_time, status, is_count_enabled, third_party_order_no, jingfen_actual_price,
is_refunded, refund_date, is_refund_received, refund_received_date, is_rebate_received, rebate_received_date, is_refunded, refund_date, is_refund_received, refund_received_date, is_rebate_received, rebate_received_date,
is_price_protected, price_protected_date, is_invoice_opened, invoice_opened_date, is_review_posted, review_posted_date, is_price_protected, price_protected_date, is_invoice_opened, invoice_opened_date, is_review_posted, review_posted_date,
rebate_remark_json, rebate_remark_has_abnormal, rebate_remark_json, rebate_remark_has_abnormal,
selling_price_type, selling_price, profit, selling_price_manual, profit_manual selling_price_type, selling_price, profit, selling_price_manual, profit_manual, extra_cost
from jd_order from jd_order
</sql> </sql>
@@ -66,8 +68,18 @@
) )
</if> </if>
<if test="distributionMark != null and distributionMark != ''"> and distribution_mark = #{distributionMark}</if> <if test="distributionMark != null and distributionMark != ''"> and distribution_mark = #{distributionMark}</if>
<if test="modelNumber != null and modelNumber != ''"> and model_number like concat('%', #{modelNumber}, '%')</if> <if test="modelNumber != null and modelNumber != ''">
<if test="modelNumberExclude != null and modelNumberExclude != ''"> and (model_number is null or model_number not like concat('%', #{modelNumberExclude}, '%'))</if> and (
model_number like concat('%', #{modelNumber}, '%')
or concat(IFNULL(model_number, ''), IFNULL(model_shop, '')) like concat('%', #{modelNumber}, '%')
)
</if>
<if test="modelShop != null and modelShop != ''"> and model_shop = #{modelShop}</if>
<if test="params.modelNumberExcludeList != null and params.modelNumberExcludeList.size() > 0">
<foreach collection="params.modelNumberExcludeList" item="ex">
and (model_number is null or model_number not like concat('%', #{ex}, '%'))
</foreach>
</if>
<if test="link != null and link != ''"> and link like concat('%', #{link}, '%')</if> <if test="link != null and link != ''"> and link like concat('%', #{link}, '%')</if>
<if test="paymentAmount != null"> and payment_amount = #{paymentAmount}</if> <if test="paymentAmount != null"> and payment_amount = #{paymentAmount}</if>
<if test="rebateAmount != null"> and rebate_amount = #{rebateAmount}</if> <if test="rebateAmount != null"> and rebate_amount = #{rebateAmount}</if>
@@ -115,8 +127,18 @@
) )
</if> </if>
<if test="distributionMark != null and distributionMark != ''"> and distribution_mark = #{distributionMark}</if> <if test="distributionMark != null and distributionMark != ''"> and distribution_mark = #{distributionMark}</if>
<if test="modelNumber != null and modelNumber != ''"> and model_number like concat('%', #{modelNumber}, '%')</if> <if test="modelNumber != null and modelNumber != ''">
<if test="modelNumberExclude != null and modelNumberExclude != ''"> and (model_number is null or model_number not like concat('%', #{modelNumberExclude}, '%'))</if> and (
model_number like concat('%', #{modelNumber}, '%')
or concat(IFNULL(model_number, ''), IFNULL(model_shop, '')) like concat('%', #{modelNumber}, '%')
)
</if>
<if test="modelShop != null and modelShop != ''"> and model_shop = #{modelShop}</if>
<if test="params.modelNumberExcludeList != null and params.modelNumberExcludeList.size() > 0">
<foreach collection="params.modelNumberExcludeList" item="ex">
and (model_number is null or model_number not like concat('%', #{ex}, '%'))
</foreach>
</if>
<if test="link != null and link != ''"> and link like concat('%', #{link}, '%')</if> <if test="link != null and link != ''"> and link like concat('%', #{link}, '%')</if>
<if test="paymentAmount != null"> and payment_amount = #{paymentAmount}</if> <if test="paymentAmount != null"> and payment_amount = #{paymentAmount}</if>
<if test="rebateAmount != null"> and rebate_amount = #{rebateAmount}</if> <if test="rebateAmount != null"> and rebate_amount = #{rebateAmount}</if>
@@ -168,21 +190,21 @@
<insert id="insertJDOrder" parameterType="JDOrder" useGeneratedKeys="true" keyProperty="id"> <insert id="insertJDOrder" parameterType="JDOrder" useGeneratedKeys="true" keyProperty="id">
insert into jd_order ( insert into jd_order (
remark, distribution_mark, model_number, link, remark, distribution_mark, model_number, model_shop, link,
payment_amount, rebate_amount, address, logistics_link, payment_amount, rebate_amount, address, logistics_link,
tencent_doc_pushed, tencent_doc_push_time, tencent_doc_pushed, tencent_doc_push_time,
order_id, buyer, order_time, create_time, update_time, status, is_count_enabled, third_party_order_no, jingfen_actual_price, order_id, buyer, order_time, create_time, update_time, status, is_count_enabled, third_party_order_no, jingfen_actual_price,
is_refunded, refund_date, is_refund_received, refund_received_date, is_rebate_received, rebate_received_date, is_refunded, refund_date, is_refund_received, refund_received_date, is_rebate_received, rebate_received_date,
is_price_protected, price_protected_date, is_invoice_opened, invoice_opened_date, is_review_posted, review_posted_date, is_price_protected, price_protected_date, is_invoice_opened, invoice_opened_date, is_review_posted, review_posted_date,
selling_price_type, selling_price, profit, selling_price_manual, profit_manual selling_price_type, selling_price, profit, selling_price_manual, profit_manual, extra_cost
) values ( ) values (
#{remark}, #{distributionMark}, #{modelNumber}, #{link}, #{remark}, #{distributionMark}, #{modelNumber}, #{modelShop}, #{link},
#{paymentAmount}, #{rebateAmount}, #{address}, #{logisticsLink}, #{paymentAmount}, #{rebateAmount}, #{address}, #{logisticsLink},
0, null, 0, null,
#{orderId}, #{buyer}, #{orderTime}, now(), now(), #{status}, #{isCountEnabled}, #{thirdPartyOrderNo}, #{jingfenActualPrice}, #{orderId}, #{buyer}, #{orderTime}, now(), now(), #{status}, #{isCountEnabled}, #{thirdPartyOrderNo}, #{jingfenActualPrice},
#{isRefunded}, #{refundDate}, #{isRefundReceived}, #{refundReceivedDate}, #{isRebateReceived}, #{rebateReceivedDate}, #{isRefunded}, #{refundDate}, #{isRefundReceived}, #{refundReceivedDate}, #{isRebateReceived}, #{rebateReceivedDate},
#{isPriceProtected}, #{priceProtectedDate}, #{isInvoiceOpened}, #{invoiceOpenedDate}, #{isReviewPosted}, #{reviewPostedDate}, #{isPriceProtected}, #{priceProtectedDate}, #{isInvoiceOpened}, #{invoiceOpenedDate}, #{isReviewPosted}, #{reviewPostedDate},
#{sellingPriceType}, #{sellingPrice}, #{profit}, #{sellingPriceManual}, #{profitManual} #{sellingPriceType}, #{sellingPrice}, #{profit}, #{sellingPriceManual}, #{profitManual}, #{extraCost,jdbcType=DOUBLE}
) )
</insert> </insert>
@@ -192,6 +214,7 @@
<if test="remark != null"> remark = #{remark},</if> <if test="remark != null"> remark = #{remark},</if>
<if test="distributionMark != null"> distribution_mark = #{distributionMark},</if> <if test="distributionMark != null"> distribution_mark = #{distributionMark},</if>
<if test="modelNumber != null"> model_number = #{modelNumber},</if> <if test="modelNumber != null"> model_number = #{modelNumber},</if>
<if test="modelShop != null"> model_shop = #{modelShop},</if>
<if test="link != null"> link = #{link},</if> <if test="link != null"> link = #{link},</if>
<if test="paymentAmount != null"> payment_amount = #{paymentAmount},</if> <if test="paymentAmount != null"> payment_amount = #{paymentAmount},</if>
<if test="rebateAmount != null"> rebate_amount = #{rebateAmount},</if> <if test="rebateAmount != null"> rebate_amount = #{rebateAmount},</if>
@@ -220,12 +243,14 @@
<if test="reviewPostedDate != null"> review_posted_date = #{reviewPostedDate},</if> <if test="reviewPostedDate != null"> review_posted_date = #{reviewPostedDate},</if>
<if test="rebateRemarkJson != null"> rebate_remark_json = #{rebateRemarkJson},</if> <if test="rebateRemarkJson != null"> rebate_remark_json = #{rebateRemarkJson},</if>
<if test="rebateRemarkHasAbnormal != null"> rebate_remark_has_abnormal = #{rebateRemarkHasAbnormal},</if> <if test="rebateRemarkHasAbnormal != null"> rebate_remark_has_abnormal = #{rebateRemarkHasAbnormal},</if>
<!-- extra_cost随列表保存一并写入 applyProfitFields避免仅写了 profit 却未持久化额外成本 -->
<if test="params != null and params.applyProfitFields != null and params.applyProfitFields == true"> <if test="params != null and params.applyProfitFields != null and params.applyProfitFields == true">
selling_price_type = #{sellingPriceType,jdbcType=VARCHAR}, selling_price_type = #{sellingPriceType,jdbcType=VARCHAR},
selling_price = #{sellingPrice,jdbcType=DOUBLE}, selling_price = #{sellingPrice,jdbcType=DOUBLE},
profit = #{profit,jdbcType=DOUBLE}, profit = #{profit,jdbcType=DOUBLE},
selling_price_manual = #{sellingPriceManual,jdbcType=INTEGER}, selling_price_manual = #{sellingPriceManual,jdbcType=INTEGER},
profit_manual = #{profitManual,jdbcType=INTEGER}, profit_manual = #{profitManual,jdbcType=INTEGER},
extra_cost = COALESCE(#{extraCost,jdbcType=DOUBLE}, 0),
</if> </if>
update_time = now() update_time = now()
</set> </set>
@@ -294,24 +319,45 @@
<resultMap id="QuickRecordModelOptionResult" type="com.ruoyi.jarvis.domain.dto.QuickRecordModelOption"> <resultMap id="QuickRecordModelOptionResult" type="com.ruoyi.jarvis.domain.dto.QuickRecordModelOption">
<result property="modelNumber" column="model_number"/> <result property="modelNumber" column="model_number"/>
<result property="modelShop" column="model_shop"/>
<result property="lastPaymentAmount" column="last_payment_amount"/> <result property="lastPaymentAmount" column="last_payment_amount"/>
<result property="lastRebateAmount" column="last_rebate_amount"/> <result property="lastRebateAmount" column="last_rebate_amount"/>
</resultMap> </resultMap>
<select id="selectQuickRecordModelOptions" resultMap="QuickRecordModelOptionResult"> <select id="selectQuickRecordModelOptions" resultMap="QuickRecordModelOptionResult">
select o.model_number as model_number, select o.model_number as model_number,
o.model_shop as model_shop,
o.payment_amount as last_payment_amount, o.payment_amount as last_payment_amount,
o.rebate_amount as last_rebate_amount o.rebate_amount as last_rebate_amount
from jd_order o from jd_order o
inner join ( inner join (
select trim(model_number) as m, max(id) as mid select trim(IFNULL(model_number, '')) as m,
trim(IFNULL(model_shop, '')) as s,
max(id) as mid
from jd_order from jd_order
where model_number is not null and trim(model_number) != '' where model_number is not null and trim(model_number) != ''
group by trim(model_number) group by trim(IFNULL(model_number, '')), trim(IFNULL(model_shop, ''))
) t on trim(o.model_number) = t.m and o.id = t.mid ) t on trim(IFNULL(o.model_number, '')) = t.m
and trim(IFNULL(o.model_shop, '')) = t.s
and o.id = t.mid
order by o.id desc order by o.id desc
</select> </select>
<select id="selectOrdersPendingModelShopMigration" resultMap="JDOrderResult">
<include refid="selectJDOrderBase"/>
where model_number is not null
and trim(model_number) != ''
order by id asc
</select>
<update id="updateModelShopFields">
update jd_order
set model_number = #{modelNumber},
model_shop = #{modelShop},
update_time = now()
where id = #{id}
</update>
</mapper> </mapper>

View File

@@ -398,6 +398,12 @@
where order_id = #{orderId} where order_id = #{orderId}
</select> </select>
<select id="selectOrderRowsByOrderNo" parameterType="String" resultMap="OrderRowsResult">
<include refid="selectOrderRowsVo"/>
where order_id = #{orderNo} or parent_id = #{orderNo}
order by order_time desc
</select>
<select id="selectOrderRowsByGiftCouponKey" parameterType="String" resultMap="OrderRowsResult"> <select id="selectOrderRowsByGiftCouponKey" parameterType="String" resultMap="OrderRowsResult">
<include refid="selectOrderRowsVo"/> <include refid="selectOrderRowsVo"/>
where gift_coupon_key = #{giftCouponKey} where gift_coupon_key = #{giftCouponKey}

View File

@@ -0,0 +1,29 @@
# 京粉订单 goodsInfo 回填脚本(需先部署 Jarvis_java + ruoyi-java并已登录获取 token
# 用法示例见下方 $body
param(
[string]$BaseUrl = "https://jarvis.van333.cn/jarvis-api",
[string]$Token = "",
[string]$OrderIds = "",
[switch]$Missing,
[int]$Limit = 100
)
$body = @{}
if ($Missing) {
$body.missing = $true
$body.limit = $Limit
} elseif ($OrderIds) {
$body.orderIds = $OrderIds
} else {
Write-Host "请指定 -OrderIds '订单号1,订单号2' 或 -Missing"
exit 1
}
$headers = @{ "Content-Type" = "application/json" }
if ($Token) {
$headers.Authorization = "Bearer $Token"
}
$uri = "$BaseUrl/system/jdorder/orderRows/backfillGoodsInfo"
Invoke-RestMethod -Method Post -Uri $uri -Headers $headers -Body ($body | ConvertTo-Json)

View File

@@ -0,0 +1,8 @@
-- 预览:待回填 goods_info 的订单号goods_info_id 为空)
SELECT order_id
FROM order_rows
WHERE goods_info_id IS NULL
AND order_id IS NOT NULL
GROUP BY order_id
ORDER BY MAX(order_time) DESC
LIMIT 100;

10
sql/goods_info.sql Normal file
View File

@@ -0,0 +1,10 @@
-- 京东联盟订单商品信息goodsInfo 字段入库)
CREATE TABLE IF NOT EXISTS goods_info (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
owner VARCHAR(16) DEFAULT NULL COMMENT 'g=自营 p=pop',
main_sku_id VARCHAR(64) DEFAULT NULL COMMENT '自营商品主Id',
product_id VARCHAR(64) DEFAULT NULL COMMENT 'POP商品主Id',
image_url VARCHAR(512) DEFAULT NULL COMMENT '商品主图',
shop_name VARCHAR(255) DEFAULT NULL COMMENT '店铺名称',
shop_id VARCHAR(64) DEFAULT NULL COMMENT '店铺Id'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='京东订单商品信息';

View File

@@ -0,0 +1,2 @@
-- JD 订单:额外成本(计入 F/H-TF 自动利润:利润 = 对客实收 (付款−后返) extra_cost
ALTER TABLE jd_order ADD COLUMN extra_cost DOUBLE NOT NULL DEFAULT 0 COMMENT '额外成本手动录入默认0' AFTER profit_manual;

View File

@@ -0,0 +1,2 @@
-- 京东订单:型号店铺短前缀(与 model_number 拆分存储,便于筛选;完整对外型号 = model_number + model_shop
ALTER TABLE jd_order ADD COLUMN model_shop VARCHAR(64) DEFAULT NULL COMMENT '型号店铺短前缀(如海尔厨房)';