diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/InstructionServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/InstructionServiceImpl.java index 77f289a..505202f 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/InstructionServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/InstructionServiceImpl.java @@ -423,48 +423,48 @@ public class InstructionServiceImpl implements IInstructionService { // ==================== 追加:按下单人分组统计 ==================== outputs.add("\n━━━━━━━ 按下单人统计 ━━━━━━━"); - + // 按下单人分组(过滤掉拍错退款) Map> byBuyer = filtered.stream() .filter(o -> o.getStatus() == null || !"拍错退款".equals(o.getStatus())) .filter(o -> o.getBuyer() != null && !o.getBuyer().isEmpty()) .collect(Collectors.groupingBy(JDOrder::getBuyer)); - + List>> buyerEntries = new ArrayList<>(byBuyer.entrySet()); buyerEntries.sort(Comparator.comparing(en -> en.getKey() == null ? "" : en.getKey())); - + for (Map.Entry> e : buyerEntries) { String buyer = e.getKey() != null ? e.getKey() : "未提供"; List orders = e.getValue(); - + Map byModel = orders.stream() .collect(Collectors.groupingBy(JDOrder::getModelNumber, Collectors.counting())); - + int totalCount = 0; StringBuilder summary = new StringBuilder(); List> modelEntries = new ArrayList<>(byModel.entrySet()); modelEntries.sort(Comparator.comparing(en -> en.getKey() == null ? "" : en.getKey())); - + // 记录该下单人每个型号的最高单价订单 Map buyerMaxPriceOrders = new HashMap<>(); Map buyerModelCounts = new HashMap<>(); - + for (Map.Entry em : modelEntries) { int c = em.getValue().intValue(); totalCount += c; String model = em.getKey() != null ? em.getKey() : "未知"; - + summary.append("型号:").append(model).append(" 数量:").append(c).append("\n"); - + buyerModelCounts.put(model, c); - + // 找到该型号价格最高的订单 for (JDOrder order : orders) { if (model.equals(order.getModelNumber())) { JDOrder currentMax = buyerMaxPriceOrders.get(model); if (currentMax == null || (order.getPaymentAmount() != null && - (currentMax.getPaymentAmount() == null || + (currentMax.getPaymentAmount() == null || order.getPaymentAmount() > currentMax.getPaymentAmount()))) { buyerMaxPriceOrders.put(model, order); } @@ -472,7 +472,7 @@ public class InstructionServiceImpl implements IInstructionService { } } summary.append("总计:").append(totalCount).append("\n详情:"); - + List sorted = orders.stream() .sorted(Comparator.comparing(JDOrder::getRemark, Comparator.nullsFirst(String::compareTo))) .collect(Collectors.toList()); @@ -488,52 +488,52 @@ public class InstructionServiceImpl implements IInstructionService { .append("地址:").append(o.getAddress() != null ? o.getAddress() : "未提供").append("\n") .append("物流链接:\n").append(o.getLogisticsLink() != null ? o.getLogisticsLink() : "无"); } - + // 订单详情 StringBuilder infoSingle = new StringBuilder(); infoSingle.append("下单人:").append(buyer).append("\n").append(summary).append(detail).append("\n"); outputs.add(infoSingle.toString().trim()); - + // 算钱文本(使用佣金支付) if (!buyerModelCounts.isEmpty()) { StringBuilder priceText = new StringBuilder(); priceText.append("下单人:").append(buyer).append("\n"); - + // 先输出型号和数量 List> sortedEntries = buyerModelCounts.entrySet().stream() .sorted(Comparator.comparing(Map.Entry::getKey)) .collect(Collectors.toList()); - + for (Map.Entry entry : sortedEntries) { String model = entry.getKey(); int count = entry.getValue(); priceText.append("型号:").append(model).append(" 数量:").append(count).append("\n"); } - + priceText.append("\n"); - + // 输出金额计算(使用佣金支付字段) double grandTotal = 0.0; StringBuilder calculationLines = new StringBuilder(); List totalPricesList = new ArrayList<>(); - + for (Map.Entry entry : sortedEntries) { String model = entry.getKey(); int count = entry.getValue(); - + // 获取该型号最高价格的订单 JDOrder maxPriceOrder = buyerMaxPriceOrders.get(model); double singlePayment = 0.0; double singleRebate = 0.0; - + if (maxPriceOrder != null) { singlePayment = maxPriceOrder.getPaymentAmount() != null ? maxPriceOrder.getPaymentAmount() : 0.0; singleRebate = maxPriceOrder.getRebateAmount() != null ? maxPriceOrder.getRebateAmount() : 0.0; } - + singlePayment = Math.round(singlePayment * 100.0) / 100.0; singleRebate = Math.round(singleRebate * 100.0) / 100.0; - + // 从Redis获取佣金(支付)- 支付给下单人的佣金 double commissionPay = 0.0; try { @@ -549,17 +549,17 @@ public class InstructionServiceImpl implements IInstructionService { } catch (Exception ex) { // 如果获取失败,佣金为0 } - + // 计算净价(单台价格 - 返现 + 佣金支付) double netPrice = singlePayment - singleRebate + commissionPay; netPrice = Math.round(netPrice * 100.0) / 100.0; - + // 计算总价 double totalPrice = netPrice * count; totalPrice = Math.round(totalPrice * 100.0) / 100.0; totalPricesList.add(totalPrice); grandTotal += totalPrice; - + // 格式:单台价格-返现+佣金=净价×数量=总价 if (count == 1) { calculationLines.append(String.format("%.2f-%.2f+%.2f=%.2f\n", @@ -569,9 +569,9 @@ public class InstructionServiceImpl implements IInstructionService { singlePayment, singleRebate, commissionPay, netPrice, count, totalPrice)); } } - + priceText.append(calculationLines); - + // 输出汇总 if (sortedEntries.size() > 1) { priceText.append("\n"); @@ -584,7 +584,7 @@ public class InstructionServiceImpl implements IInstructionService { grandTotal = Math.round(grandTotal * 100.0) / 100.0; priceText.append(String.format("=%.2f", grandTotal)); } - + outputs.add(priceText.toString().trim()); } } @@ -606,12 +606,12 @@ public class InstructionServiceImpl implements IInstructionService { static { /* -13103996539 -15514755615 -17746927935 -13140433517 -17746921532 -15514786055 +13103996539 +15514755615 +17746927935 +13140433517 +17746921532 +15514786055 */ phoneWithTF.add("13103996539"); phoneWithTF.add("15514755615"); @@ -660,12 +660,12 @@ private String handleTF(String input) { // if ("13243039070".equals(phone) || "17530176250".equals(phone)) { // String today = new java.text.SimpleDateFormat("yyyy-MM-dd").format(new Date()); // String usedPhonesKey = "phone_used_today:" + today; - + // if (stringRedisTemplate != null) { // try { // // 获取今天已经使用过的号码集合 // Set usedPhonesToday = stringRedisTemplate.opsForSet().members(usedPhonesKey); - + // // 找出今天还没使用过的号码 // List availablePhones = new ArrayList<>(); // for (String p : phoneWithTF) { @@ -673,7 +673,7 @@ private String handleTF(String input) { // availablePhones.add(p); // } // } - + // if (!availablePhones.isEmpty()) { // // 随机选择一个今天还没用过的号码 // phone = availablePhones.get(new Random().nextInt(availablePhones.size())); @@ -684,7 +684,7 @@ private String handleTF(String input) { // // phoneWithTF 中的号码今天都用完了,使用备用号码(轮换使用) // String backupUsedKey = "backup_phone_used_today:" + today; // Set backupUsedToday = stringRedisTemplate.opsForSet().members(backupUsedKey); - + // // 检查两个备用号码哪个还没用过 // if (backupUsedToday == null || !backupUsedToday.contains("15639125541")) { // phone = "15639125541"; @@ -700,7 +700,7 @@ private String handleTF(String input) { // phone = "15639125541"; // } // } - + // // 记录今天使用的备用号码 // stringRedisTemplate.opsForSet().add(backupUsedKey, phone); // stringRedisTemplate.expire(backupUsedKey, 1, TimeUnit.DAYS); @@ -1026,7 +1026,7 @@ private String handleTF(String input) { private String generateOrderText(String shengInput) { return generateOrderText(shengInput, null); } - + private String generateOrderText(String shengInput, String providedThirdPartyOrderNo) { String[] split = shengInput.split("\n"); // 第一行可能是 生 或 生{备注} @@ -1117,7 +1117,7 @@ private String handleTF(String input) { thirdPartyOrderNo = extractOrderNumber(fenxiao); } String thirdPartyOrderNoValue = (thirdPartyOrderNo != null && !thirdPartyOrderNo.isEmpty()) ? thirdPartyOrderNo : ""; - + for (int i = 0; i < total; i++) { int currentCount = startCount + i; String orderId = today + String.format("%03d", currentCount); @@ -1236,7 +1236,7 @@ private String handleTF(String input) { if (existingByOrderId != null) { // 如果是更新同一条记录(remark相同),则不提示重复 if (!order.getRemark().equals(existingByOrderId.getRemark())) { - String warn = "[炸弹] [炸弹] [炸弹] 录单警告!!! \n订单号重复!\n" + + String warn = "[炸弹] [炸弹] [炸弹] 录单警告!!! \n订单号重复!\n" + "订单号:" + order.getOrderId() + "\n" + "已存在的订单:" + existingByOrderId.getRemark() + "\n" + "下单时间:" + new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(existingByOrderId.getOrderTime()); @@ -1251,7 +1251,7 @@ private String handleTF(String input) { if (existingByLogistics != null) { // 如果是更新同一条记录(remark相同),则不提示重复 if (!order.getRemark().equals(existingByLogistics.getRemark())) { - String warn = "[炸弹] [炸弹] [炸弹] 录单警告!!! \n物流链接重复!\n" + + String warn = "[炸弹] [炸弹] [炸弹] 录单警告!!! \n物流链接重复!\n" + "物流链接:" + order.getLogisticsLink() + "\n" + "已存在的订单:" + existingByLogistics.getRemark() + "\n" + "下单时间:" + new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(existingByLogistics.getOrderTime()); @@ -1315,7 +1315,7 @@ private String handleTF(String input) { try { if (tencentDocDelayedPushService != null) { tencentDocDelayedPushService.triggerDelayedPush(); - System.out.println("✓ H-TF订单已触发延迟推送 - 单号: " + order.getRemark() + + System.out.println("✓ H-TF订单已触发延迟推送 - 单号: " + order.getRemark() + ", 第三方单号: " + order.getThirdPartyOrderNo()); } } catch (Exception e) { @@ -1339,10 +1339,10 @@ private String handleTF(String input) { */ private String formatOrderForm(JDOrder order, String originalInput) { StringBuilder sb = new StringBuilder(); - + // 从原始输入中提取完整的物流链接 String originalLogisticsLink = extractOriginalLogisticsLink(originalInput); - + sb.append("单:\n"); sb.append(order.getRemark() != null ? order.getRemark() : "").append("\n"); sb.append("备注:").append(order.getStatus() != null ? order.getStatus() : "").append("\n"); @@ -1367,10 +1367,10 @@ private String handleTF(String input) { sb.append(order.getBuyer() != null ? order.getBuyer() : "").append("\n"); sb.append("京粉实际价格:\n"); sb.append(order.getJingfenActualPrice() != null ? order.getJingfenActualPrice().toString() : "").append("\n"); - + return sb.toString(); } - + /** * 从原始输入中提取完整的物流链接文本(不做清理) */ @@ -1393,15 +1393,15 @@ private String handleTF(String input) { private JDOrder parseOrderFromText(String input) { // 不要替换所有空格,保留换行符用于正确解析 Map fields = new HashMap<>(); - + // 先尝试新格式(带第三方单号和京粉实际价格) extractField(input, fields, "单:", "备注:"); extractField(input, fields, "备注:", "分销标记:"); - + // 检查是否有"第三方单号:"字段,如果没有则是旧格式 boolean hasThirdPartyOrderNo = input.contains("第三方单号:"); boolean hasJingfenPrice = input.contains("京粉实际价格:"); - + if (hasThirdPartyOrderNo) { // 新格式:有第三方单号字段 extractField(input, fields, "分销标记:", "第三方单号:"); @@ -1410,7 +1410,7 @@ private String handleTF(String input) { // 旧格式:分销标记后面直接是型号 extractField(input, fields, "分销标记:", "型号:"); } - + extractField(input, fields, "型号:", "链接:"); extractField(input, fields, "链接:", "下单付款:"); extractField(input, fields, "下单付款:", "后返金额:"); @@ -1418,7 +1418,7 @@ private String handleTF(String input) { extractField(input, fields, "地址:", "物流链接:"); extractField(input, fields, "物流链接:", "订单号:"); extractField(input, fields, "订单号:", "下单人:"); - + // 京粉实际价格不从表单解析,而是从数据库查询获取 // 如果表单中有这个字段,也提取出来(但不会使用,仅用于兼容) if (hasJingfenPrice) { @@ -1427,7 +1427,7 @@ private String handleTF(String input) { } else { extractField(input, fields, "下单人:", ""); } - + // 使用正则提取下单人(兼容新旧格式) String nextField = hasJingfenPrice ? "京粉实际价格:" : "单:"; java.util.regex.Pattern buyerPattern = java.util.regex.Pattern.compile("下单人:\\s*(.*?)\\s*(?=" + nextField + "|\\Z)", java.util.regex.Pattern.DOTALL); @@ -1443,7 +1443,7 @@ private String handleTF(String input) { JDOrder order = new JDOrder(); order.setRemark(fields.getOrDefault("单", null)); - + // 处理分销标记:如果提取的内容包含换行符,说明可能提取过度了,只取第一行 String distributionMark = fields.getOrDefault("分销标记", null); if (distributionMark != null && distributionMark.contains("\n")) { @@ -1464,7 +1464,7 @@ private String handleTF(String input) { distributionMark = "H-TF"; } order.setDistributionMark(distributionMark); - + // 优先从字段中获取第三方单号,如果没有则从分销标记中提取 String thirdPartyOrderNo = fields.getOrDefault("第三方单号", null); if (thirdPartyOrderNo != null) { @@ -1681,7 +1681,7 @@ private String handleTF(String input) { if (v == null) return ""; // 如果长度大于4,进行截取 - if (v.length() > 4) { + if (v.length() > 5) { // 查找第一个左括号的位置 int leftParenIndex = v.indexOf('('); if (leftParenIndex > 0) { @@ -1730,7 +1730,7 @@ private String handleTF(String input) { /** * 异步写入订单到腾讯文档 * 当订单的分销标识是 H-TF 时,自动追加到腾讯文档表格 - * + * * @param order 订单对象 */ private void asyncWriteToTencentDoc(JDOrder order) { @@ -1739,7 +1739,7 @@ private String handleTF(String input) { try { // Redis key前缀(用于获取文档配置) final String REDIS_KEY_PREFIX = "tencent:doc:auto:config:"; - + // 1. 从Token服务获取access-token(会自动从Redis读取并刷新) String accessToken = null; try { @@ -1749,12 +1749,12 @@ private String handleTF(String input) { System.err.println(" 提示:请先完成腾讯文档授权,访问 /jarvis/tendoc/authUrl 获取授权URL"); return; } - + // 2. 从Redis获取文档配置(fileId、sheetId 和 startRow) String fileId = redisCache.getCacheObject(REDIS_KEY_PREFIX + "fileId"); String sheetId = redisCache.getCacheObject(REDIS_KEY_PREFIX + "sheetId"); Integer startRow = redisCache.getCacheObject(REDIS_KEY_PREFIX + "startRow"); - + // 如果Redis中没有,则使用配置文件中的默认值 if (fileId == null || fileId.isEmpty()) { fileId = tencentDocConfig.getFileId(); @@ -1765,34 +1765,34 @@ private String handleTF(String input) { if (startRow == null) { startRow = tencentDocConfig.getStartRow(); } - + // 3. 验证配置是否完整 if (fileId == null || fileId.isEmpty() || sheetId == null || sheetId.isEmpty()) { System.err.println("✗ H-TF订单文档配置不完整,跳过自动写入。" + "\n 提示:请通过 POST /jarvis/tencentDoc/config 接口配置文档ID和工作表ID" + - "\n fileId: " + (fileId != null && !fileId.isEmpty() ? fileId : "未配置") + + "\n fileId: " + (fileId != null && !fileId.isEmpty() ? fileId : "未配置") + "\n sheetId: " + (sheetId != null && !sheetId.isEmpty() ? sheetId : "未配置") + "\n startRow: " + (startRow != null ? startRow : "默认3")); return; } - + // 4. 调用腾讯文档服务追加订单数据 com.alibaba.fastjson2.JSONObject result = tencentDocService.appendLogisticsToSheet( accessToken, fileId, sheetId, startRow, order); - + if (result != null) { System.out.println("✓ H-TF订单已自动追加到腾讯文档" + - "\n 单号: " + order.getRemark() + - "\n 第三方单号: " + order.getThirdPartyOrderNo() + + "\n 单号: " + order.getRemark() + + "\n 第三方单号: " + order.getThirdPartyOrderNo() + "\n 文档: " + fileId + "/" + sheetId); } else { - System.err.println("✗ 订单追加到腾讯文档失败 - 单号: " + order.getRemark() + + System.err.println("✗ 订单追加到腾讯文档失败 - 单号: " + order.getRemark() + "\n API返回null,请检查access-token是否有效或文档是否存在"); } } catch (Exception e) { // 写入失败不影响录单结果,仅记录错误日志 - System.err.println("✗ 异步写入腾讯文档失败 - 单号: " + order.getRemark() + - "\n 错误: " + e.getMessage() + + System.err.println("✗ 异步写入腾讯文档失败 - 单号: " + order.getRemark() + + "\n 错误: " + e.getMessage() + "\n 提示:" + "\n 1. 检查腾讯文档授权是否有效" + "\n 2. 检查文档ID和工作表ID是否正确" +