package com.rj.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.rj.dto.hxr.HxrAdminOrderApiContext; import com.rj.dto.hxr.HxrOrderRow; import com.rj.dto.hxr.HxrOrderSelectResponse; import com.rj.entity.LbDepartmentUser; import com.rj.entity.LbOrderRow; import com.rj.entity.LbUser; import com.rj.mapper.LbOrderRowMapper; import com.rj.service.*; import com.rj.tenant.TenantContextHolder; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @Slf4j @Service public class LbOrderRowServiceImpl extends ServiceImpl implements ILbOrderRowService { /** 从第三方同步的订单明细默认 data_type */ private static final String SYNC_DATA_TYPE_DETAIL = "detail_data"; private static final String SUM_DATA_TYPE = "sum_data"; private static final int DINGTALK_SELLER_CONFIRM_BATCH_SIZE = 15; private static final String DINGTALK_SELLER_CONFIRM_PREFIX = "提醒,请如下卖家确认,不要耽误 买方寄卖"; private static final long SUM_PLACEHOLDER_USER_ID = 10000L; private static final String SUM_ORDER_SN = "order_sn_10000"; private static final DateTimeFormatter DAY_TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private static final DateTimeFormatter DAY_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); @Autowired private HxrAdminOrderSelectService hxrAdminOrderSelectService; @Autowired private ILbThirdIntegrationConfigService lbThirdIntegrationConfigService; @Autowired private ILbDepartmentUserService lbDepartmentUserService; @Autowired private DingTalkRobotService dingTalkRobotService; @Override public Map add(LbOrderRow entity) { Map result = new HashMap<>(); try { if (entity.getId() == null) { result.put("success", false); result.put("message", "id(订单 id)不能为空"); return result; } if (entity.getTenantId() == null || entity.getTenantId().trim().isEmpty()) { result.put("success", false); result.put("message", "tenantId不能为空"); return result; } if (entity.getSellerId() == null) { result.put("success", false); result.put("message", "sellerId不能为空"); return result; } if (entity.getBuyerId() == null) { result.put("success", false); result.put("message", "buyerId不能为空"); return result; } if (entity.getOrderSn() == null || entity.getOrderSn().trim().isEmpty()) { result.put("success", false); result.put("message", "orderSn不能为空"); return result; } if (entity.getMerchandiseId() == null) { result.put("success", false); result.put("message", "merchandiseId不能为空"); return result; } entity.setTenantId(entity.getTenantId().trim()); if (getByTenantAndId(entity.getTenantId(), entity.getId()) != null) { result.put("success", false); result.put("message", "该租户下订单 id 已存在"); return result; } entity.setOrderSn(entity.getOrderSn().trim()); if (entity.getStatus() == null) { entity.setStatus(0); } if (entity.getIsResell() == null) { entity.setIsResell(0); } if (entity.getIsShow() == null) { entity.setIsShow(0); } boolean ok = this.save(entity); result.put("success", ok); result.put("message", ok ? "新增成功" : "新增失败"); if (ok) { result.put("data", getByTenantAndId(entity.getTenantId(), entity.getId())); } return result; } catch (Exception e) { result.put("success", false); result.put("message", "新增异常:" + e.getMessage()); return result; } } @Override public Map update(LbOrderRow entity) { Map result = new HashMap<>(); try { if (entity.getId() == null) { result.put("success", false); result.put("message", "id不能为空"); return result; } String tenantId = resolveTenantId(entity.getTenantId()); if (tenantId == null) { result.put("success", false); result.put("message", "tenantId不能为空"); return result; } entity.setTenantId(tenantId); if (getByTenantAndId(tenantId, entity.getId()) == null) { result.put("success", false); result.put("message", "记录不存在"); return result; } boolean ok = this.update( entity, new LambdaQueryWrapper() .eq(LbOrderRow::getTenantId, tenantId) .eq(LbOrderRow::getId, entity.getId())); result.put("success", ok); result.put("message", ok ? "编辑成功" : "编辑失败"); if (ok) { result.put("data", getByTenantAndId(tenantId, entity.getId())); } return result; } catch (Exception e) { result.put("success", false); result.put("message", "编辑异常:" + e.getMessage()); return result; } } @Override public Map deleteById(Long id, String tenantId) { Map result = new HashMap<>(); try { if (id == null) { result.put("success", false); result.put("message", "id不能为空"); return result; } String tid = resolveTenantId(tenantId); if (tid == null) { result.put("success", false); result.put("message", "tenantId不能为空"); return result; } if (getByTenantAndId(tid, id) == null) { result.put("success", false); result.put("message", "记录不存在"); return result; } boolean ok = this.remove( new LambdaQueryWrapper() .eq(LbOrderRow::getTenantId, tid) .eq(LbOrderRow::getId, id)); result.put("success", ok); result.put("message", ok ? "删除成功" : "删除失败"); return result; } catch (Exception e) { result.put("success", false); result.put("message", "删除异常:" + e.getMessage()); return result; } } @Override public Map pageQuery( Integer current, Integer size, String isResell, String dataType, String orderSn, String phone, Long buyerId, Long sellerId, Integer status, Long merchandiseId) { Map result = new HashMap<>(); try { if (current == null || current < 1) { current = 1; } if (size == null || size < 1) { size = 10; } LambdaQueryWrapper w = new LambdaQueryWrapper<>(); if (isResell != null && !isResell.trim().isEmpty()) { w.eq(LbOrderRow::getIsResell, isResell.trim()); } if (dataType != null && !dataType.trim().isEmpty()) { w.eq(LbOrderRow::getDataType, dataType.trim()); } if (orderSn != null && !orderSn.trim().isEmpty()) { w.like(LbOrderRow::getOrderSn, orderSn.trim()); } if (phone != null && !phone.trim().isEmpty()) { w.like(LbOrderRow::getPhone, phone.trim()); } if (buyerId != null) { w.eq(LbOrderRow::getBuyerId, buyerId); } if (sellerId != null) { w.eq(LbOrderRow::getSellerId, sellerId); } if (status != null) { w.eq(LbOrderRow::getStatus, status); } if (merchandiseId != null) { w.eq(LbOrderRow::getMerchandiseId, merchandiseId); } w.orderByDesc(LbOrderRow::getId); Page page = this.page(new Page<>(current, size), w); result.put("success", true); result.put("message", "查询成功"); result.put("data", page.getRecords()); result.put("total", page.getTotal()); result.put("current", page.getCurrent()); result.put("size", page.getSize()); result.put("pages", page.getPages()); return result; } catch (Exception e) { result.put("success", false); result.put("message", "查询异常:" + e.getMessage()); return result; } } @Override @Transactional(rollbackFor = Exception.class) public Map syncFromHxrAdmin( String buyTimeStart, String buyTimeEnd, String tenantId, Integer isResell) { return syncFromHxrAdmin(buyTimeStart, buyTimeEnd, tenantId, isResell, null); } @Override @Transactional(rollbackFor = Exception.class) public Map syncFromHxrAdmin( String buyTimeStart, String buyTimeEnd, String tenantId, Integer isResell, Integer hxrOrderStatus) { Map result = new HashMap<>(); try { if (tenantId == null || tenantId.trim().isEmpty()) { result.put("success", false); result.put("message", "tenantId不能为空"); return result; } String tid = tenantId.trim(); Optional apiContextOpt = lbThirdIntegrationConfigService.resolveAdminOrderApiContext(tid); if (apiContextOpt.isEmpty()) { result.put("success", false); result.put("message", "未找到该租户的第三方集成配置,或配置未启用、URL/凭证不完整(请检查 lb_third_integration_config)"); result.put("synced", 0); return result; } HxrAdminOrderApiContext apiContext = apiContextOpt.get(); int orderPageLimit = apiContext.pageLimit() > 0 ? apiContext.pageLimit() : 90; String previousTenantId = TenantContextHolder.getTenantId(); TenantContextHolder.setTenantId(tid); try { HxrOrderSelectResponse firstBody = null; int page = 1; int totalSynced = 0; while (true) { Optional opt = fetchHxrOrderPage(buyTimeStart, buyTimeEnd, isResell, hxrOrderStatus, page, apiContext); if (opt.isEmpty()) { if (page == 1) { result.put("success", false); result.put("message", "未拉取到订单(请检查 lb_third_integration_config 中 cookie/phpsid、URL 或网络)"); result.put("synced", 0); return result; } result.put("success", false); result.put("message", "第 " + page + " 页拉取失败,已成功同步前 " + (page - 1) + " 页共 " + totalSynced + " 条"); result.put("synced", totalSynced); if (firstBody != null) { result.put("remoteCount", firstBody.count()); result.put("allMoney", firstBody.allMoney()); } result.put("pages", page - 1); return result; } HxrOrderSelectResponse body = opt.get(); if (firstBody == null) { firstBody = body; } List rows = body.data(); if (rows == null || rows.isEmpty()) { break; } List entities = new ArrayList<>(rows.size()); for (HxrOrderRow row : rows) { entities.add(toLbOrderRow(row, tid)); } fillSellerInfoFromDepartmentUsers(entities, tid); fillBuyerInfoFromLBtUsers(entities, tid); int upserted = upsertBatch(entities); if (upserted < 0) { result.put("success", false); result.put("message", "第 " + page + " 页保存失败,已成功同步前 " + (page - 1) + " 页共 " + totalSynced + " 条"); result.put("synced", totalSynced); result.put("remoteCount", firstBody.count()); result.put("allMoney", firstBody.allMoney()); result.put("pages", page - 1); return result; } totalSynced += upserted; if (rows.size() < orderPageLimit) { break; } page++; } if (totalSynced == 0) { result.put("success", true); result.put("message", "接口成功,时间范围内无订单数据"); result.put("synced", 0); result.put("remoteCount", firstBody != null ? firstBody.count() : 0); result.put("pages", page); return result; } result.put("success", true); result.put("message", "同步完成"); result.put("synced", totalSynced); result.put("remoteCount", firstBody.count()); result.put("allMoney", firstBody.allMoney()); result.put("pages", page); return result; } finally { if (previousTenantId != null) { TenantContextHolder.setTenantId(previousTenantId); } else { TenantContextHolder.clear(); } } } catch (Exception e) { result.put("success", false); result.put("message", "同步异常:" + e.getMessage()); result.put("synced", 0); log.error("syncFromHxrAdmin failed", e); return result; } } private Optional fetchHxrOrderPage( String buyTimeStart, String buyTimeEnd, Integer isResell, Integer hxrOrderStatus, int page, HxrAdminOrderApiContext apiContext) throws Exception { if (hxrOrderStatus != null) { if (hxrOrderStatus == 0) { return hxrAdminOrderSelectService.fetchUnpaidOrderSelect( buyTimeStart, buyTimeEnd, page, apiContext); } if (hxrOrderStatus == 1) { return hxrAdminOrderSelectService.fetchPaidOrderSelect( buyTimeStart, buyTimeEnd, page, apiContext); } } return hxrAdminOrderSelectService.fetchOrderSelect( buyTimeStart, buyTimeEnd, isResell, page, apiContext); } @Override @Transactional(rollbackFor = Exception.class) public Map generateDailySumData( String buyTimeStart, String buyTimeEnd, String tenantId) { Map result = new HashMap<>(); try { if (tenantId == null || tenantId.trim().isEmpty()) { result.put("success", false); result.put("message", "tenantId不能为空"); return result; } String tid = tenantId.trim(); List details = listDetailRowsByBuyTimeRange(buyTimeStart, buyTimeEnd, tid); if (details.isEmpty()) { result.put("success", true); result.put("message", "时间范围内无明细数据"); result.put("generated", 0); result.put("sourceCount", 0); return result; } Map> byDay = new LinkedHashMap<>(); int skippedNoBuyTime = 0; for (LbOrderRow row : details) { LocalDate day = parseBuyTimeToDate(row.getBuyTime()); if (day == null) { skippedNoBuyTime++; continue; } byDay.computeIfAbsent(day, k -> new ArrayList<>()).add(row); } if (byDay.isEmpty()) { result.put("success", false); result.put("message", "明细 buy_time 均无法解析为日期,无法汇总"); result.put("skippedNoBuyTime", skippedNoBuyTime); return result; } String nowStr = LocalDateTime.now().format(DAY_TIME_FMT); List sumRows = new ArrayList<>(byDay.size()); for (Map.Entry> entry : byDay.entrySet()) { sumRows.add(buildDailySumRow(tid, entry.getKey(), entry.getValue(), nowStr)); } int upserted = upsertBatch(sumRows); boolean ok = upserted >= 0; result.put("success", ok); result.put("message", ok ? "按日汇总完成" : "保存失败"); result.put("generated", ok ? upserted : 0); result.put("sourceCount", details.size()); result.put("skippedNoBuyTime", skippedNoBuyTime); if (ok) { result.put("data", sumRows); } return result; } catch (Exception e) { result.put("success", false); result.put("message", "汇总异常:" + e.getMessage()); result.put("generated", 0); return result; } } @Override public Map sendSellerConfirmToDingTalk( String buyTimeStart, String buyTimeEnd, String tenantId) { Map result = new HashMap<>(); try { if (tenantId == null || tenantId.trim().isEmpty()) { result.put("success", false); result.put("message", "tenantId不能为空"); return result; } String tid = tenantId.trim(); List details = listDetailRowsByBuyTimeRange(buyTimeStart, buyTimeEnd, tid); if (details.isEmpty()) { result.put("success", true); result.put("message", "时间范围内无订单明细"); result.put("recordCount", 0); result.put("messageCount", 0); return result; } List sellerNames = details.stream() .map(this::resolveSellerDisplayName) .distinct() .limit(DINGTALK_SELLER_CONFIRM_BATCH_SIZE) .collect(Collectors.toList()); int messageCount = 0; if (!sellerNames.isEmpty()) { dingTalkRobotService.sendText( DINGTALK_SELLER_CONFIRM_PREFIX + String.join(",", sellerNames)); messageCount = 1; } result.put("success", true); result.put("message", "发送成功"); result.put("recordCount", details.size()); result.put("messageCount", messageCount); return result; } catch (IllegalArgumentException | IllegalStateException e) { log.warn( "发送卖家确认通知到钉钉失败, tenantId={}, buyTime={}~{}, reason={}", tenantId, buyTimeStart, buyTimeEnd, e.getMessage()); result.put("success", false); result.put("message", e.getMessage()); return result; } catch (Exception e) { log.error( "发送卖家确认通知到钉钉异常, tenantId={}, buyTime={}~{}", tenantId, buyTimeStart, buyTimeEnd, e); result.put("success", false); result.put("message", "发送失败:" + e.getMessage()); return result; } } private List listDetailRowsByBuyTimeRange( String buyTimeStart, String buyTimeEnd, String tenantId) { LambdaQueryWrapper w = new LambdaQueryWrapper<>(); w.eq(LbOrderRow::getTenantId, tenantId); w.eq(LbOrderRow::getDataType, SYNC_DATA_TYPE_DETAIL); if (buyTimeStart != null && !buyTimeStart.trim().isEmpty()) { w.ge(LbOrderRow::getBuyTime, buyTimeStart.trim()); } if (buyTimeEnd != null && !buyTimeEnd.trim().isEmpty()) { w.le(LbOrderRow::getBuyTime, buyTimeEnd.trim()); } w.orderByAsc(LbOrderRow::getBuyTime).orderByAsc(LbOrderRow::getId); return this.list(w); } private String resolveSellerDisplayName(LbOrderRow row) { if (row.getSellerName() != null && !row.getSellerName().trim().isEmpty()) { return row.getSellerName().trim(); } if (row.getSellerId() != null) { return String.valueOf(row.getSellerId()); } return "未知"; } private LbOrderRow buildDailySumRow( String tenantId, LocalDate day, List dayRows, String nowStr) { BigDecimal moneySum = BigDecimal.ZERO; int unresellCount = 0; for (LbOrderRow row : dayRows) { if (row.getTotalMoney() != null) { moneySum = moneySum.add(row.getTotalMoney()); } if (row.getIsResell() == null || row.getIsResell() != 1) { unresellCount++; } } String payTime = day.atStartOfDay().format(DAY_TIME_FMT); Long id = resolveSumRowId(tenantId, payTime); LbOrderRow sum = new LbOrderRow(); sum.setId(id); sum.setTenantId(tenantId); sum.setDataType(SUM_DATA_TYPE); sum.setTodayTotalMoneySum(moneySum); sum.setTodayUnresellCount(unresellCount); sum.setTodayOrderCount(dayRows.size()); sum.setSellerId(SUM_PLACEHOLDER_USER_ID); sum.setBuyerId(SUM_PLACEHOLDER_USER_ID); sum.setOrderSn(SUM_ORDER_SN); sum.setPhone("18808852688"); sum.setConsignee("老板统计"); sum.setTotalMoney(moneySum); sum.setIsResell(1); sum.setStatus(1); sum.setIsShow(1); sum.setPayTime(payTime); sum.setBuyTime(payTime); sum.setMerchandiseId(0L); sum.setCreatedAt(nowStr); sum.setUpdatedAt(nowStr); return sum; } /** 同一天、同一租户已存在 sum_data 则复用其 id,否则生成占位 id */ private Long resolveSumRowId(String tenantId, String payTime) { LambdaQueryWrapper w = new LambdaQueryWrapper<>(); w.eq(LbOrderRow::getTenantId, tenantId) .eq(LbOrderRow::getDataType, SUM_DATA_TYPE) .eq(LbOrderRow::getPayTime, payTime) .last("LIMIT 1"); LbOrderRow existing = this.getOne(w, false); if (existing != null && existing.getId() != null) { return existing.getId(); } LocalDate day = LocalDate.parse(payTime.substring(0, 10), DAY_FMT); long dayKey = day.getYear() * 10000L + day.getMonthValue() * 100L + day.getDayOfMonth(); int tenantSlot = Math.floorMod(tenantId.hashCode(), 10000); return 10_000_000_000_000_000L + dayKey * 10_000L + tenantSlot; } private static LocalDate parseBuyTimeToDate(String buyTime) { if (buyTime == null || buyTime.trim().isEmpty()) { return null; } String text = buyTime.trim(); try { return LocalDateTime.parse(text, DAY_TIME_FMT).toLocalDate(); } catch (DateTimeParseException ignored) { // continue } try { return LocalDate.parse(text.substring(0, Math.min(10, text.length())), DAY_FMT); } catch (DateTimeParseException ignored) { // continue } try { return LocalDateTime.parse(text, DateTimeFormatter.ISO_LOCAL_DATE_TIME).toLocalDate(); } catch (DateTimeParseException e) { return null; } } 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; } } /** * 按复合主键 {@code (tenant_id, id)} 插入或更新;同批重复键保留最后一条。 * * @return 实际 upsert 条数;无有效键时返回 0;失败返回 -1 */ private int upsertBatch(List entities) { if (entities == null || entities.isEmpty()) { return 0; } Map deduped = new LinkedHashMap<>(); for (LbOrderRow entity : entities) { if (entity.getId() == null) { continue; } String tenantId = trimToNull(entity.getTenantId()); if (tenantId == null) { continue; } entity.setTenantId(tenantId); deduped.put(compositeKey(tenantId, entity.getId()), entity); } if (deduped.isEmpty()) { return 0; } List rows = new ArrayList<>(deduped.values()); try { baseMapper.upsertBatch(rows); return rows.size(); } catch (Exception e) { log.error("lb_order_row upsertBatch failed, size={}", rows.size(), e); return -1; } } /** * 按 seller_id 关联 lb_department_user.user_id,用部门用户的 name、phone 填充卖方姓名与电话。 */ private void fillSellerInfoFromDepartmentUsers(List entities, String tenantId) { if (entities == null || entities.isEmpty()) { return; } Set sellerIds = new HashSet<>(); for (LbOrderRow entity : entities) { if (entity.getSellerId() != null) { sellerIds.add(entity.getSellerId()); } } if (sellerIds.isEmpty()) { return; } List userIdStrs = new ArrayList<>(sellerIds.size()); for (Long sellerId : sellerIds) { userIdStrs.add(String.valueOf(sellerId)); } log.info("sellerIds:{}",userIdStrs.toString()); LambdaQueryWrapper w = new LambdaQueryWrapper<>(); w.eq(LbDepartmentUser::getTenantId, tenantId) .in(LbDepartmentUser::getUserId, userIdStrs); List users = lbDepartmentUserService.list(w); Map byUserId = new LinkedHashMap<>(); for (LbDepartmentUser user : users) { if (user.getUserId() != null && !byUserId.containsKey(user.getUserId())) { byUserId.put(user.getUserId(), user); } } for (LbOrderRow entity : entities) { if (entity.getSellerId() == null) { continue; } LbDepartmentUser user = byUserId.get(String.valueOf(entity.getSellerId())); if (user != null) { entity.setSellerName(user.getName()); entity.setSellerPhone(user.getPhone()); } } } @Autowired private ILbUserService lbUserService; /** * 按 buyer_id 关联 lb_department_user.user_id,用部门用户的 name、phone 填充买方姓名与电话。 */ private void fillBuyerInfoFromLBtUsers(List entities, String tenantId) { if (entities == null || entities.isEmpty()) { return; } Set buyerIds = new HashSet<>(); for (LbOrderRow entity : entities) { if (entity.getBuyerId() != null) { buyerIds.add(entity.getBuyerId()); } } if (buyerIds.isEmpty()) { return; } List userIdStrs = new ArrayList<>(buyerIds.size()); for (Long buyerId : buyerIds) { userIdStrs.add(String.valueOf(buyerId)); } log.info("buyerIds:{}", userIdStrs.toString()); LambdaQueryWrapper w = new LambdaQueryWrapper<>(); w.eq(LbUser::getTenantId, tenantId) .in(LbUser::getId, userIdStrs); List users = lbUserService.list(w); Map byUserId = new LinkedHashMap<>(); for (LbUser user : users) { if (user.getId() != null && !byUserId.containsKey(user.getId())) { byUserId.put(user.getId().toString(), user); } } for (LbOrderRow entity : entities) { if (entity.getBuyerId() == null) { continue; } LbUser user = byUserId.get(String.valueOf(entity.getBuyerId())); if (user != null) { entity.setBuyerName(user.getNickname()); entity.setBuyerPhone(user.getMobile()); } } } private static LbOrderRow toLbOrderRow(HxrOrderRow row, String tenantId) { LbOrderRow e = new LbOrderRow(); e.setId(row.id()); e.setOldId(row.oldId()); e.setTenantId(tenantId); e.setDataType(SYNC_DATA_TYPE_DETAIL); e.setTodayTotalMoneySum(BigDecimal.ZERO); e.setTodayOrderCount(0); e.setTodayUnresellCount(0); e.setSellerId(row.sellerId()); e.setSellerName(row.sellerName()); e.setSellerPhone(row.sellerPhone()); e.setBuyerId(row.buyerId()); e.setBuyerName(row.buyerName()); e.setBuyerPhone(row.buyerPhone()); e.setOrderSn(row.orderSn()); e.setTotalMoney(parseMoney(row.totalMoney())); e.setPayTime(row.payTime()); e.setPayImg(row.payImg()); e.setStatus(row.status()); e.setIsResell(row.isResell()); e.setIsShow(row.isShow()); e.setConsignee(row.consignee()); e.setPhone(row.phone()); e.setProvince(row.province()); e.setCity(row.city()); e.setArea(row.area()); e.setAddress(row.address()); e.setMerchandiseId(row.merchandiseId()); e.setConfirmTime(row.confirmTime()); e.setBuyTime(row.buyTime()); e.setCreatedAt(row.createdAt()); e.setUpdatedAt(row.updatedAt()); return e; } private LbOrderRow getByTenantAndId(String tenantId, Long id) { if (trimToNull(tenantId) == null || id == null) { return null; } return this.getOne( new LambdaQueryWrapper() .eq(LbOrderRow::getTenantId, tenantId.trim()) .eq(LbOrderRow::getId, id), false); } private String resolveTenantId(String tenantIdFromParam) { String current = trimToNull(TenantContextHolder.getTenantId()); if (current != null) { return current; } return trimToNull(tenantIdFromParam); } private static String compositeKey(String tenantId, Long id) { return tenantId + ":" + id; } private static String trimToNull(String value) { if (value == null) { return null; } String trimmed = value.trim(); return trimmed.isEmpty() ? null : trimmed; } }