首页统计接口以及导入表格中数据格式错误的问题

This commit is contained in:
2026-02-03 15:55:03 +08:00
parent c880e5f48a
commit 4582778bb3
10 changed files with 435 additions and 11 deletions

View File

@@ -1122,6 +1122,7 @@ public class ExcelUtil<T>
/**
* 添加单元格
*/
private final Map<String, CellStyle> bdStyleCache = new HashMap<>();
public Cell addCell(Excel attr, Row row, T vo, Field field, int column)
{
Cell cell = null;
@@ -1152,6 +1153,8 @@ public class ExcelUtil<T>
// =========================
// ✅ BigDecimal按“实际几位显示几位”去掉末尾0核心修改点
// =========================
// 类里加一个缓存(避免重复创建 style
if (value instanceof BigDecimal)
{
BigDecimal bd = (BigDecimal) value;
@@ -1161,16 +1164,51 @@ public class ExcelUtil<T>
bd = bd.setScale(attr.scale(), attr.roundingMode());
}
// 去掉末尾01.000000 -> 11.2300 -> 1.23
String text = bd.stripTrailingZeros().toPlainString();
// 有后缀只能当文本
if (StringUtils.isNotEmpty(attr.suffix()))
{
String text = bd.stripTrailingZeros().toPlainString();
cell.setCellValue(StringUtils.isEmpty(text) ? attr.defaultValue() : text + attr.suffix());
addStatisticsData(column, text, attr);
return cell;
}
// 强制按字符串写入,避免 Excel 数值格式导致显示成固定小数位
cell.setCellType(CellType.STRING);
cell.setCellValue(StringUtils.isEmpty(text) ? attr.defaultValue() : text + attr.suffix());
BigDecimal stripped = bd.stripTrailingZeros();
String text = stripped.toPlainString();
// ✅ 判断是否整数strip 后 scale <= 0 就是整数
boolean isInt = stripped.scale() <= 0;
// ✅ 写入数值:整数写 long小数写 double避免显示 1.
if (isInt)
{
try {
cell.setCellValue(stripped.longValueExact());
} catch (ArithmeticException ex) {
// 超大整数兜底
cell.setCellValue(stripped.doubleValue());
}
}
else
{
cell.setCellValue(stripped.doubleValue());
}
// ✅ clone + cache 样式,整数用 0小数用 0.############
String fmt = isInt ? "0" : "0.############";
String styleKey = "bd_" + fmt + "_" + attr.align() + "_" + attr.color() + "_" + attr.backgroundColor() + "_" + attr.wrapText();
CellStyle numStyle = bdStyleCache.get(styleKey);
if (numStyle == null)
{
numStyle = wb.createCellStyle();
numStyle.cloneStyleFrom(cell.getCellStyle());
numStyle.setDataFormat(wb.createDataFormat().getFormat(fmt));
bdStyleCache.put(styleKey, numStyle);
}
cell.setCellStyle(numStyle);
// 统计
addStatisticsData(column, text, attr);
return cell;
}

View File

@@ -1,13 +1,21 @@
package com.zg.project.wisdom.controller;
import com.zg.common.utils.poi.ExcelUtil;
import com.zg.framework.web.domain.AjaxResult;
import com.zg.project.wisdom.domain.dto.HomeStatQueryDTO;
import com.zg.project.wisdom.domain.vo.StockAgeExportVO;
import com.zg.project.wisdom.domain.vo.StockAgeStatVO;
import com.zg.project.wisdom.domain.vo.TodoStatVO;
import com.zg.project.wisdom.domain.vo.WarehouseSlotStatVO;
import com.zg.project.wisdom.service.RkStatisticsService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@Api(tags = "库存统计")
@RestController
@RequestMapping("/stat")
@@ -25,5 +33,39 @@ public class RkStatisticsController {
public AjaxResult home(@RequestBody(required = false) HomeStatQueryDTO query) {
return AjaxResult.success(rkStatisticsService.getHomeStatistics(query));
}
/**
* 首页统计,库龄统计
* */
@GetMapping("/stockAge/stat")
public AjaxResult stockAgeStat() {
List<StockAgeStatVO> stockAgeStatVOList = rkStatisticsService.selectStockAgeStat();
return AjaxResult.success(stockAgeStatVOList);
}
/**
* 首页统计,待办事项
* */
@GetMapping("/todo")
public AjaxResult todoStat() {
List<TodoStatVO> list = rkStatisticsService.selectTodoStat();
return AjaxResult.success(list);
}
/**
* 小仓库位使用情况统计(总库位/已使用/未使用)
*/
@GetMapping("/warehouse/slot")
public AjaxResult warehouseSlotStat() {
List<WarehouseSlotStatVO> list = rkStatisticsService.selectWarehouseSlotStat();
return AjaxResult.success(list);
}
/**
* 库龄>=30天明细导出分组>30天 / >60天
*/
@ApiOperation("库龄>=30天明细导出")
@PostMapping("/stockAge/export30")
public void exportStockAge30(HttpServletResponse response) {
List<StockAgeExportVO> list = rkStatisticsService.selectStockAgeExport30();
ExcelUtil<StockAgeExportVO> util = new ExcelUtil<>(StockAgeExportVO.class);
util.exportExcel(response, list, "库龄>=30天明细");
}
}

