资金流水

master
ljw 2026-01-22 17:01:55 +08:00
parent 820f28fd17
commit 367639ae1b
8 changed files with 451 additions and 6 deletions

View File

@ -89,6 +89,18 @@
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.35</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>
</dependencies>
<build>

View File

@ -0,0 +1,133 @@
package com.njzscloud.dispose.common.utils;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.poi.excel.BigExcelWriter;
import cn.hutool.poi.excel.ExcelUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* File
*
* @author ljw
* @date 2505-10-13
*/
@Slf4j
public class FileUtil extends cn.hutool.core.io.FileUtil {
/**
*
* <br>
* windows Linux ,
* windows \\==\
*
* <pre>
* java.io.tmpdir
* windows : C:\Users/xxx\AppData\Local\Temp\
* linux: /temp
* </pre>
*/
public static final String SYS_TEM_DIR = System.getProperty("java.io.tmpdir") + File.separator;
/**
* excel
*/
public static void downloadExcel(List<Map<String, Object>> list, HttpServletResponse response, String fileName) throws IOException {
String tempPath = SYS_TEM_DIR + IdUtil.fastSimpleUUID() + ".xlsx";
File file = new File(tempPath);
BigExcelWriter writer = ExcelUtil.getBigWriter(file);
// 处理数据以防止CSV注入
List<Map<String, Object>> sanitizedList = list.parallelStream().map(map -> {
Map<String, Object> sanitizedMap = new LinkedHashMap<>();
map.forEach((key, value) -> {
if (value instanceof String) {
String strValue = (String) value;
// 检查并处理以特殊字符开头的值
if (strValue.startsWith("=") || strValue.startsWith("+") || strValue.startsWith("-") || strValue.startsWith("@")) {
strValue = "'" + strValue; // 添加单引号前缀
}
sanitizedMap.put(key, strValue);
} else {
sanitizedMap.put(key, value);
}
});
return sanitizedMap;
}).collect(Collectors.toList());
// 一次性写出内容,使用默认样式,强制输出标题
writer.write(sanitizedList, true);
SXSSFSheet sheet = (SXSSFSheet) writer.getSheet();
// 手动设置列宽,确保标题和数据内容都能完整显示
/*int columnCount = sheet.getRow(0).getLastCellNum();
for (int i = 0; i < columnCount; i++) {
String headerText = "";
int maxContentLength = 0;
try {
// 获取标题文本
if (sheet.getRow(0) != null && sheet.getRow(0).getCell(i) != null) {
headerText = sheet.getRow(0).getCell(i).getStringCellValue();
}
// 遍历所有数据行,找到最长的内容
for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
if (sheet.getRow(rowIndex) != null && sheet.getRow(rowIndex).getCell(i) != null) {
String cellValue = sheet.getRow(rowIndex).getCell(i).getStringCellValue();
if (cellValue != null) {
maxContentLength = Math.max(maxContentLength, cellValue.length());
}
}
}
} catch (Exception e) {
// 忽略获取单元格值的异常
}
// 根据标题和内容的最大长度计算合适的列宽
int headerLength = headerText.length();
int contentLength = maxContentLength;
int maxLength = Math.max(headerLength, contentLength);
// 设置列宽每个字符约2个字符宽度最小8个字符最大50个字符
int calculatedWidth = Math.max(Math.min(maxLength * 2, 50), 8) * 256;
int currentWidth = sheet.getColumnWidth(i);
// 设置列宽,确保标题和内容都能完整显示
sheet.setColumnWidth(i, Math.max(calculatedWidth, currentWidth));
}*/
// 尝试自动调整列宽
try {
sheet.trackAllColumnsForAutoSizing();
writer.autoSizeColumnAll();
} catch (Exception e) {
log.warn("自动调整列宽失败,使用手动设置的列宽", e);
}
//response为HttpServletResponse对象
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
//设置文件名,支持中文文件名
try {
String encodedFileName = java.net.URLEncoder.encode(fileName, "UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + encodedFileName);
} catch (Exception e) {
// 如果编码失败,使用默认文件名
response.setHeader("Content-Disposition", "attachment;filename=file.xlsx");
}
ServletOutputStream out = response.getOutputStream();
// 终止后删除临时文件
file.deleteOnExit();
writer.flush(out, true);
//此处记得关闭输出Servlet流
IoUtil.close(out);
}
}

View File

@ -4,11 +4,14 @@ import com.njzscloud.common.core.utils.R;
import com.njzscloud.common.mp.support.PageParam;
import com.njzscloud.common.mp.support.PageResult;
import com.njzscloud.dispose.finance.pojo.entity.MoneyFlowEntity;
import com.njzscloud.dispose.finance.pojo.param.SearchMoneyFlowParam;
import com.njzscloud.dispose.finance.pojo.result.MoneyFlowResult;
import com.njzscloud.dispose.finance.service.MoneyFlowService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/**
@ -62,8 +65,16 @@ public class MoneyFlowController {
*
*/
@GetMapping("/paging")
public R<PageResult<MoneyFlowEntity>> paging(PageParam pageParam, MoneyFlowEntity moneyFlowEntity) {
return R.success(moneyFlowService.paging(pageParam, moneyFlowEntity));
public R<PageResult<MoneyFlowResult>> paging(PageParam pageParam, SearchMoneyFlowParam flowParam) {
return R.success(moneyFlowService.paging(pageParam, flowParam));
}
/**
*
*/
@GetMapping(value = "/export")
public void export(HttpServletResponse response, SearchMoneyFlowParam flowParam) throws IOException {
moneyFlowService.export(response, flowParam);
}
}

View File

@ -1,14 +1,26 @@
package com.njzscloud.dispose.finance.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njzscloud.dispose.finance.pojo.entity.MoneyFlowEntity;
import com.njzscloud.dispose.finance.pojo.result.MoneyFlowResult;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
*
*
* @author ljw
*/
@Mapper
public interface MoneyFlowMapper extends BaseMapper<MoneyFlowEntity> {
}
IPage<MoneyFlowResult> paging(Page<Object> page, @Param("ew") QueryWrapper<?> ew);
List<MoneyFlowResult> paging(@Param("ew") QueryWrapper<?> ew);
}

