From fb2f225b646f41b23af60ea7177923b51f9b6bd2 Mon Sep 17 00:00:00 2001 From: cst61 Date: Sun, 17 May 2026 09:01:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=BC=E5=87=BA=E5=90=8C=E4=BA=8B=E7=89=B9?= =?UTF-8?q?=E6=9D=83=EF=BC=8C=E5=A2=9E=E5=8A=A0=E5=92=8C=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E8=A1=A8=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rj/controller/LbOrderRowController.java | 6 +- .../controller/LbPurchaseApplyController.java | 15 ++ .../rj/dto/LbOrderRowSyncFromHxrRequest.java | 3 + src/main/java/com/rj/entity/LbOrderRow.java | 21 +- .../com/rj/service/ILbOrderRowService.java | 9 +- .../rj/service/ILbPurchaseApplyService.java | 6 + .../service/impl/LbOrderRowServiceImpl.java | 58 ++++- .../impl/LbPurchaseApplyServiceImpl.java | 238 +++++++++++++----- 8 files changed, 284 insertions(+), 72 deletions(-) diff --git a/src/main/java/com/rj/controller/LbOrderRowController.java b/src/main/java/com/rj/controller/LbOrderRowController.java index 88d6393..330d125 100644 --- a/src/main/java/com/rj/controller/LbOrderRowController.java +++ b/src/main/java/com/rj/controller/LbOrderRowController.java @@ -62,6 +62,7 @@ public class LbOrderRowController { @RequestParam(defaultValue = "1") Integer current, @RequestParam(defaultValue = "10") Integer size, @RequestParam(required = false) String isResell, + @Parameter(description = "数据类型") @RequestParam(required = false) String dataType, @RequestParam(required = false) String orderSn, @RequestParam(required = false) Long buyerId, @RequestParam(required = false) Long sellerId, @@ -69,7 +70,7 @@ public class LbOrderRowController { @RequestParam(required = false) Long merchandiseId) { Map result = lbOrderRowService.pageQuery( - current, size, isResell, orderSn, buyerId, sellerId, status, merchandiseId); + current, size, isResell, dataType, orderSn, buyerId, sellerId, status, merchandiseId); Boolean success = (Boolean) result.get("success"); if (success != null && success) { return ResponseEntity.ok(result); @@ -88,7 +89,8 @@ public class LbOrderRowController { request.getBuyTimeStart(), request.getBuyTimeEnd(), request.getTenantId(), - request.getIsResell()); + request.getIsResell(), + request.getDataType()); Boolean success = (Boolean) result.get("success"); if (success != null && success) { return ResponseEntity.ok(result); diff --git a/src/main/java/com/rj/controller/LbPurchaseApplyController.java b/src/main/java/com/rj/controller/LbPurchaseApplyController.java index 49b969c..5cf2464 100644 --- a/src/main/java/com/rj/controller/LbPurchaseApplyController.java +++ b/src/main/java/com/rj/controller/LbPurchaseApplyController.java @@ -155,6 +155,21 @@ public class LbPurchaseApplyController { return ResponseEntity.badRequest().body(result); } + @GetMapping("/exportColleagueVip") + @Operation(summary = "导出同事特权开通提醒Excel", + description = "筛选逻辑与 syncHxrAdminColleagueVip 一致:仅导出申请人当日有进货记录、" + + "且具备同事手机号的可开通记录,列含昵称、手机号、特权开通日期、开通日期") + public void exportColleagueVip( + @Parameter(description = "申请开始日期(yyyy-MM-dd)", required = true) + @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate applyDateStart, + @Parameter(description = "申请结束日期(yyyy-MM-dd)", required = true) + @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate applyDateEnd, + @Parameter(description = "租户ID", required = true) + @RequestParam String tenantId, + HttpServletResponse response) { + lbPurchaseApplyService.exportColleagueVipExcel(applyDateStart, applyDateEnd, tenantId, response); + } + @GetMapping("/export") @Operation(summary = "导出进货申请Excel", description = "按租户ID与申请日期范围导出,按申请日分组并合并首列") public void export( diff --git a/src/main/java/com/rj/dto/LbOrderRowSyncFromHxrRequest.java b/src/main/java/com/rj/dto/LbOrderRowSyncFromHxrRequest.java index 69a4847..b669de5 100644 --- a/src/main/java/com/rj/dto/LbOrderRowSyncFromHxrRequest.java +++ b/src/main/java/com/rj/dto/LbOrderRowSyncFromHxrRequest.java @@ -21,4 +21,7 @@ public class LbOrderRowSyncFromHxrRequest { @Schema(description = "转卖状态筛选(对应后台 is_resell)", requiredMode = Schema.RequiredMode.REQUIRED) private Integer isResell; + + @Schema(description = "写入 lb_order_row.data_type;为空时默认使用 isResell 的字符串值") + private String dataType; } diff --git a/src/main/java/com/rj/entity/LbOrderRow.java b/src/main/java/com/rj/entity/LbOrderRow.java index 84f9b0f..b9e8655 100644 --- a/src/main/java/com/rj/entity/LbOrderRow.java +++ b/src/main/java/com/rj/entity/LbOrderRow.java @@ -9,6 +9,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import java.io.Serializable; +import java.math.BigDecimal; @Data @EqualsAndHashCode(callSuper = false) @@ -31,6 +32,10 @@ public class LbOrderRow implements Serializable { @Schema(description = "租户ID") private String tenantId; + @TableField("data_type") + @Schema(description = "数据类型") + private String dataType; + @TableField("seller_id") @Schema(description = "卖家 id") private Long sellerId; @@ -44,8 +49,8 @@ public class LbOrderRow implements Serializable { private String orderSn; @TableField("total_money") - @Schema(description = "总金额(接口为字符串)") - private String totalMoney; + @Schema(description = "总金额") + private BigDecimal totalMoney; @TableField("pay_time") @Schema(description = "支付时间(原样字符串)") @@ -110,4 +115,16 @@ public class LbOrderRow implements Serializable { @TableField("updated_at") @Schema(description = "更新时间") private String updatedAt; + + @TableField("today_total_money_sum") + @Schema(description = "汇总后的总金额") + private BigDecimal todayTotalMoneySum; + + @TableField("today_unresell_count") + @Schema(description = "当天没有寄卖的单数总和") + private Integer todayUnresellCount; + + @TableField("today_order_count") + @Schema(description = "当天交易单数") + private Integer todayOrderCount; } diff --git a/src/main/java/com/rj/service/ILbOrderRowService.java b/src/main/java/com/rj/service/ILbOrderRowService.java index c1a23c0..5437ca0 100644 --- a/src/main/java/com/rj/service/ILbOrderRowService.java +++ b/src/main/java/com/rj/service/ILbOrderRowService.java @@ -16,7 +16,8 @@ public interface ILbOrderRowService extends IService { Map pageQuery( Integer current, Integer size, - String tenantId, + String isResell, + String dataType, String orderSn, Long buyerId, Long sellerId, @@ -27,5 +28,9 @@ public interface ILbOrderRowService extends IService { * 按条件调用 hxrd 后台订单列表接口拉取数据,并 upsert 到 {@code lb_order_row}。 */ Map syncFromHxrAdmin( - String buyTimeStart, String buyTimeEnd, String tenantId, Integer isResell); + String buyTimeStart, + String buyTimeEnd, + String tenantId, + Integer isResell, + String dataType); } diff --git a/src/main/java/com/rj/service/ILbPurchaseApplyService.java b/src/main/java/com/rj/service/ILbPurchaseApplyService.java index 10b3a53..bd2defd 100644 --- a/src/main/java/com/rj/service/ILbPurchaseApplyService.java +++ b/src/main/java/com/rj/service/ILbPurchaseApplyService.java @@ -54,4 +54,10 @@ public interface ILbPurchaseApplyService extends IService { * 按申请日期区间与租户导出进货申请 Excel(按申请日分组,首列合并单元格)。 */ void exportExcel(LocalDate applyDateStart, LocalDate applyDateEnd, String tenantId, HttpServletResponse response); + + /** + * 导出与 {@link #syncHxrAdminColleagueVipByApplyDateRange} 相同筛选条件下的同事特权开通提醒 Excel。 + */ + void exportColleagueVipExcel( + LocalDate applyDateStart, LocalDate applyDateEnd, String tenantId, HttpServletResponse response); } diff --git a/src/main/java/com/rj/service/impl/LbOrderRowServiceImpl.java b/src/main/java/com/rj/service/impl/LbOrderRowServiceImpl.java index 5d194a6..4bfb1f6 100644 --- a/src/main/java/com/rj/service/impl/LbOrderRowServiceImpl.java +++ b/src/main/java/com/rj/service/impl/LbOrderRowServiceImpl.java @@ -13,6 +13,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -139,6 +140,7 @@ public class LbOrderRowServiceImpl extends ServiceImpl syncFromHxrAdmin( - String buyTimeStart, String buyTimeEnd, String tenantId, Integer isResell) { + String buyTimeStart, String buyTimeEnd, String tenantId, Integer isResell, String dataType) { Map result = new HashMap<>(); try { if (tenantId == null || tenantId.trim().isEmpty()) { @@ -223,12 +228,18 @@ public class LbOrderRowServiceImpl extends ServiceImpl entities = new ArrayList<>(rows.size()); for (HxrOrderRow row : rows) { - entities.add(toLbOrderRow(row, tid)); + entities.add(toLbOrderRow( + row, tid, rowDataType, todayTotalMoneySum, todayOrderCount, todayUnresellCount)); } boolean ok = this.saveOrUpdateBatch(entities); +// boolean ok = this.saveBatch(entities); result.put("success", ok); result.put("message", ok ? "同步完成" : "批量保存失败"); result.put("synced", ok ? entities.size() : 0); @@ -243,15 +254,54 @@ public class LbOrderRowServiceImpl extends ServiceImpl rows) { + int n = 0; + for (HxrOrderRow row : rows) { + if (row.isResell() == 0) { + n++; + } + } + return n; + } + + private static BigDecimal parseMoney(String money) { + if (money == null || money.trim().isEmpty()) { + return null; + } + try { + return new BigDecimal(money.trim()); + } catch (NumberFormatException e) { + return null; + } + } + + private static LbOrderRow toLbOrderRow( + HxrOrderRow row, + String tenantId, + String dataType, + BigDecimal todayTotalMoneySum, + Integer todayOrderCount, + Integer todayUnresellCount) { LbOrderRow e = new LbOrderRow(); e.setId(row.id()); + e.setDataType("detail_data"); e.setOldId(row.oldId()); e.setTenantId(tenantId); + e.setDataType(dataType); + e.setTodayTotalMoneySum(todayTotalMoneySum); + e.setTodayOrderCount(todayOrderCount); + e.setTodayUnresellCount(todayUnresellCount); e.setSellerId(row.sellerId()); e.setBuyerId(row.buyerId()); e.setOrderSn(row.orderSn()); - e.setTotalMoney(row.totalMoney()); + e.setTotalMoney(parseMoney(row.totalMoney())); e.setPayTime(row.payTime()); e.setPayImg(row.payImg()); e.setStatus(row.status()); diff --git a/src/main/java/com/rj/service/impl/LbPurchaseApplyServiceImpl.java b/src/main/java/com/rj/service/impl/LbPurchaseApplyServiceImpl.java index 4b0cea0..2d6439b 100644 --- a/src/main/java/com/rj/service/impl/LbPurchaseApplyServiceImpl.java +++ b/src/main/java/com/rj/service/impl/LbPurchaseApplyServiceImpl.java @@ -79,6 +79,11 @@ public class LbPurchaseApplyServiceImpl /** 试用期包含的工作日天数(周一至周五),周六日不计入。 */ private static final int TRIAL_WEEKDAY_COUNT = 3; + /** 同事特权开通提醒中「特权开通日期」包含的工作日天数(与 syncHxrAdminColleagueVip 的 viptime 规则一致)。 */ + private static final int COLLEAGUE_PRIVILEGE_WEEKDAY_COUNT = 2; + + private static final String COLLEAGUE_VIP_GROUP_LABEL = "开通特权提醒"; + private static final String PARSE_SYSTEM_PROMPT = """ 你是信息抽取助手。用户会提供一段「进货申请」相关的自然语言订货信息。 请只输出一个 JSON 对象,不要 markdown 代码块,不要解释性文字。 @@ -651,28 +656,14 @@ public class LbPurchaseApplyServiceImpl LocalDate applyDateStart, LocalDate applyDateEnd, String tenantId) { Map result = new HashMap<>(); try { - if (applyDateStart == null || applyDateEnd == null) { + ColleagueVipContext ctx = resolveColleagueVipContext(applyDateStart, applyDateEnd, tenantId); + if (ctx.validationError() != null) { result.put("success", false); - result.put("message", "申请开始日期与结束日期不能为空"); + result.put("message", ctx.validationError()); return result; } - if (applyDateEnd.isBefore(applyDateStart)) { - result.put("success", false); - result.put("message", "结束日期不能早于开始日期"); - return result; - } - if (tenantId == null || tenantId.trim().isEmpty()) { - result.put("success", false); - result.put("message", "租户id不能为空"); - return result; - } - - LambdaQueryWrapper q = new LambdaQueryWrapper<>(); - q.eq(LbPurchaseApply::getTenantId, tenantId.trim()) - .ge(LbPurchaseApply::getApplyDate, applyDateStart) - .le(LbPurchaseApply::getApplyDate, applyDateEnd); - List rows = this.list(q); - if (rows == null || rows.isEmpty()) { + List rows = ctx.rows(); + if (rows.isEmpty()) { result.put("success", true); result.put("message", "该条件下无申请记录,未调用第三方"); result.put("totalRecords", 0); @@ -683,26 +674,7 @@ public class LbPurchaseApplyServiceImpl return result; } - Set ApplyUserNames = new HashSet<>(); - for (LbPurchaseApply row : rows) { - if (row.getColleagueName() != null && !row.getColleagueName().trim().isEmpty()) { - ApplyUserNames.add(row.getApplyUser().trim()); - } - } - Set tradeMatchedNicknames = new HashSet<>(); - if (!ApplyUserNames.isEmpty()) { - LambdaQueryWrapper tradeQuery = new LambdaQueryWrapper<>(); - tradeQuery.eq(LbDailyUserTrade::getTenantId, tenantId.trim()) - .in(LbDailyUserTrade::getNickname, ApplyUserNames) - .select(LbDailyUserTrade::getNickname); - List matchedTrades = lbDailyUserTradeMapper.selectList(tradeQuery); - for (LbDailyUserTrade trade : matchedTrades) { - if (trade.getNickname() != null && !trade.getNickname().trim().isEmpty()) { - tradeMatchedNicknames.add(trade.getNickname().trim()); - } - } - } - + Set tradeMatchedNicknames = ctx.tradeMatchedNicknames(); List> details = new ArrayList<>(); int ok = 0; int fail = 0; @@ -714,30 +686,10 @@ public class LbPurchaseApplyServiceImpl one.put("applyUserName", applyUserName); String phone = row.getColleaguePhone() != null ? row.getColleaguePhone().trim() : ""; one.put("colleaguePhone", phone); - if (applyUserName.isEmpty()) { + String skipReason = colleagueVipSkipReason(row, tradeMatchedNicknames); + if (skipReason != null) { one.put("success", false); - one.put("message", "colleague_name 为空,跳过"); - fail++; - details.add(one); - continue; - } - if (!tradeMatchedNicknames.contains(applyUserName)) { - one.put("success", false); - one.put("message", "申请人姓名在 lb_daily_user_trade 中未找到 ,说明今天没进货 ,推荐人不能享受特权,跳过开通特权"); - fail++; - details.add(one); - continue; - } - if (phone.isEmpty()) { - one.put("success", false); - one.put("message", "colleague_phone 为空,跳过"); - fail++; - details.add(one); - continue; - } - if (row.getApplyDate() == null) { - one.put("success", false); - one.put("message", "applyDate 为空,跳过"); + one.put("message", skipReason); fail++; details.add(one); continue; @@ -803,6 +755,168 @@ public class LbPurchaseApplyServiceImpl } } + @Override + public void exportColleagueVipExcel( + LocalDate applyDateStart, LocalDate applyDateEnd, String tenantId, HttpServletResponse response) { + try { + ColleagueVipContext ctx = resolveColleagueVipContext(applyDateStart, applyDateEnd, tenantId); + if (ctx.validationError() != null) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, ctx.validationError()); + return; + } + + List exportRows = new ArrayList<>(); + for (LbPurchaseApply row : ctx.rows()) { + if (colleagueVipSkipReason(row, ctx.tradeMatchedNicknames()) == null) { + exportRows.add(row); + } + } + + try (Workbook workbook = new XSSFWorkbook()) { + Sheet sheet = workbook.createSheet("开通特权提醒"); + + CellStyle headerStyle = createHeaderStyle(workbook); + CellStyle groupStyle = createGroupStyle(workbook); + CellStyle dataStyle = createDataStyle(workbook); + + String[] headers = {"", "昵称", "手机号", "特权开通日期", "开通日期"}; + Row headerRow = sheet.createRow(0); + headerRow.setHeightInPoints(22); + for (int i = 0; i < headers.length; i++) { + createExportCell(headerRow, i, headers[i], headerStyle); + } + + int rowIndex = 1; + if (!exportRows.isEmpty()) { + int groupStartRow = rowIndex; + for (LbPurchaseApply item : exportRows) { + Row row = sheet.createRow(rowIndex); + row.setHeightInPoints(20); + createExportCell(row, 1, formatColleagueVipNickname(item), dataStyle); + createExportCell(row, 2, nullToEmpty(item.getColleaguePhone()), dataStyle); + createExportCell(row, 3, formatColleaguePrivilegeRange(item.getApplyDate()), dataStyle); + createExportCell(row, 4, formatActivationDate(item.getApplyDate()), dataStyle); + rowIndex++; + } + int groupEndRow = rowIndex - 1; + Row firstRow = sheet.getRow(groupStartRow); + createExportCell(firstRow, 0, COLLEAGUE_VIP_GROUP_LABEL, groupStyle); + if (groupEndRow > groupStartRow) { + sheet.addMergedRegion(new CellRangeAddress(groupStartRow, groupEndRow, 0, 0)); + } + } + + adjustExportColumnWidths(sheet, headers); + + String filename = "开通特权提醒_" + LocalDateTime.now().format(EXPORT_FILENAME_TS) + ".xlsx"; + String encoded = URLEncoder.encode(filename, StandardCharsets.UTF_8).replaceAll("\\+", "%20"); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded); + + try (ServletOutputStream os = response.getOutputStream()) { + workbook.write(os); + os.flush(); + } + } + } catch (Exception e) { + log.error("同事特权开通提醒导出异常", e); + try { + response.reset(); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + response.setContentType("text/plain;charset=UTF-8"); + response.getWriter().write("导出异常:" + e.getMessage()); + } catch (Exception ignored) { + // ignore + } + } + } + + private record ColleagueVipContext( + String validationError, + List rows, + Set tradeMatchedNicknames) {} + + private ColleagueVipContext resolveColleagueVipContext( + LocalDate applyDateStart, LocalDate applyDateEnd, String tenantId) { + if (applyDateStart == null || applyDateEnd == null) { + return new ColleagueVipContext("申请开始日期与结束日期不能为空", List.of(), Set.of()); + } + if (applyDateEnd.isBefore(applyDateStart)) { + return new ColleagueVipContext("结束日期不能早于开始日期", List.of(), Set.of()); + } + if (tenantId == null || tenantId.trim().isEmpty()) { + return new ColleagueVipContext("租户id不能为空", List.of(), Set.of()); + } + + LambdaQueryWrapper q = new LambdaQueryWrapper<>(); + q.eq(LbPurchaseApply::getTenantId, tenantId.trim()) + .ge(LbPurchaseApply::getApplyDate, applyDateStart) + .le(LbPurchaseApply::getApplyDate, applyDateEnd); + List rows = this.list(q); + if (rows == null) { + rows = List.of(); + } + + Set applyUserNames = new HashSet<>(); + for (LbPurchaseApply row : rows) { + if (row.getColleagueName() != null && !row.getColleagueName().trim().isEmpty() + && row.getApplyUser() != null && !row.getApplyUser().trim().isEmpty()) { + applyUserNames.add(row.getApplyUser().trim()); + } + } + Set tradeMatchedNicknames = new HashSet<>(); + if (!applyUserNames.isEmpty()) { + LambdaQueryWrapper tradeQuery = new LambdaQueryWrapper<>(); + tradeQuery.eq(LbDailyUserTrade::getTenantId, tenantId.trim()) + .in(LbDailyUserTrade::getNickname, applyUserNames) + .select(LbDailyUserTrade::getNickname); + List matchedTrades = lbDailyUserTradeMapper.selectList(tradeQuery); + for (LbDailyUserTrade trade : matchedTrades) { + if (trade.getNickname() != null && !trade.getNickname().trim().isEmpty()) { + tradeMatchedNicknames.add(trade.getNickname().trim()); + } + } + } + return new ColleagueVipContext(null, rows, tradeMatchedNicknames); + } + + private static String colleagueVipSkipReason(LbPurchaseApply row, Set tradeMatchedNicknames) { + String applyUserName = row.getApplyUser() != null ? row.getApplyUser().trim() : ""; + if (applyUserName.isEmpty()) { + return "colleague_name 为空,跳过"; + } + if (!tradeMatchedNicknames.contains(applyUserName)) { + return "申请人姓名在 lb_daily_user_trade 中未找到 ,说明今天没进货 ,推荐人不能享受特权,跳过开通特权"; + } + String phone = row.getColleaguePhone() != null ? row.getColleaguePhone().trim() : ""; + if (phone.isEmpty()) { + return "colleague_phone 为空,跳过"; + } + if (row.getApplyDate() == null) { + return "applyDate 为空,跳过"; + } + return null; + } + + private static String formatColleagueVipNickname(LbPurchaseApply item) { + String colleagueName = item.getColleagueName(); + if (colleagueName != null && !colleagueName.trim().isEmpty()) { + return colleagueName.trim(); + } + return item.getApplyUser() == null ? "" : item.getApplyUser().trim(); + } + + private static String formatColleaguePrivilegeRange(LocalDate applyDate) { + if (applyDate == null) { + return ""; + } + LocalDate start = firstWeekdayOnOrAfter(applyDate); + LocalDate endDate = endOfInclusiveWeekdaySpan(start, COLLEAGUE_PRIVILEGE_WEEKDAY_COUNT, null); + return start.format(PRIVILEGE_RANGE_FMT) + "-" + endDate.format(PRIVILEGE_RANGE_FMT); + } + @Override public void exportExcel(LocalDate applyDateStart, LocalDate applyDateEnd, String tenantId, HttpServletResponse response) { try {