报表导出
This commit is contained in:
@@ -6,11 +6,15 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.rj.entity.LbDailyUserTrade;
|
||||
import com.rj.entity.LbDailyUserTradeReport;
|
||||
import com.rj.entity.LbDepartmentUser;
|
||||
import com.rj.entity.LbOrderRow;
|
||||
import com.rj.entity.LbUser;
|
||||
import com.rj.service.ILbDeductionAmountService;
|
||||
import com.rj.service.ILbOrderRowService;
|
||||
import com.rj.mapper.LbDailyUserTradeMapper;
|
||||
import com.rj.mapper.LbDailyUserTradeReportMapper;
|
||||
import com.rj.service.ILbDailyUserTradeReportService;
|
||||
import com.rj.service.ILbDepartmentUserService;
|
||||
import com.rj.service.ILbUserService;
|
||||
import com.rj.service.support.LbDailyUserTradeReportExportSupport;
|
||||
import com.rj.service.support.LbDailyUserTradeReportWriteSupport;
|
||||
import com.rj.tenant.TenantContextHolder;
|
||||
@@ -26,6 +30,7 @@ import org.springframework.transaction.support.TransactionTemplate;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
@@ -48,6 +53,8 @@ public class LbDailyUserTradeReportServiceImpl extends ServiceImpl<LbDailyUserTr
|
||||
private static final Logger log = LoggerFactory.getLogger(LbDailyUserTradeReportServiceImpl.class);
|
||||
private static final String DATA_TYPE_REPORT_SUM = "report_sum";
|
||||
private static final String DATA_TYPE_REPORT_DETAIL = "report_detail";
|
||||
private static final String ORDER_DATA_TYPE_DETAIL = "detail_data";
|
||||
private static final DateTimeFormatter ORDER_BUY_TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
private static final BigDecimal CROSS_DAY_DIFF_TOLERANCE = new BigDecimal("3");
|
||||
|
||||
@Autowired
|
||||
@@ -58,6 +65,10 @@ public class LbDailyUserTradeReportServiceImpl extends ServiceImpl<LbDailyUserTr
|
||||
private PlatformTransactionManager transactionManager;
|
||||
@Autowired
|
||||
private ILbDeductionAmountService lbDeductionAmountService;
|
||||
@Autowired
|
||||
private ILbUserService lbUserService;
|
||||
@Autowired
|
||||
private ILbOrderRowService lbOrderRowService;
|
||||
|
||||
@Override
|
||||
public Page<LbDailyUserTrade> pageDailyUserTradeByUserIdAndDateRange(
|
||||
@@ -527,8 +538,18 @@ public class LbDailyUserTradeReportServiceImpl extends ServiceImpl<LbDailyUserTr
|
||||
List<LbDailyUserTradeReport> rows = listReportsForExport(tenantIdTrim, parsedReportDate);
|
||||
Map<String, BigDecimal> deductionByUserName =
|
||||
lbDeductionAmountService.sumDikouAmtByUserNameForReportDate(tenantIdTrim, parsedReportDate);
|
||||
Map<String, String> userIdByNickname = buildUserIdByNickname(tenantIdTrim);
|
||||
OrderTradeExportData orderTradeExportData = buildOrderTradeExportData(tenantIdTrim, parsedReportDate);
|
||||
LbDailyUserTradeReportExportSupport.writeExcel(
|
||||
parsedReportDate, rows, deductionByUserName, response);
|
||||
parsedReportDate,
|
||||
rows,
|
||||
deductionByUserName,
|
||||
userIdByNickname,
|
||||
orderTradeExportData.buyTotalByUserId(),
|
||||
orderTradeExportData.buyDetailByUserId(),
|
||||
orderTradeExportData.sellTotalByUserId(),
|
||||
orderTradeExportData.sellDetailByUserId(),
|
||||
response);
|
||||
} catch (Exception e) {
|
||||
LbDailyUserTradeReportExportSupport.writeExportError(response, e);
|
||||
}
|
||||
@@ -557,6 +578,97 @@ public class LbDailyUserTradeReportServiceImpl extends ServiceImpl<LbDailyUserTr
|
||||
return rows;
|
||||
}
|
||||
|
||||
/** 结果表检查表 N 列:按 lb_user.nickname 匹配用户 id。 */
|
||||
private Map<String, String> buildUserIdByNickname(String tenantId) {
|
||||
List<LbUser> users = lbUserService.list(
|
||||
new LambdaQueryWrapper<LbUser>().eq(LbUser::getTenantId, tenantId));
|
||||
Map<String, String> userIdByNickname = new HashMap<>();
|
||||
if (users == null) {
|
||||
return userIdByNickname;
|
||||
}
|
||||
for (LbUser user : users) {
|
||||
if (user.getNickname() == null || user.getNickname().trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (user.getId() == null) {
|
||||
continue;
|
||||
}
|
||||
userIdByNickname.putIfAbsent(user.getNickname().trim(), String.valueOf(user.getId()));
|
||||
}
|
||||
return userIdByNickname;
|
||||
}
|
||||
|
||||
/** 结果表检查表 P/Q/S/T:按租户与当日 buy_time 查询 lb_order_row,分别按 buyer_id、seller_id 汇总。 */
|
||||
private OrderTradeExportData buildOrderTradeExportData(String tenantId, LocalDate reportDate) {
|
||||
String buyTimeStart = reportDate.atStartOfDay().format(ORDER_BUY_TIME_FMT);
|
||||
String buyTimeEnd = reportDate.atTime(23, 59, 59).format(ORDER_BUY_TIME_FMT);
|
||||
|
||||
List<LbOrderRow> orderRows = lbOrderRowService.list(
|
||||
new LambdaQueryWrapper<LbOrderRow>()
|
||||
.eq(LbOrderRow::getTenantId, tenantId)
|
||||
.eq(LbOrderRow::getDataType, ORDER_DATA_TYPE_DETAIL)
|
||||
.ge(LbOrderRow::getBuyTime, buyTimeStart)
|
||||
.le(LbOrderRow::getBuyTime, buyTimeEnd)
|
||||
.orderByAsc(LbOrderRow::getBuyTime)
|
||||
.orderByAsc(LbOrderRow::getId));
|
||||
|
||||
Map<String, BigDecimal> buyTotalByUserId = new HashMap<>();
|
||||
Map<String, List<BigDecimal>> buyAmountsByUserId = new LinkedHashMap<>();
|
||||
Map<String, BigDecimal> sellTotalByUserId = new HashMap<>();
|
||||
Map<String, List<BigDecimal>> sellAmountsByUserId = new LinkedHashMap<>();
|
||||
if (orderRows == null) {
|
||||
return new OrderTradeExportData(buyTotalByUserId, Map.of(), sellTotalByUserId, Map.of());
|
||||
}
|
||||
for (LbOrderRow row : orderRows) {
|
||||
BigDecimal money = row.getTotalMoney() == null ? BigDecimal.ZERO : row.getTotalMoney();
|
||||
if (row.getBuyerId() != null) {
|
||||
String buyerId = String.valueOf(row.getBuyerId());
|
||||
buyTotalByUserId.merge(buyerId, money, BigDecimal::add);
|
||||
buyAmountsByUserId.computeIfAbsent(buyerId, k -> new ArrayList<>()).add(money);
|
||||
}
|
||||
if (row.getSellerId() != null) {
|
||||
String sellerId = String.valueOf(row.getSellerId());
|
||||
sellTotalByUserId.merge(sellerId, money, BigDecimal::add);
|
||||
sellAmountsByUserId.computeIfAbsent(sellerId, k -> new ArrayList<>()).add(money);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String> buyDetailByUserId = new HashMap<>();
|
||||
for (Map.Entry<String, List<BigDecimal>> entry : buyAmountsByUserId.entrySet()) {
|
||||
buyDetailByUserId.put(entry.getKey(), formatOrderAmountDetail(entry.getValue()));
|
||||
}
|
||||
Map<String, String> sellDetailByUserId = new HashMap<>();
|
||||
for (Map.Entry<String, List<BigDecimal>> entry : sellAmountsByUserId.entrySet()) {
|
||||
sellDetailByUserId.put(entry.getKey(), formatOrderAmountDetail(entry.getValue()));
|
||||
}
|
||||
return new OrderTradeExportData(
|
||||
buyTotalByUserId, buyDetailByUserId, sellTotalByUserId, sellDetailByUserId);
|
||||
}
|
||||
|
||||
private static String formatOrderAmountDetail(List<BigDecimal> amounts) {
|
||||
if (amounts == null || amounts.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (BigDecimal amount : amounts) {
|
||||
if (amount == null) {
|
||||
continue;
|
||||
}
|
||||
if (!sb.isEmpty()) {
|
||||
sb.append('+');
|
||||
}
|
||||
sb.append(amount.stripTrailingZeros().toPlainString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private record OrderTradeExportData(
|
||||
Map<String, BigDecimal> buyTotalByUserId,
|
||||
Map<String, String> buyDetailByUserId,
|
||||
Map<String, BigDecimal> sellTotalByUserId,
|
||||
Map<String, String> sellDetailByUserId) {
|
||||
}
|
||||
|
||||
private static final class DetailRepairStat {
|
||||
final boolean repaired;
|
||||
final int updatedCount;
|
||||
|
||||
@@ -7,11 +7,9 @@ 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.DingTalkRobotService;
|
||||
import com.rj.service.HxrAdminOrderSelectService;
|
||||
import com.rj.service.ILbDepartmentUserService;
|
||||
import com.rj.service.ILbOrderRowService;
|
||||
import com.rj.service.*;
|
||||
import com.rj.tenant.TenantContextHolder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -303,6 +301,7 @@ public class LbOrderRowServiceImpl extends ServiceImpl<LbOrderRowMapper, LbOrder
|
||||
entities.add(toLbOrderRow(row, tid));
|
||||
}
|
||||
fillSellerInfoFromDepartmentUsers(entities, tid);
|
||||
fillBuyerInfoFromLBtUsers(entities, tid);
|
||||
int upserted = upsertBatch(entities);
|
||||
if (upserted < 0) {
|
||||
result.put("success", false);
|
||||
@@ -685,6 +684,50 @@ public class LbOrderRowServiceImpl extends ServiceImpl<LbOrderRowMapper, LbOrder
|
||||
}
|
||||
}
|
||||
}
|
||||
@Autowired
|
||||
private ILbUserService lbUserService;
|
||||
/**
|
||||
* 按 buyer_id 关联 lb_department_user.user_id,用部门用户的 name、phone 填充买方姓名与电话。
|
||||
*/
|
||||
private void fillBuyerInfoFromLBtUsers(List<LbOrderRow> entities, String tenantId) {
|
||||
if (entities == null || entities.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Set<Long> buyerIds = new HashSet<>();
|
||||
for (LbOrderRow entity : entities) {
|
||||
if (entity.getBuyerId() != null) {
|
||||
buyerIds.add(entity.getBuyerId());
|
||||
}
|
||||
}
|
||||
if (buyerIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<String> userIdStrs = new ArrayList<>(buyerIds.size());
|
||||
for (Long buyerId : buyerIds) {
|
||||
userIdStrs.add(String.valueOf(buyerId));
|
||||
}
|
||||
log.info("buyerIds:{}", userIdStrs.toString());
|
||||
LambdaQueryWrapper<LbUser> w = new LambdaQueryWrapper<>();
|
||||
w.eq(LbUser::getTenantId, tenantId)
|
||||
.in(LbUser::getId, userIdStrs);
|
||||
List<LbUser> users = lbUserService.list(w);
|
||||
Map<String, LbUser> 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();
|
||||
@@ -699,6 +742,8 @@ public class LbOrderRowServiceImpl extends ServiceImpl<LbOrderRowMapper, LbOrder
|
||||
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());
|
||||
|
||||
@@ -14,11 +14,13 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.rj.dto.hxr.HxrUserRow;
|
||||
import com.rj.entity.LbDailyUserTrade;
|
||||
import com.rj.entity.LbDailyUserTradeReport;
|
||||
import com.rj.entity.LbOrderRow;
|
||||
import com.rj.entity.LbPurchaseApply;
|
||||
import com.rj.entity.LbUser;
|
||||
import com.rj.util.IsoWorkdayUtils;
|
||||
import com.rj.mapper.LbDailyUserTradeMapper;
|
||||
import com.rj.mapper.LbDailyUserTradeReportMapper;
|
||||
import com.rj.mapper.LbOrderRowMapper;
|
||||
import com.rj.mapper.LbPurchaseApplyMapper;
|
||||
import com.rj.mapper.LbUserMapper;
|
||||
import com.rj.service.HxrAdminUserService;
|
||||
@@ -46,6 +48,7 @@ import java.util.HashSet;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@@ -71,12 +74,22 @@ public class LbPurchaseApplyServiceImpl
|
||||
@Autowired
|
||||
private LbUserMapper lbUserMapper;
|
||||
|
||||
@Autowired
|
||||
private LbOrderRowMapper lbOrderRowMapper;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Value("${langchain4j.open-ai.chat-model.model-name:qwen-plus}")
|
||||
private String dashScopeChatModel;
|
||||
|
||||
private static final String ORDER_DATA_TYPE_DETAIL = "detail_data";
|
||||
|
||||
private static final DateTimeFormatter ORDER_DAY_TIME_FMT =
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
private static final DateTimeFormatter ORDER_DAY_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
|
||||
private static final DateTimeFormatter TRY_DATE_MM_DD = DateTimeFormatter.ofPattern("MM.dd");
|
||||
private static final DateTimeFormatter PRIVILEGE_RANGE_FMT = DateTimeFormatter.ofPattern("MM.dd");
|
||||
private static final DateTimeFormatter EXPORT_FILENAME_TS = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
||||
@@ -447,6 +460,7 @@ public class LbPurchaseApplyServiceImpl
|
||||
|
||||
Page<LbPurchaseApply> page = this.page(new Page<>(current, size), queryWrapper);
|
||||
fillLinkedLbUsers(page.getRecords());
|
||||
fillLatestTradeOrders(page.getRecords());
|
||||
result.put("success", true);
|
||||
result.put("message", "查询成功");
|
||||
result.put("data", page.getRecords());
|
||||
@@ -507,6 +521,133 @@ public class LbPurchaseApplyServiceImpl
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按租户 + 申请人手机号关联 {@link LbOrderRow}:{@code apply_phone} 匹配 {@code buyer_phone},
|
||||
* 取 pay_time 最近的一条已成交明细订单。
|
||||
*/
|
||||
private void fillLatestTradeOrders(List<LbPurchaseApply> records) {
|
||||
if (records == null || records.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Set<String> buyerPhones = new HashSet<>();
|
||||
Set<String> tenantIds = new HashSet<>();
|
||||
for (LbPurchaseApply record : records) {
|
||||
String applyPhone = trimToNull(record.getApplyPhone());
|
||||
if (applyPhone != null) {
|
||||
buyerPhones.add(applyPhone);
|
||||
}
|
||||
String tenantId = trimToNull(record.getTenantId());
|
||||
if (tenantId != null) {
|
||||
tenantIds.add(tenantId);
|
||||
}
|
||||
}
|
||||
if (buyerPhones.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<LbOrderRow> orderQuery = new LambdaQueryWrapper<>();
|
||||
orderQuery.eq(LbOrderRow::getDataType, ORDER_DATA_TYPE_DETAIL)
|
||||
.in(LbOrderRow::getBuyerPhone, buyerPhones)
|
||||
.isNotNull(LbOrderRow::getPayTime);
|
||||
if (tenantIds.size() == 1) {
|
||||
orderQuery.eq(LbOrderRow::getTenantId, tenantIds.iterator().next());
|
||||
} else if (tenantIds.size() > 1) {
|
||||
orderQuery.in(LbOrderRow::getTenantId, tenantIds);
|
||||
}
|
||||
|
||||
List<LbOrderRow> orderRows = lbOrderRowMapper.selectList(orderQuery);
|
||||
if (orderRows == null || orderRows.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, LbOrderRow> latestByTenantPhone = new HashMap<>();
|
||||
for (LbOrderRow row : orderRows) {
|
||||
if (!isPaidOrderRow(row)) {
|
||||
continue;
|
||||
}
|
||||
String buyerPhone = trimToNull(row.getBuyerPhone());
|
||||
if (buyerPhone == null) {
|
||||
continue;
|
||||
}
|
||||
String key = buildTenantMobileKey(row.getTenantId(), buyerPhone);
|
||||
LbOrderRow prev = latestByTenantPhone.get(key);
|
||||
if (prev == null || isOrderPayTimeNewer(row, prev)) {
|
||||
latestByTenantPhone.put(key, row);
|
||||
}
|
||||
}
|
||||
|
||||
for (LbPurchaseApply record : records) {
|
||||
String applyPhone = trimToNull(record.getApplyPhone());
|
||||
if (applyPhone == null) {
|
||||
continue;
|
||||
}
|
||||
LbOrderRow latest = latestByTenantPhone.get(buildTenantMobileKey(record.getTenantId(), applyPhone));
|
||||
if (latest != null) {
|
||||
record.setLatestTradeOrder(latest);
|
||||
}else {
|
||||
record.setLatestTradeOrder(new LbOrderRow());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isPaidOrderRow(LbOrderRow row) {
|
||||
String payTime = row.getPayTime();
|
||||
return payTime != null && !payTime.trim().isEmpty();
|
||||
}
|
||||
|
||||
private static boolean isOrderPayTimeNewer(LbOrderRow order, LbOrderRow prev) {
|
||||
LocalDateTime payTime = parseOrderTime(payTimeOrBuyTime(order));
|
||||
LocalDateTime prevPayTime = parseOrderTime(payTimeOrBuyTime(prev));
|
||||
if (payTime == null) {
|
||||
return false;
|
||||
}
|
||||
if (prevPayTime == null) {
|
||||
return true;
|
||||
}
|
||||
int cmp = payTime.compareTo(prevPayTime);
|
||||
if (cmp != 0) {
|
||||
return cmp > 0;
|
||||
}
|
||||
Long id = order.getId();
|
||||
Long prevId = prev.getId();
|
||||
if (id == null) {
|
||||
return false;
|
||||
}
|
||||
if (prevId == null) {
|
||||
return true;
|
||||
}
|
||||
return id > prevId;
|
||||
}
|
||||
|
||||
private static String payTimeOrBuyTime(LbOrderRow row) {
|
||||
if (row.getPayTime() != null && !row.getPayTime().trim().isEmpty()) {
|
||||
return row.getPayTime().trim();
|
||||
}
|
||||
return row.getBuyTime() == null ? null : row.getBuyTime().trim();
|
||||
}
|
||||
|
||||
private static LocalDateTime parseOrderTime(String timeText) {
|
||||
if (timeText == null || timeText.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return LocalDateTime.parse(timeText, ORDER_DAY_TIME_FMT);
|
||||
} catch (DateTimeParseException ignored) {
|
||||
// continue
|
||||
}
|
||||
try {
|
||||
return LocalDate.parse(timeText.substring(0, Math.min(10, timeText.length())), ORDER_DAY_FMT)
|
||||
.atStartOfDay();
|
||||
} catch (DateTimeParseException ignored) {
|
||||
// continue
|
||||
}
|
||||
try {
|
||||
return LocalDateTime.parse(timeText, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
|
||||
} catch (DateTimeParseException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页关联用户时仅暴露业务所需字段,其余置空或 0,避免泄露手机号、密码等隐私。
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user