View File

@@ -0,0 +1,48 @@
package com.zg.project.wisdom.domain.vo;
import com.zg.framework.aspectj.lang.annotation.Excel;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
@Data
public class StockAgeExportVO {
@Excel(name = "库龄分组")
private String stockAgeGroup;
@Excel(name = "ID")
private Long id;
@Excel(name = "项目号")
private String projectNo;
@Excel(name = "项目描述")
private String projectDesc;
@Excel(name = "入库时间", dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date inTime;
@Excel(name = "库龄(天)")
private Integer stockAgeDays;
@Excel(name = "合同单价", cellType = Excel.ColumnType.NUMERIC, scale = 3, roundingMode = BigDecimal.ROUND_HALF_UP)
private BigDecimal unitPrice;
@Excel(name = "数量", cellType = Excel.ColumnType.NUMERIC, scale = 3, roundingMode = BigDecimal.ROUND_HALF_UP)
private BigDecimal qty;
@Excel(name = "金额", cellType = Excel.ColumnType.NUMERIC, scale = 3, roundingMode = BigDecimal.ROUND_HALF_UP)
private BigDecimal lineAmount;
@Excel(name = "库位")
private String slotCode;
@Excel(name = "托盘")
private String trayCode;
@Excel(name = "单据号")
private String billNo;
}

View File

@@ -0,0 +1,9 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
@Data
public class StockAgeStatVO {
private String name;
private Integer value;
}

View File

@@ -0,0 +1,22 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class TodoStatVO {
/** 类型:应到未到[部分未到] / 应到未到[全部未到] / 应出未出[全部未出] */
private String type;
/** 项目号xm_no 去重) */
private Integer projectCnt;
/** 总数量(保留 3 位小数) */
private BigDecimal totalQty;
/** 总金额(保留 3 位小数) */
private BigDecimal totalAmt;
}

View File

@@ -0,0 +1,21 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
@Data
public class WarehouseSlotStatVO {
/** 小仓编码pcde_detail.warehouse_code */
private String cangkuCode;
/** 小仓名称pcde_detail.warehouse_name */
private String cangkuName;
/** 总库位 */
private Integer totalSlot;
/** 已使用库位 */
private Integer usedSlot;
/** 未使用库位 */
private Integer unusedSlot;
}

View File

@@ -1,7 +1,6 @@
package com.zg.project.wisdom.mapper;
import com.zg.project.wisdom.domain.vo.HomeKpiVO;
import com.zg.project.wisdom.domain.vo.StockStatisticGroupVO;
import com.zg.project.wisdom.domain.vo.*;
import io.lettuce.core.dynamic.annotation.Param;
import org.apache.ibatis.annotations.Mapper;
@@ -32,4 +31,23 @@ public interface RkStatisticsMapper {
/** 首页 KPI */
HomeKpiVO statHomeKpi();
/**
* 首页库龄统计接口
* */
List<StockAgeStatVO> selectStockAgeStat();
/**
* 首页待办事项接口
* */
List<TodoStatVO> selectTodoStat();
/**
* 小仓库位使用情况统计(总库位/已使用/未使用)
*/
List<WarehouseSlotStatVO> selectWarehouseSlotStat();
/**
* 库龄>=30天明细用于导出
*/
List<StockAgeExportVO> selectStockAgeExport30();
}

View File

@@ -1,7 +1,9 @@
package com.zg.project.wisdom.service;
import com.zg.project.wisdom.domain.dto.HomeStatQueryDTO;
import com.zg.project.wisdom.domain.vo.HomeStatVO;
import com.zg.project.wisdom.domain.vo.*;
import java.util.List;
/** 首页统计服务 */
public interface RkStatisticsService {
@@ -12,4 +14,21 @@ public interface RkStatisticsService {
*/
HomeStatVO getHomeStatistics(HomeStatQueryDTO query);
/**
* 首页库龄统计接口
* */
List<StockAgeStatVO> selectStockAgeStat();
/**
* 首页待办事项接口
* */
List<TodoStatVO> selectTodoStat();
/**
* 小仓库位使用情况统计
*/
List<WarehouseSlotStatVO> selectWarehouseSlotStat();
/**
* 库龄>=30天明细用于导出
*/
List<StockAgeExportVO> selectStockAgeExport30();
}

View File

@@ -1,13 +1,14 @@
package com.zg.project.wisdom.service.impl;
import com.zg.project.wisdom.domain.dto.HomeStatQueryDTO;
import com.zg.project.wisdom.domain.vo.HomeStatVO;
import com.zg.project.wisdom.domain.vo.*;
import com.zg.project.wisdom.mapper.RkStatisticsMapper;
import com.zg.project.wisdom.service.RkStatisticsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
@Service
public class RkStatisticsServiceImpl implements RkStatisticsService {
@@ -36,5 +37,33 @@ public class RkStatisticsServiceImpl implements RkStatisticsService {
return vo;
}
/**
* 首页库龄统计接口
* */
@Override
public List<StockAgeStatVO> selectStockAgeStat() {
return rkStatisticsMapper.selectStockAgeStat();
}
/**
* 首页待办事项接口
* */
@Override
public List<TodoStatVO> selectTodoStat() {
return rkStatisticsMapper.selectTodoStat();
}
/**
* 首页统计仓库使用情况
* */
@Override
public List<WarehouseSlotStatVO> selectWarehouseSlotStat() {
return rkStatisticsMapper.selectWarehouseSlotStat();
}
/**
* 库龄>=30天明细用于导出
*/
@Override
public List<StockAgeExportVO> selectStockAgeExport30() {
return rkStatisticsMapper.selectStockAgeExport30();
}
}

View File

@@ -111,6 +111,184 @@
WHERE ri.exec_status = 1
AND ri.is_delete = 0
</select>
<select id="selectStockAgeStat" resultType="com.zg.project.wisdom.domain.vo.StockAgeStatVO">
<![CDATA[
SELECT '10-20天' AS name, COUNT(*) AS value
FROM rk_info ri
WHERE ri.is_delete = '0'
AND ri.is_chuku = '0'
AND ri.operation_time IS NOT NULL
AND TIMESTAMPDIFF(DAY, ri.operation_time, NOW()) > 10
AND TIMESTAMPDIFF(DAY, ri.operation_time, NOW()) <= 20
UNION ALL
SELECT '20-30天' AS name, COUNT(*) AS value
FROM rk_info ri
WHERE ri.is_delete = '0'
AND ri.is_chuku = '0'
AND ri.operation_time IS NOT NULL
AND TIMESTAMPDIFF(DAY, ri.operation_time, NOW()) > 20
AND TIMESTAMPDIFF(DAY, ri.operation_time, NOW()) <= 30
UNION ALL
SELECT '30天以上' AS name, COUNT(*) AS value
FROM rk_info ri
WHERE ri.is_delete = '0'
AND ri.is_chuku = '0'
AND ri.operation_time IS NOT NULL
AND TIMESTAMPDIFF(DAY, ri.operation_time, NOW()) > 30
UNION ALL
SELECT '30-60天' AS name, COUNT(*) AS value
FROM rk_info ri
WHERE ri.is_delete = '0'
AND ri.is_chuku = '0'
AND ri.operation_time IS NOT NULL
AND TIMESTAMPDIFF(DAY, ri.operation_time, NOW()) > 30
AND TIMESTAMPDIFF(DAY, ri.operation_time, NOW()) <= 60
UNION ALL
SELECT '60天以上' AS name, COUNT(*) AS value
FROM rk_info ri
WHERE ri.is_delete = '0'
AND ri.is_chuku = '0'
AND ri.operation_time IS NOT NULL
AND TIMESTAMPDIFF(DAY, ri.operation_time, NOW()) > 60
]]>
</select>
<select id="selectTodoStat" resultType="com.zg.project.wisdom.domain.vo.TodoStatVO">
<![CDATA[
SELECT
'应到未到[部分未到]' AS type,
COUNT(DISTINCT g.xm_no) AS projectCnt,
ROUND(
COALESCE(
SUM(GREATEST(g.jh_qty - g.real_qty, 0)),
0
),
3
) AS totalQty,
ROUND(
COALESCE(
SUM(GREATEST(g.jh_qty - g.real_qty, 0) * g.ht_dj),
0
),
3
) AS totalAmt
FROM gys_jh g
WHERE g.is_delete = '0'
AND g.status = '2'
UNION ALL
SELECT
'应到未到[全部未到]' AS type,
COUNT(DISTINCT g.xm_no) AS projectCnt,
ROUND(
COALESCE(
SUM(g.jh_qty),
0
),
3
) AS totalQty,
ROUND(
COALESCE(
SUM(g.jh_qty * g.ht_dj),
0
),
3
) AS totalAmt
FROM gys_jh g
WHERE g.is_delete = '0'
AND g.status = '0'
UNION ALL
SELECT
'应出未出[全部未出]' AS type,
COUNT(DISTINCT g.xm_no) AS projectCnt,
ROUND(
COALESCE(
SUM(g.real_qty),
0
),
3
) AS totalQty,
ROUND(
COALESCE(
SUM(g.real_qty * g.ht_dj),
0
),
3
) AS totalAmt
FROM gys_jh g
WHERE g.is_delete = '0'
AND g.status = '1';
]]>
</select>
<select id="selectWarehouseSlotStat"
resultType="com.zg.project.wisdom.domain.vo.WarehouseSlotStatVO">
<![CDATA[
SELECT
p.warehouse_code AS cangkuCode,
p.warehouse_name AS cangkuName,
COUNT(DISTINCT p.pcode) AS totalSlot,
COUNT(DISTINCT CASE WHEN r.id IS NOT NULL THEN p.pcode END) AS usedSlot,
COUNT(DISTINCT p.pcode)
- COUNT(DISTINCT CASE WHEN r.id IS NOT NULL THEN p.pcode END) AS unusedSlot
FROM pcde_detail p
LEFT JOIN rk_info r
ON r.pcode = p.pcode
AND r.cangku = p.warehouse_code
AND r.is_delete = '0'
AND r.is_chuku = '0'
WHERE p.is_delete = '0'
AND p.warehouse_name IS NOT NULL
AND TRIM(p.warehouse_name) <> ''
GROUP BY p.warehouse_code, p.warehouse_name
ORDER BY p.warehouse_code
]]>
</select>
<select id="selectStockAgeExport30"
resultType="com.zg.project.wisdom.domain.vo.StockAgeExportVO">
<![CDATA[
SELECT
CASE
WHEN TIMESTAMPDIFF(DAY, ri.operation_time, NOW()) >= 60 THEN '>60天'
ELSE '>30天'
END AS stockAgeGroup,
ri.id AS id,
ri.xm_no AS projectNo,
ri.xm_ms AS projectDesc,
ri.operation_time AS inTime,
TIMESTAMPDIFF(DAY, ri.operation_time, NOW()) AS stockAgeDays,
ri.ht_dj AS unitPrice,
ri.real_qty AS qty,
ROUND(COALESCE(ri.ht_dj, 0) * COALESCE(ri.real_qty, 0), 3) AS lineAmount,
ri.pcode AS slotCode,
ri.tray_code AS trayCode,
ri.bill_no AS billNo
FROM rk_info ri
WHERE ri.is_delete = '0'
AND ri.is_chuku = '0'
AND ri.operation_time IS NOT NULL
AND TIMESTAMPDIFF(DAY, ri.operation_time, NOW()) >= 30
ORDER BY
stockAgeGroup DESC,
stockAgeDays DESC,
ri.operation_time ASC
]]>
</select>
</mapper>