View File

@ -0,0 +1,62 @@
package com.njzscloud.dispose.finance.pojo.param;
import com.njzscloud.dispose.finance.constant.AccountType;
import com.njzscloud.dispose.finance.constant.MoneyChangeCategory;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
*
*
* @author ljw
*/
@Getter
@Setter
@ToString
@Accessors(chain = true)
public class SearchMoneyFlowParam {
/**
* Id
*/
private Long orderId;
/**
*
*/
private String orderSn;
/**
*
*/
private Integer trainNum;
/**
* Idfin_money_account.id
*/
private Long moneyAccountId;
/**
* money_change_category
*/
private MoneyChangeCategory moneyChangeCategory;
/**
*
*/
private String accountName;
/**
* GeRen-->QiYe-->
*/
private AccountType accountType;
private LocalDateTime startTime;
private LocalDateTime endTime;
}

View File

@ -0,0 +1,115 @@
package com.njzscloud.dispose.finance.pojo.result;
import com.njzscloud.dispose.finance.constant.AccountType;
import com.njzscloud.dispose.finance.constant.MoneyChangeCategory;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
/**
*
*
* @author ljw
*/
@Getter
@Setter
@ToString
@Accessors(chain = true)
public class MoneyFlowResult {
/**
*
*/
private Long id;
/**
* Id
*/
private Long orderId;
/**
*
*/
private String orderSn;
/**
*
*/
private Integer trainNum;
/**
* Id
*/
private Long moneyAccountId;
/**
*
*/
private BigDecimal beforeBalance;
/**
*
*/
private BigDecimal delta;
/**
*
*/
private BigDecimal afterBalance;
/**
* money_change_category
*/
private MoneyChangeCategory moneyChangeCategory;
/**
*
*/
private String memo;
/**
*
*/
private List<String> fileUrl;
/**
* Id
*/
private Long creatorId;
/**
* Id
*/
private Long modifierId;
/**
*
*/
private LocalDateTime createTime;
/**
*
*/
private LocalDateTime modifyTime;
/**
*
*/
private Boolean deleted;
/**
*
*/
private String accountName;
/**
* GeRen-->QiYe-->
*/
private AccountType accountType;
}

View File

@ -1,17 +1,27 @@
package com.njzscloud.dispose.finance.service;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njzscloud.common.mp.support.PageParam;
import com.njzscloud.common.mp.support.PageResult;
import com.njzscloud.dispose.common.utils.FileUtil;
import com.njzscloud.dispose.finance.mapper.MoneyFlowMapper;
import com.njzscloud.dispose.finance.pojo.entity.MoneyFlowEntity;
import com.njzscloud.dispose.finance.pojo.param.SearchMoneyFlowParam;
import com.njzscloud.dispose.finance.pojo.result.MoneyFlowResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
*
@ -52,10 +62,56 @@ public class MoneyFlowService extends ServiceImpl<MoneyFlowMapper, MoneyFlowEnti
return this.getById(id);
}
/**
*
*/
private QueryWrapper<MoneyFlowEntity> buildQueryWrapper(SearchMoneyFlowParam param) {
return Wrappers.<MoneyFlowEntity>query()
.eq("fmf.deleted", Boolean.FALSE)
.eq(param.getOrderId() != null, "fmf.order_id", param.getOrderId())
.like(StrUtil.isNotBlank(param.getOrderSn()), "fmf.order_sn", param.getOrderSn())
.eq(param.getTrainNum() != null, "fmf.train_num", param.getTrainNum())
.eq(param.getMoneyAccountId() != null, "fmf.money_account_id", param.getMoneyAccountId())
.eq(param.getMoneyChangeCategory() != null, "fmf.money_change_category", param.getMoneyChangeCategory())
.and(StrUtil.isNotBlank(param.getAccountName()), wrapper ->
wrapper.like("cc.customer_name", param.getAccountName())
.or()
.like("co.org_name", param.getAccountName())
)
.eq(param.getAccountType() != null, "fma.account_type", param.getAccountType())
.ge(param.getStartTime() != null, "fmf.create_time", param.getStartTime())
.le(param.getEndTime() != null, "fmf.create_time", param.getEndTime());
}
/**
*
*/
public PageResult<MoneyFlowEntity> paging(PageParam pageParam, MoneyFlowEntity moneyFlowEntity) {
return PageResult.of(this.page(pageParam.toPage(), Wrappers.query(moneyFlowEntity)));
public PageResult<MoneyFlowResult> paging(PageParam pageParam, SearchMoneyFlowParam param) {
return PageResult.of(baseMapper.paging(pageParam.toPage(), buildQueryWrapper(param)));
}
/**
*
*/
public void export(HttpServletResponse response, SearchMoneyFlowParam param) throws IOException {
List<MoneyFlowResult> exportList = baseMapper.paging(buildQueryWrapper(param));
List<Map<String, Object>> downList = new ArrayList<>();
int i = 1;
for (MoneyFlowResult result : exportList) {
Map<String, Object> map = new LinkedHashMap<>();
map.put("编号", i);
map.put("账户名称", result.getAccountName());
map.put("账户类型", result.getAccountType().getTxt());
map.put("订单号", result.getOrderSn());
map.put("车次", result.getTrainNum());
map.put("变动类型", result.getMoneyChangeCategory().getTxt());
map.put("变动前金额", result.getBeforeBalance());
map.put("变动金额", result.getDelta());
map.put("变动后金额", result.getAfterBalance());
map.put("备注", result.getMemo());
downList.add(map);
i++;
}
FileUtil.downloadExcel(downList, response, "资金变动明细.xlsx");
}
}

View File

@ -0,0 +1,44 @@
<?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.njzscloud.dispose.finance.mapper.MoneyFlowMapper">
<resultMap id="BaseResultMap" type="com.njzscloud.dispose.finance.pojo.result.MoneyFlowResult" autoMapping="true">
<id column="id" property="id"/>
<result column="file_url" property="fileUrl" typeHandler="com.njzscloud.common.mp.support.handler.j.JsonTypeHandler"/>
</resultMap>
<sql id="SearchMoneyFlowResultSql">
SELECT fmf.id,
fmf.order_id,
fmf.order_sn,
fmf.train_num,
fmf.money_account_id,
fmf.before_balance,
fmf.delta,
fmf.after_balance,
fmf.money_change_category,
fmf.memo,
fmf.file_url,
fmf.creator_id,
fmf.modifier_id,
fmf.create_time,
fmf.modify_time,
fmf.deleted,
CASE fma.account_type
WHEN 'GeRen' THEN cc.customer_name
WHEN 'QiYe' THEN co.org_name
ELSE NULL
END AS account_name,
fma.account_type
FROM fin_money_flow fmf
LEFT JOIN fin_money_account fma ON fma.id = fmf.money_account_id
LEFT JOIN cst_customer cc ON cc.id = fma.customer_id
LEFT JOIN cst_org co ON co.id = fma.org_id
</sql>
<select id="paging" resultMap="BaseResultMap">
<include refid="SearchMoneyFlowResultSql"/>
<if test="ew.customSqlSegment != null and ew.customSqlSegment != ''">
${ew.customSqlSegment}
</if>
</select>
</mapper>