Compare commits

...

33 Commits

Author SHA1 Message Date
f27ff89614 Merge remote-tracking branch 'origin/master' 2026-03-12 15:47:03 +08:00
87de67a188 预出库扣减库存逻辑 2026-03-12 15:46:52 +08:00
676ab34c1b 入库修改入库类型 2026-03-12 14:56:28 +08:00
ef0c52b6df Merge remote-tracking branch 'origin/master' 2026-03-10 10:52:51 +08:00
424aac6748 盘点部分数据错误问题 2026-03-10 10:52:31 +08:00
cd0f7b371d sql逻辑修改 2026-03-10 10:42:58 +08:00
8ceaeb03ef sql逻辑修改 2026-03-06 16:11:26 +08:00
8323ae3cd9 出库 2026-03-06 15:38:31 +08:00
15a4053cab 出库 2026-03-06 15:21:45 +08:00
a892cdd03d 出库 2026-03-06 08:42:46 +08:00
ac45b0c79b 供应计划更新单位 2026-03-05 15:38:03 +08:00
0c23b61cbb 追加入库逻辑修改 2026-03-05 15:37:28 +08:00
dbc2bada83 盘点内容以及单据号规则 2026-02-28 09:42:05 +08:00
8f802a8738 库位使用统计sql优化 2026-02-28 08:51:23 +08:00
d29b336252 库存表新增字段以及存值 2026-02-25 16:53:28 +08:00
2183528894 库存表新增字段以及存值 2026-02-25 15:25:48 +08:00
367dd00008 库存表新增字段以及存值 2026-02-25 11:05:38 +08:00
54c9486a88 Merge remote-tracking branch 'origin/master' 2026-02-13 10:32:12 +08:00
56e4514ee8 导出文件格式问题以及查询出入库问题 2026-02-13 10:32:00 +08:00
b84fd1e046 Merge remote-tracking branch 'origin/master' 2026-02-13 09:47:21 +08:00
7b4965ff7a 配送系统相关逻辑修改 2026-02-13 09:47:05 +08:00
219e23c194 配送系统同步修改的内容 2026-02-06 14:49:42 +08:00
8fa2c1c52d 清楚测试逻辑代码 2026-02-05 16:58:14 +08:00
27a105b4f6 新增库存列表接口,根据订单编号排序
追加入库接口逻辑修改
2026-02-05 16:25:28 +08:00
6ffafc81f4 Merge remote-tracking branch 'origin/master' 2026-02-04 16:32:48 +08:00
d141676788 首页统计接口修改
配送修改状态修改
2026-02-04 16:32:37 +08:00
c362acece6 配送系统同步修改的内容 2026-02-04 16:04:28 +08:00
4582778bb3 首页统计接口以及导入表格中数据格式错误的问题 2026-02-03 15:55:03 +08:00
c880e5f48a 配送系统接口修改
添加总金额字段
施工队导入逻辑修改
2026-02-03 11:25:35 +08:00
009901d5ee 添加bigdecimal序列号
添加移库单向列表
2026-02-02 18:10:54 +08:00
e7528a6fde 出入库统计接口 2026-02-02 10:04:14 +08:00
5812fce297 出入库统计接口 2026-02-02 09:00:39 +08:00
ae2f7a897e 首页接口重构 2026-02-02 08:06:58 +08:00
90 changed files with 2990 additions and 2247 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;
}
@@ -1206,6 +1244,25 @@ public class ExcelUtil<T>
}
else
{
if (value instanceof String)
{
String str = (String) value;
if ("sapNo".equals(field.getName() )
&& str.matches("^\\d{1,15}$"))
{
cell.setCellValue(Double.parseDouble(str));
// 设置为数字格式
CellStyle numStyle = wb.createCellStyle();
numStyle.cloneStyleFrom(cell.getCellStyle());
numStyle.setDataFormat(wb.createDataFormat().getFormat("0"));
cell.setCellStyle(numStyle);
addStatisticsData(column, str, attr);
return cell;
}
}
// 设置列类型
setCellVo(value, attr, cell);
}

View File

@@ -122,6 +122,7 @@ public class SecurityConfig
"/AutoInventory/**",
"/ws/**",
"/photo/**",
"/wisdom/bill/delivery/list",
"/wisdom/record/**",
"/wisdom/stock/**",
"/system/media/**",

View File

@@ -38,7 +38,6 @@ public class PcdeDetailController extends BaseController
public TableDataInfo list(PcdeDetail pcdeDetail)
{
startPage();
System.out.printf("111111");
List<PcdeDetail> list = pcdeDetailService.selectPcdeDetailList(pcdeDetail);
return getDataTable(list);
}

View File

@@ -25,7 +25,7 @@ public class ConstructionTeam extends BaseEntity
private String teamName;
/** 施工队编号 */
@Excel(name = "施工队编号")
// @Excel(name = "施工队编号")
private String teamCode;
/** 创建人 */

View File

@@ -68,6 +68,14 @@ public interface PcdeDetailMapper
*/
PcdeDetail selectByPcode(String pcode);
/**
* 根据仓库编码和库位编码查询库位信息
* @param pcode
* @return
*/
PcdeDetail selectByPcodeAndWarehouse(@Param("pcode") String pcode,
@Param("warehouseCode") String warehouseCode);
/**
* 根据仓库编码查询库位编码
* @param pcode

View File

@@ -146,26 +146,17 @@ public class ConstructionTeamServiceImpl implements IConstructionTeamService
StringBuilder failureMsg = new StringBuilder();
Date now = new Date();
// ===== ① Excel 内去重 =====
Set<String> teamCodeSet = new HashSet<>();
// ===== ① Excel 内名称去重 =====
Set<String> teamNameSet = new HashSet<>();
for (ConstructionTeam team : teamList)
{
String teamCode = team.getTeamCode();
String teamName = team.getTeamName();
if (StringUtils.isBlank(teamCode) || StringUtils.isBlank(teamName))
if (StringUtils.isBlank(teamName))
{
failureNum++;
failureMsg.append("<br/>施工队名称/编号不能为空,已跳过。");
continue;
}
if (!teamCodeSet.add(teamCode))
{
failureNum++;
failureMsg.append("<br/>施工队编号重复Excel 内):").append(teamCode);
failureMsg.append("<br/>施工队名称不能为空,已跳过。");
continue;
}
@@ -177,28 +168,17 @@ public class ConstructionTeamServiceImpl implements IConstructionTeamService
}
}
// ===== ② 数据库内去重 =====
// ===== ② 数据库校验 + 自动生成编号 =====
for (ConstructionTeam team : teamList)
{
try
{
// 跳过前面校验失败的数据
if (StringUtils.isBlank(team.getTeamCode()) || StringUtils.isBlank(team.getTeamName()))
if (StringUtils.isBlank(team.getTeamName()))
{
continue;
}
// 按编号查
ConstructionTeam existByCode =
constructionTeamMapper.selectByTeamCode(team.getTeamCode());
if (existByCode != null)
{
failureNum++;
failureMsg.append("<br/>施工队编号已存在:").append(team.getTeamCode());
continue;
}
// 按名称查
// 按名称查重
ConstructionTeam existByName =
constructionTeamMapper.selectByTeamName(team.getTeamName());
if (existByName != null)
@@ -208,6 +188,14 @@ public class ConstructionTeamServiceImpl implements IConstructionTeamService
continue;
}
// ===== 自动生成唯一编号 =====
String code;
do {
code = generateTeamCode();
} while (constructionTeamMapper.selectByTeamCode(code) != null);
team.setTeamCode(code);
// ===== 新增 =====
team.setCreatedBy(operName);
team.setCreatedAt(now);
@@ -226,9 +214,7 @@ public class ConstructionTeamServiceImpl implements IConstructionTeamService
catch (Exception e)
{
failureNum++;
failureMsg.append("<br/>施工队编号")
.append(team.getTeamCode())
.append(",施工队名称:")
failureMsg.append("<br/>施工队名称")
.append(team.getTeamName())
.append(" 导入失败:")
.append(e.getMessage());
@@ -245,5 +231,4 @@ public class ConstructionTeamServiceImpl implements IConstructionTeamService
return "导入成功,共 " + successNum + " 条数据";
}
}

View File

@@ -68,28 +68,46 @@ public class AutoInventoryController extends BaseController {
/**
* 开始匹配
*/
// @PostMapping("/match")
// @ApiOperation("开始匹配")
// public AjaxResult match(@RequestBody QueryDTO dto) {
// // 停止盘点
// rfidService.stopScan(dto.getDeviceId());
//
// dto.setScanType(1);
//
// rkInfoService.matchWithStatus(dto);
//
// return AjaxResult.success();
// }
@PostMapping("/match")
@ApiOperation("开始匹配")
public AjaxResult match(@RequestBody QueryDTO dto) {
// 停止盘点
rfidService.stopScan(dto.getDeviceId());
dto.setScanType(1);
rkInfoService.matchWithStatus(dto);
return AjaxResult.success();
}
/**
* 极简盘点匹配(只按仓库+场景)
*/
@PostMapping("/matchPure")
@ApiOperation("极简盘点匹配")
public AjaxResult matchPure(@RequestBody QueryDTO dto) {
if (dto.getScanType() != null && dto.getScanType() == 1) {
rfidService.stopScan(dto.getDeviceId());
}
dto.setScanType(1);
rkInfoService.matchPure(dto);
return AjaxResult.success();
}
/**
* 匹配后图表统计
*/
// @PostMapping("/chart")
// @ApiOperation("匹配后图表统计")
// public AjaxResult chart(@RequestBody QueryDTO dto) {
// ChartDataVO vo = rkInfoService.matchWithAll(dto);
// return AjaxResult.success(vo);
// }
@PostMapping("/chart")
@ApiOperation("匹配后图表统计")
public AjaxResult chart(@RequestBody QueryDTO dto) {
ChartDataVO vo = rkInfoService.matchWithAll(dto);
return AjaxResult.success(vo);
}
/**

View File

@@ -1,20 +1,34 @@
package com.zg.project.inventory.AutoInventory.controller;
import com.github.pagehelper.PageHelper;
import com.zg.common.utils.StringUtils;
import com.zg.common.utils.poi.ExcelUtil;
import com.zg.framework.web.controller.BaseController;
import com.zg.framework.web.domain.AjaxResult;
import com.zg.framework.web.page.TableDataInfo;
import com.zg.project.inventory.AutoInventory.service.InventoryMatchScanService;
import com.zg.project.inventory.domain.dto.InventoryDTO;
import com.zg.project.inventory.domain.dto.MatchScanPageDTO;
import com.zg.project.inventory.domain.entity.InventoryMatchScan;
import com.zg.project.inventory.domain.vo.InventoryMatchScanVO;
import com.zg.project.inventory.domain.vo.RkInfoMatchVO;
import com.zg.project.inventory.domain.vo.RkInventoryExportVO;
import com.zg.project.wisdom.domain.RkInfo;
import com.zg.project.wisdom.domain.RkRecord;
import com.zg.project.wisdom.domain.vo.StockStatisticVO;
import com.zg.project.wisdom.mapper.RkInfoMapper;
import com.zg.project.wisdom.service.IRkInfoService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static com.zg.common.utils.PageUtils.startPage;
@@ -25,7 +39,10 @@ public class InventoryMatchScanController extends BaseController {
@Autowired
private InventoryMatchScanService inventoryMatchScanService;
@Autowired
private IRkInfoService rkInfoService;
@Autowired
private RkInfoMapper rkInfoMapper;
/**
* 分页查询盘点结果
* @param matchScan
@@ -40,9 +57,63 @@ public class InventoryMatchScanController extends BaseController {
@PostMapping("/export")
public void export(HttpServletResponse response, InventoryMatchScan criteria) {
List<InventoryMatchScan> list = inventoryMatchScanService.selectInventoryMatchScanList(criteria);
ExcelUtil<InventoryMatchScan> util = new ExcelUtil<>(InventoryMatchScan.class);
util.exportExcel(response, list, "盘点匹配结果");
// 1⃣ 查盘点结果
List<InventoryMatchScan> list =
inventoryMatchScanService.selectInventoryMatchScanList(criteria);
if (list == null || list.isEmpty()) {
new ExcelUtil<>(RkInventoryExportVO.class)
.exportExcel(response, new ArrayList<>(), "盘点匹配结果");
return;
}
// 2⃣ 收集所有 pcode去重
Set<String> pcodes = list.stream()
.map(InventoryMatchScan::getPcode)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toSet());
// 3⃣ 一次性查所有货物
List<RkInfo> allInfos =
rkInfoService.listRkInfoByPcodes(new ArrayList<>(pcodes));
// 4⃣ 按 pcode 分组
Map<String, List<RkInfo>> infoMap = allInfos.stream()
.collect(Collectors.groupingBy(RkInfo::getPcode));
// 5⃣ 组装
List<RkInventoryExportVO> exportList = new ArrayList<>();
for (InventoryMatchScan scan : list) {
List<RkInfo> rkInfos = infoMap.get(scan.getPcode());
if (rkInfos == null || rkInfos.isEmpty()) {
RkInventoryExportVO vo = new RkInventoryExportVO();
BeanUtils.copyProperties(scan, vo);
exportList.add(vo);
} else {
for (RkInfo info : rkInfos) {
RkInventoryExportVO vo = new RkInventoryExportVO();
BeanUtils.copyProperties(info, vo);
BeanUtils.copyProperties(scan, vo);
exportList.add(vo);
}
}
}
// 6⃣ 导出
ExcelUtil<RkInventoryExportVO> util =
new ExcelUtil<>(RkInventoryExportVO.class);
util.exportExcel(response, exportList, "盘点匹配结果");
}
/**
@@ -50,15 +121,15 @@ public class InventoryMatchScanController extends BaseController {
* @param matchScan
* @return
*/
// @GetMapping("/countList")
// public TableDataInfo countList(InventoryMatchScan matchScan) {
// // 开启分页
// startPage();
// // 调用 service 层查询数据
// List<RkInfoMatchVO> list = inventoryMatchScanService.selectMatchScanCountList(matchScan);
// // 返回分页后的结果
// return getDataTable(list);
// }
@GetMapping("/countList")
public TableDataInfo countList(InventoryMatchScan matchScan) {
// 开启分页
startPage();
// 调用 service 层查询数据
List<RkInfoMatchVO> list = inventoryMatchScanService.selectMatchScanCountList(matchScan);
// 返回分页后的结果
return getDataTable(list);
}
/**
@@ -77,4 +148,51 @@ public class InventoryMatchScanController extends BaseController {
return AjaxResult.success(getDataTable(list));
}
@PostMapping("/inventoryList")
public TableDataInfo inventoryList(@RequestBody InventoryDTO query) {
PageHelper.startPage(query.getPageNum(), query.getPageSize());
String warehouseCode = query.getWarehouseCode();
String sceneId = query.getSceneId();
List<RkInfo> normalAll =
rkInfoMapper.getByWarehouseAndScene(warehouseCode, sceneId);
return getDataTable(normalAll);
}
/**
* 出入库统计,返回总数
*/
@PostMapping("/statistics")
public AjaxResult statistics(@RequestBody InventoryDTO query) {
RkInfo rkInfo = new RkInfo();
rkInfo.setScene(query.getSceneId());
rkInfo.setCangku(query.getWarehouseCode());
StockStatisticVO stockStatistic = rkInfoMapper.selectStockStatisticByCondition(rkInfo);
return AjaxResult.success(stockStatistic);
}
/**
* 条目数
*/
@PostMapping("/countByWarehouseAndScene")
public AjaxResult countByWarehouseAndScene(@RequestBody InventoryDTO query)
{
String warehouseCode = query.getWarehouseCode();
String sceneId = query.getSceneId();
int count = rkInfoService.countByWarehouseAndScene(warehouseCode, sceneId);
return AjaxResult.success(count);
}
@PostMapping("/exportInventory")
public void exportInventory(HttpServletResponse response, InventoryDTO query)
{
String warehouseCode = query.getWarehouseCode();
String sceneId = query.getSceneId();
// 查询数据(不分页)
List<RkInfo> list =
rkInfoMapper.getByWarehouseAndScene(warehouseCode, sceneId);
ExcelUtil<RkInfo> util = new ExcelUtil<>(RkInfo.class);
util.exportExcel(response, list, "盘点数据");
}
}

View File

@@ -33,4 +33,11 @@ public interface InventoryMatchScanMapper {
List<RkInfoMatchVO> selectOnlyFromMatchScan(InventoryMatchScan param);
List<RkInfoMatchVO> selectJoinRkInfo(InventoryMatchScan param);
/**
* 查询指定仓库下未被扫描的库位
* @param param
* @return
*/
List<RkInfoMatchVO> selectUnscanFromMatchScan(InventoryMatchScan param);
}

View File

@@ -19,5 +19,6 @@ public interface InventoryMatchScanService {
List<InventoryMatchScan> selectInventoryMatchScanList(InventoryMatchScan matchScan);
// List<RkInfoMatchVO> selectMatchScanCountList(InventoryMatchScan matchScan);
List<RkInfoMatchVO> selectMatchScanCountList(InventoryMatchScan matchScan);
}

View File

@@ -56,22 +56,26 @@ public class InventoryMatchScanServiceImpl implements InventoryMatchScanService
return mapper.selectInventoryMatchScanList(matchScan);
}
// @Override
// public List<RkInfoMatchVO> selectMatchScanCountList(InventoryMatchScan param) {
// // 状态=2只查扫描表中的数据
// if ("2".equals(param.getStatus())) {
// return mapper.selectOnlyFromMatchScan(param);
// }
// // 状态=1查询指定仓库下未被扫描的库位
// else if ("1".equals(param.getStatus())) {
// // ✅ 根据任务ID取 sceneId再按 scene 查询“未被扫描”的库位
// String sceneId = taskMapper.getSceneByTaskId(param.getTaskId());
// return rkInfoMapper.getUnscannedPcodeByScene(sceneId, param.getTaskId());
// }
// // 其它情况:返回扫描表和库存表的关联数据
// else {
// return mapper.selectJoinRkInfo(param);
// }
// }
@Override
public List<RkInfoMatchVO> selectMatchScanCountList(InventoryMatchScan param) {
// ========== 1. 已扫描异常 ==========
if ("2".equals(param.getStatus())) {
return mapper.selectOnlyFromMatchScan(param);
}
// ========== 2. ❗未扫到(你要的逻辑)==========
else if ("1".equals(param.getStatus())) {
// 👉 只查扫描表!没有就是空!
return mapper.selectUnscanFromMatchScan(param);
}
// ========== 3. 正常匹配 ==========
else {
return mapper.selectJoinRkInfo(param);
}
}
}

View File

@@ -16,18 +16,18 @@ public class HdInventoryController {
@Autowired
private IRkInfoService rkInfoService;
// @ApiModelProperty("开始匹配")
// @PostMapping("/match")
// public AjaxResult match(@RequestBody QueryDTO dto) {
//
// dto.setScanType(0);
//
//// dto.setDeviceId("0");
//
// rkInfoService.matchWithStatus(dto);
//
// return AjaxResult.success();
@ApiModelProperty("开始匹配")
@PostMapping("/match")
public AjaxResult match(@RequestBody QueryDTO dto) {
// }
dto.setScanType(0);
// dto.setDeviceId("0");
rkInfoService.matchPure(dto);
return AjaxResult.success();
}
}

View File

@@ -22,14 +22,14 @@ public class StatisticsController extends BaseController {
* @param taskId
* @return
*/
// @GetMapping("/count")
// public AjaxResult conuntGetByTaskId(@RequestParam("taskId") String taskId)
// {
//
// String warehouse = taskService.getWhByTaskId(taskId);
//
// int count = rkInfoService.countGetByWh(warehouse);
//
// return AjaxResult.success(count);
// }
@GetMapping("/count")
public AjaxResult conuntGetByTaskId(@RequestParam("taskId") String taskId)
{
String warehouse = taskService.getWhByTaskId(taskId);
int count = rkInfoService.countGetByWh(warehouse);
return AjaxResult.success(count);
}
}

View File

@@ -94,4 +94,5 @@ public interface InventoryTaskMapper
* @return
*/
String selectSceneIdById(String taskId);
}

View File

@@ -0,0 +1,14 @@
package com.zg.project.inventory.domain.dto;
import lombok.Data;
@Data
public class InventoryDTO {
private String warehouseCode;
private String sceneId;
/** 当前页 */
private Integer pageNum;
/** 每页条数 */
private Integer pageSize;
}

View File

@@ -16,7 +16,7 @@ public class InventoryMatchScan extends BaseEntity {
private static final long serialVersionUID = 1L;
/** 主键ID */
@Excel(name = "主键ID")
// @Excel(name = "主键ID")
private Long id;
/** 设备ID */
@@ -28,7 +28,7 @@ public class InventoryMatchScan extends BaseEntity {
private String taskId;
/** 扫描标签ID货品码ID */
@Excel(name = "扫描标签ID")
@Excel(name = "库位号")
private String pcode;
/** 系统入库时间 */
@@ -44,7 +44,7 @@ public class InventoryMatchScan extends BaseEntity {
private String status;
/** 匹配到的真实货品码 */
@Excel(name = "库位号")
// @Excel(name = "库位号")
private String rkPcode;
@Excel(name = "盘点任务名称")
@@ -115,6 +115,13 @@ public class InventoryMatchScan extends BaseEntity {
public void setRkPcode(String rkPcode) {
this.rkPcode = rkPcode;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
@Override
@@ -128,6 +135,7 @@ public class InventoryMatchScan extends BaseEntity {
.append("scanType", getScanType())
.append("status", getStatus())
.append("rkPcode", getRkPcode())
.append("taskName", getTaskName())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())

View File

@@ -0,0 +1,129 @@
package com.zg.project.inventory.domain.vo;
import com.zg.framework.aspectj.lang.annotation.Excel;
import com.zg.project.wisdom.domain.RkInfo;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
@Data
public class RkInventoryExportVO extends RkInfo {
private static final long serialVersionUID = 1L;
/** 主键ID */
@Excel(name = "主键ID")
private Long id;
/** 设备ID */
@Excel(name = "设备ID")
private String deviceId;
/** 盘点任务ID */
@Excel(name = "盘点任务ID")
private String taskId;
/** 扫描标签ID货品码ID */
@Excel(name = "库位号")
private String pcode;
/** 系统入库时间 */
@Excel(name = "系统入库时间")
private String tme;
/** 盘点类型0=手动盘点1=自动盘点) */
@Excel(name = "盘点类型", readConverterExp = "0=手动盘点,1=自动盘点")
private Integer scanType;
/** 匹配状态0=正常, 1=未扫到, 2=误扫) */
@Excel(name = "匹配状态", readConverterExp = "0=正常,1=未扫到,2=误扫")
private String status;
/** 匹配到的真实货品码 */
@Excel(name = "库位号")
private String rkPcode;
@Excel(name = "盘点任务名称")
private String taskName;
// ---------- Getter/Setter ----------
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public String getPcode() {
return pcode;
}
public void setPcode(String pcode) {
this.pcode = pcode;
}
public String getTme() {
return tme;
}
public void setTme(String tme) {
this.tme = tme;
}
public Integer getScanType() {
return scanType;
}
public void setScanType(Integer scanType) {
this.scanType = scanType;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getRkPcode() {
return rkPcode;
}
public void setRkPcode(String rkPcode) {
this.rkPcode = rkPcode;
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("deviceId", getDeviceId())
.append("taskId", getTaskId())
.append("pcode", getPcode())
.append("tme", getTme())
.append("scanType", getScanType())
.append("status", getStatus())
.append("rkPcode", getRkPcode())
.toString();
}
}

View File

@@ -0,0 +1,22 @@
package com.zg.project.wisdom.config;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.math.BigDecimal;
public class BigDecimalSerializer extends JsonSerializer<BigDecimal> {
@Override
public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
if (value == null) {
gen.writeNull();
} else {
// 保持数字类型不带引号不补0、不截断
gen.writeNumber(value.stripTrailingZeros().toPlainString());
}
}
}

View File

@@ -6,6 +6,7 @@ import javax.servlet.http.HttpServletResponse;
import com.github.pagehelper.PageHelper;
import com.zg.project.wisdom.domain.dto.BorrowReturnDTO;
import com.zg.project.wisdom.domain.dto.RkBillCreateDTO;
import com.zg.project.wisdom.domain.vo.DeliveryBillVO;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
@@ -49,6 +50,7 @@ public class RkBillController extends BaseController
List<RkBill> list = rkBillService.selectRkBillList(rkBill);
return getDataTable(list);
}
/**
@@ -136,4 +138,20 @@ public class RkBillController extends BaseController
return toAjax(rkBillService.returnBorrow(dto));
}
/**
* 配送出库单据查询
* @param
* @return
*/
@GetMapping("/delivery/list")
public AjaxResult listDelivery(RkBill query) {
query.setIsDelivery("1");
List<DeliveryBillVO> list = rkBillService.selectDeliveryBillList(query);
return AjaxResult.success(list);
}
}

View File

@@ -48,6 +48,18 @@ public class RkInfoController extends BaseController
return getDataTable(list);
}
/**
* 查询库存单据明细列表(按订单编号排序)
*/
@PreAuthorize("@ss.hasPermi('wisdom:stock:list')")
@GetMapping("/listByBillNo")
public TableDataInfo listByBillNo(RkInfo rkInfo)
{
startPage();
List<RkInfo> list = rkInfoService.selectRkInfoListOrderByBillNo(rkInfo);
return getDataTable(list);
}
/**
* 导出库存单据明细列表
*/
@@ -113,4 +125,15 @@ public class RkInfoController extends BaseController
return AjaxResult.success(stockStatistic);
}
/**
*
* @param pcode
* @return
*/
@GetMapping("/pcode/{pcode}")
public AjaxResult listRkInfoByPcode(@PathVariable String pcode) {
List<RkInfo> rows = rkInfoService.listRkInfoByPcode(pcode);
return AjaxResult.success(rows);
}
}

View File

@@ -2,6 +2,10 @@ package com.zg.project.wisdom.controller;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import com.zg.project.wisdom.domain.RkInfo;
import com.zg.project.wisdom.domain.dto.RkDeliveryUpdateDTO;
import com.zg.project.wisdom.domain.vo.StockStatisticVO;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
@@ -163,11 +167,29 @@ public class RkRecordController extends BaseController
}
/**
* 出入库统计(同时返回)
* 出入库统计,返回总数
*/
@PostMapping("/statistic")
public AjaxResult statistic(@RequestBody RkRecord query) {
return AjaxResult.success(rkRecordService.getRecordStatistic(query));
@PostMapping("/statistics")
public AjaxResult statistics(@RequestBody RkRecord query) {
StockStatisticVO stockStatistic = rkRecordService.selectRecordStatisticByCondition(query);
return AjaxResult.success(stockStatistic);
}
/**
* 更新配送状态
*/
@PostMapping("/updateDeliveryStatus")
public AjaxResult updateDeliveryStatus(@RequestBody RkDeliveryUpdateDTO dto) {
if (dto.getIds() == null || dto.getIds().isEmpty()) {
return AjaxResult.error("rk_record 主键ID集合不能为空");
}
if (dto.getIsDelivery() == null) {
return AjaxResult.error("配送状态不能为空");
}
int rows = rkRecordService.updateDeliveryStatus(dto.getIds(), dto.getIsDelivery());
return AjaxResult.success(rows);
}
}

View File

@@ -1,188 +1,93 @@
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.RkInfoQueryDTO;
import com.zg.project.wisdom.domain.vo.*;
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 com.zg.project.wisdom.service.WarehouseStatService;
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.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
@Api(tags = "库存统计")
@RestController
@RequestMapping("/stat")
public class RkStatisticsController {
@Resource
@Autowired
private RkStatisticsService rkStatisticsService;
@Resource
private WarehouseStatService warehouseStatService;
/** 库龄统计(未出库:>10 / >20 / >30 天value 为项目数(按 xm_no 去重) */
@ApiOperation("库龄统计(未出库:>10 / >20 / >30 天)")
@GetMapping("/age")
public AjaxResult ageStats() {
List<Map<String, Object>> stats = rkStatisticsService.getAgeStatsAsList();
return AjaxResult.success(stats);
}
/**
* 仓库-场景库位使用统计(总仓库数、每仓库场景数、每场景库位/使用率/空闲率
* @return
* 首页统计(一次返回所有模块
*/
@GetMapping("/warehouseScene")
public AjaxResult warehouseScene() {
WarehouseSceneStatsResp resp = warehouseStatService.getWarehouseSceneStats();
return AjaxResult.success(resp);
}
/**
* 按仓库查询:场景列表 + 每个场景的可用库位明细
* 示例GET /warehouse/positions/available?warehouseCode=CK001
*/
@ApiOperation("按仓库查询各场景的可用库位(排除未出库占用)")
@GetMapping("/warehouse/available")
public AjaxResult listAvailableByWarehouse(@RequestParam String warehouseCode) {
List<SceneAvailableVO> data = rkStatisticsService.listAvailableByWarehouse(warehouseCode);
return AjaxResult.success(data);
}
/**
* 当前库存根据入库类型统计项目数,每个项目的条目数,实际入库总数,金额总和(仅未出库)
*/
@GetMapping("/type/summary")
public AjaxResult typeSummary() {
List<RkSummaryVO> list = rkStatisticsService.getTypeSummary();
return AjaxResult.success(list);
@ApiOperation("首页统计")
@PostMapping("/home")
public AjaxResult home(@RequestBody(required = false) HomeStatQueryDTO query) {
return AjaxResult.success(rkStatisticsService.getHomeStatistics(query));
}
/**
* 县局汇总仅未出库county 非空)
* 按时间查询【入库类型】统计
*/
@GetMapping("/county/summary")
public AjaxResult countySummary() {
List<RkSummaryVO> list = rkStatisticsService.getCountySummary();
@ApiOperation("按时间查询入库类型统计")
@PostMapping("/inTypeByTime")
public AjaxResult inTypeByTime(@RequestBody HomeStatQueryDTO query) {
return AjaxResult.success(rkStatisticsService.getInTypeByTime(query));
}
/**
* 按时间查询【出库类型】统计
*/
@ApiOperation("按时间查询出库类型统计")
@PostMapping("/outTypeByTime")
public AjaxResult outTypeByTime(@RequestBody HomeStatQueryDTO query) {
return AjaxResult.success(rkStatisticsService.getOutTypeByTime(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);
}
/**
* 一个接口:一周天级统计(返回入库+出库)根据供电中心统计
* - 如果 start/end 都传了,就用传入值;
* - 否则end=今天start=本周周一;
* 小仓库位使用情况统计(总库位/已使用/未使用)
*/
@ApiOperation("一周天级统计(入库+出库合并返回)")
@GetMapping("/week/daily")
public AjaxResult weekDaily(@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
Map<String, Object> data = rkStatisticsService.weekDaily(start, end);
return AjaxResult.success(data);
}
/**
* 库龄统计(>30天 & >60天项目数、条目数、总金额 + 明细列表
*/
@ApiOperation("库龄统计:>30天 & >60天含汇总与明细")
@GetMapping("/age/count")
public AjaxResult age3060() {
List<Map<String, Object>> data = rkStatisticsService.getAge3060();
return AjaxResult.success(data);
}
@ApiOperation("导出库龄明细(>30天与>60天合并导出")
@PostMapping("/age/export")
public void exportAge3060(HttpServletResponse response) {
rkStatisticsService.exportAge3060(response);
}
/**
* 入库类型饼图数据接口
* 返回每个入库类型的项目数、条目数、总数量、总金额
*/
@ApiOperation("入库类型饼图数据")
@GetMapping("/type/pie")
public AjaxResult typePie() {
List<RkTypePieVO> list = rkStatisticsService.getTypePie();
@GetMapping("/warehouse/slot")
public AjaxResult warehouseSlotStat() {
List<WarehouseSlotStatVO> list = rkStatisticsService.selectWarehouseSlotStat();
return AjaxResult.success(list);
}
/**
* 按入库类型统计(带时间范围
* 统计维度:项目数、条目数、总数量、总金额
* 时间字段rk_time自然日闭区间 [start, end]
* 参数格式:推荐 yyyy-MM-dd如 2025-01-01
* 库龄>=30天明细导出分组>30天 / >60天
*/
@ApiOperation("按入库类型统计(带时间范围:项目数/条目数/总数量/总金额)")
@GetMapping("/type/range")
public AjaxResult typeSummaryRange(@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
List<RkTypePieVO> list = rkStatisticsService.getTypeSummaryByRange(start, end);
return AjaxResult.success(list);
@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天明细");
}
@ApiOperation("按出库类型统计(带时间范围:项目数/条目数/总数量/总金额)")
@GetMapping("/type/out/range")
public AjaxResult outTypeSummaryRange(@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
List<RkTypePieVO> list = rkStatisticsService.getOutTypeSummaryByRange(start, end);
return AjaxResult.success(list);
}
@ApiOperation("本月出入库汇总(项目数/条目数/总数量/总金额)")
@GetMapping("/month/summary")
public AjaxResult monthSummary() {
RkMonthInOutSummaryVO vo = rkStatisticsService.getThisMonthInOutSummary();
return AjaxResult.success(vo);
}
@ApiOperation("应到未到统计(未到/部分未到)")
@GetMapping("/undelivered")
public AjaxResult undelivered() {
GysJhUndeliveredVO vo = rkStatisticsService.getUndeliveredSummary();
return AjaxResult.success(vo);
}
@ApiOperation("当前库存按物资类型统计(项目数/条目数/总数量/总金额)")
@GetMapping("/stock/wlType")
public AjaxResult stockByWlType() {
List<WlTypeStockStatVO> list = rkStatisticsService.getCurrentStockByWlType();
return AjaxResult.success(list);
}
/**
* 当前库存:入库时间 >30天项目数 / 条目数 / 总数量 / 总金额)
* 口径仅统计未删除、未出库is_chuku=0 或 NULL、rk_time 非空且 DATEDIFF(CURDATE(), DATE(rk_time)) > 30 的记录
*/
@ApiOperation("当前库存:入库时间>30天项目数/条目数/总数量/总金额)")
@GetMapping("/age/gt30")
public AjaxResult ageGt30() {
RkTypePieVO vo = rkStatisticsService.getAgeGt30Summary();
return AjaxResult.success(vo);
}
/**
* range: 0=最近一周(7天, 按日)2=最近半年(6个月, 按月)
* 返回每个时间桶一行bucket, 入/出库项目数&条目数)
*/
@ApiOperation("出/入库项目数与条目数(按时间桶返回)")
@GetMapping("/io/buckets")
public AjaxResult ioBuckets(@RequestParam Integer range) {
List<IOBucketVO> rows = rkStatisticsService.getIOBuckets(range);
return AjaxResult.success(rows);
}
// @PostMapping("/statistics")
// public Map<String, Object> statistics(@RequestBody RkInfoQueryDTO dto) {
// // 调用新的合并查询方法
// return rkStatisticsService.selectStockStatistics(dto);
// }
}

View File

@@ -4,6 +4,8 @@ import java.math.BigDecimal;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.zg.project.wisdom.config.BigDecimalSerializer;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.zg.framework.aspectj.lang.annotation.Excel;
@@ -73,6 +75,7 @@ public class GysJh extends BaseEntity
/** 合同数量 */
@Excel(name = "合同数量")
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal htQty;
/** SAP订单编号 */
@@ -81,10 +84,12 @@ public class GysJh extends BaseEntity
/** 计划交货数量 */
@Excel(name = "计划交货数量")
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal jhQty;
/** 已入库数量(累计) */
@Excel(name = "已入库数量")
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal realQty;
/**

View File

@@ -1,7 +1,9 @@
package com.zg.project.wisdom.domain;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.zg.framework.web.domain.BaseEntity;
import com.zg.framework.aspectj.lang.annotation.Excel;
import com.zg.project.wisdom.config.BigDecimalSerializer;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
@@ -10,9 +12,6 @@ import java.util.Date;
/**
* 移库记录对象 move_record
*
* @author zg
* @date 2025-06-20
*/
public class MoveRecord extends BaseEntity {
@@ -27,12 +26,16 @@ public class MoveRecord extends BaseEntity {
/** 移库生成的新库存IDrk_info.id用于撤销移库 */
private Long newRkInfoId;
/** ================= 新增上级移库记录ID ================= */
private Long parentMoveId;
/** 实物ID */
@Excel(name = "实物ID")
private String entityId;
/** 实际移库数量 */
@Excel(name = "移库数量")
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal realQty;
/** 原仓库 */
@@ -70,7 +73,7 @@ public class MoveRecord extends BaseEntity {
@Excel(name = "操作人名称")
private String movedByName;
/** 操作时间(移库时间) */
/** 操作时间 */
@Excel(name = "操作时间", dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date movedAt;
@@ -104,6 +107,14 @@ public class MoveRecord extends BaseEntity {
// ==================== Getter / Setter ====================
public Long getParentMoveId() {
return parentMoveId;
}
public void setParentMoveId(Long parentMoveId) {
this.parentMoveId = parentMoveId;
}
public Long getId() {
return id;
}
@@ -286,6 +297,7 @@ public class MoveRecord extends BaseEntity {
.append("id", getId())
.append("rkInfoId", getRkInfoId())
.append("newRkInfoId", getNewRkInfoId())
.append("parentMoveId", getParentMoveId())
.append("entityId", getEntityId())
.append("realQty", getRealQty())
.append("fromCangku", getFromCangku())
@@ -298,17 +310,7 @@ public class MoveRecord extends BaseEntity {
.append("movedBy", getMovedBy())
.append("movedByName", getMovedByName())
.append("movedAt", getMovedAt())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("isDelete", getIsDelete())
.append("fromCangkuName", getFromCangkuName())
.append("toCangkuName", getToCangkuName())
.append("xmNo", getXmNo())
.append("xmMs", getXmMs())
.append("wlMs", getWlMs())
.append("gysMc", getGysMc())
.toString();
}
}

View File

@@ -97,8 +97,8 @@ public class RkBill extends BaseEntity {
@Excel(name = "施工队名称")
private String teamName;
/** 是否需要配送(0否,1是,2配送中,3配送完成) */
@Excel(name = "是否需要配送", readConverterExp = "0=否,1=是,2=配送中,3=配送完成")
/** 配送状态:0否 1是 2待接单 3配送中 4配送完成 */
@Excel(name = "是否需要配送", readConverterExp = "0=否,1=是,2=已接单,3=配送中,4=配送完成")
private String isDelivery;
/** 借用时间 */

View File

@@ -3,6 +3,8 @@ package com.zg.project.wisdom.domain;
import java.math.BigDecimal;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.zg.project.wisdom.config.BigDecimalSerializer;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.zg.framework.aspectj.lang.annotation.Excel;
@@ -23,7 +25,7 @@ public class RkInfo extends BaseEntity
private Long id;
/** 业务操作类型0入库1出库2借料出库3还料入库 */
@Excel(name = "业务类型", readConverterExp = "0=入库,1=出库,2=借料出库,3=还料入库")
// @Excel(name = "业务类型", readConverterExp = "0=入库,1=出库,2=借料出库,3=还料入库")
private String bizType;
/** 出入库类型 */
@@ -35,7 +37,7 @@ public class RkInfo extends BaseEntity
private String operationTypeName;
/** 执行状态0预入/预出1已完成 */
@Excel(name = "执行状态", readConverterExp = "0=预入/预出,1=已完成")
// @Excel(name = "执行状态", readConverterExp = "0=预入/预出,1=已完成")
private String execStatus;
/** 物资类型 */
@@ -78,11 +80,11 @@ public class RkInfo extends BaseEntity
private String operatorName;
/** 是否已出库0已入库1已出库 */
@Excel(name = "是否已出库", readConverterExp = "0=已入库,1=已出库")
// @Excel(name = "是否已出库", readConverterExp = "0=已入库,1=已出库")
private String isChuku;
/** 审核状态0入库待审核1已通过2已驳回3出库待审核 */
@Excel(name = "审核状态", readConverterExp = "0=入库待审核,1=已通过,2=已驳回,3=出库待审核")
// @Excel(name = "审核状态", readConverterExp = "0=入库待审核,1=已通过,2=已驳回,3=出库待审核")
private String status;
/** 单据号 */
@@ -90,7 +92,7 @@ public class RkInfo extends BaseEntity
private String billNo;
/** 县局 */
@Excel(name = "县局")
// @Excel(name = "县局")
private String xj;
/** 项目号 */
@@ -102,11 +104,11 @@ public class RkInfo extends BaseEntity
private String xmMs;
/** 出库项目号(借用方项目) */
@Excel(name = "借用方项目号")
// @Excel(name = "借用方项目号")
private String xmNoCk;
/** 出库项目描述(借用方项目描述) */
@Excel(name = "借用方项目描述")
// @Excel(name = "借用方项目描述")
private String xmMsCk;
/** 物料号 */
@@ -118,35 +120,40 @@ public class RkInfo extends BaseEntity
private String wlMs;
/** 供应商编码 */
@Excel(name = "供应商编码")
// @Excel(name = "供应商编码")
private String gysNo;
/** 供应商名称 */
@Excel(name = "供应商名称")
private String gysMc;
/** 计划交货金额 */
@Excel(name = "计划交货金额")
private BigDecimal jhAmt;
/** 合同单价 */
@Excel(name = "合同单价")
private BigDecimal htDj;
/** SAP订单编号 */
@Excel(name = "SAP订单编号")
private String sapNo;
/** 行号 */
@Excel(name = "行号")
// @Excel(name = "行号")
private String xh;
/** 计划交货金额 */
// @Excel(name = "计划交货金额")
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal jhAmt;
/** 合同单价 */
@Excel(name = "合同单价")
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal htDj;
/** 计划交货数量 */
@Excel(name = "计划交货数量")
// @Excel(name = "计划交货数量")
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal jhQty;
/** 合同数量 */
@Excel(name = "合同数量")
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal htQty;
/** 计量单位 */
@@ -155,8 +162,14 @@ public class RkInfo extends BaseEntity
/** 实际入库数量 */
@Excel(name = "实际入库数量")
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal realQty;
/** 导出用:总金额 = realQty * htDj不落库 */
@Excel(name = "总金额")
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal totalAmount;
/** 库位码 */
@Excel(name = "库位码")
private String pcode;
@@ -174,7 +187,7 @@ public class RkInfo extends BaseEntity
private String entityId;
/** 施工队 */
@Excel(name = "施工队")
// @Excel(name = "施工队")
private String teamCode;
/** 借用时间 */
@@ -192,7 +205,7 @@ public class RkInfo extends BaseEntity
private String hasMoved;
/** 是否借料0否,1是,2已归还 */
@Excel(name = "是否借料", readConverterExp = "0=否,1=是,2=已归还")
// @Excel(name = "是否借料", readConverterExp = "0=否,1=是,2=已归还")
private String isBorrowed;
/** 是否删除0正常1已删除 */
@@ -200,7 +213,7 @@ public class RkInfo extends BaseEntity
private String isDelete;
/** 供应计划ID */
@Excel(name = "供应计划ID")
// @Excel(name = "供应计划ID")
private Long gysJhId;
/** rdid */
@@ -212,8 +225,8 @@ public class RkInfo extends BaseEntity
/** sid */
private Long sid;
/** 是否需要配送(0否,1是,2配送中,3配送完成) */
@Excel(name = "是否需要配送", readConverterExp = "0否,1是,2配送中,3配送完成")
/** 配送状态:0否 1是 2待接单 3配送中 4配送完成 */
// @Excel(name = "是否需要配送", readConverterExp = "0=否,1=是,2=待配送,3=配送中,4=配送完成")
private String isDelivery;
/** 封样编号1 */
@@ -238,6 +251,12 @@ public class RkInfo extends BaseEntity
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date endDate;
/** 场景 */
@Excel(name = "场景",readConverterExp = "HJ=货架,DC=堆场")
private String scene;
private String sceneName;
public void setId(Long id)
{
this.id = id;
@@ -572,6 +591,14 @@ public class RkInfo extends BaseEntity
return realQty;
}
public BigDecimal getTotalAmount() {
return totalAmount;
}
public void setTotalAmount(BigDecimal totalAmount) {
this.totalAmount = totalAmount;
}
public void setPcode(String pcode)
{
this.pcode = pcode;
@@ -775,6 +802,24 @@ public class RkInfo extends BaseEntity
this.endDate = endDate;
}
public void setScene(String scene)
{
this.scene = scene;
}
public String getScene()
{
return scene;
}
public void setSceneName(String sceneName)
{
this.sceneName = sceneName;
}
public String getSceneName()
{
return sceneName;
}
@Override
public String toString() {
@@ -839,6 +884,8 @@ public class RkInfo extends BaseEntity
.append("isUpdate", getIsUpdate())
.append("startDate", startDate)
.append("endDate", endDate)
.append("totalAmount", totalAmount)
.append("scene", getScene())
.toString();
}
}

View File

@@ -5,6 +5,8 @@ import java.util.Date;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.zg.project.wisdom.config.BigDecimalSerializer;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.zg.framework.aspectj.lang.annotation.Excel;
@@ -34,7 +36,8 @@ public class RkRecord extends BaseEntity
/** 出入库类型 */
// @Excel(name = "出入库类型")
private String operationType;
// ====== 查询字段(新增)======
private List<String> operationTypes;
/** 出入库类型名称(联表) */
@Excel(name = "出入库类型名称")
private String operationTypeName;
@@ -48,7 +51,7 @@ public class RkRecord extends BaseEntity
private String teamName;
/** 执行状态0预入/预出1已完成 */
@Excel(name = "执行状态", readConverterExp = "0=预操作,1=已完成")
// @Excel(name = "执行状态", readConverterExp = "0=预操作,1=已完成")
private String execStatus;
/** 物资类型 */
@@ -116,11 +119,11 @@ public class RkRecord extends BaseEntity
private String xmMs;
/** 出库项目号(借用方项目) */
@Excel(name = "出库项目号", readConverterExp = "借=用方项目")
// @Excel(name = "出库项目号", readConverterExp = "借=用方项目")
private String xmNoCk;
/** 出库项目描述(借用方项目描述) */
@Excel(name = "出库项目描述", readConverterExp = "借=用方项目描述")
// @Excel(name = "出库项目描述", readConverterExp = "借=用方项目描述")
private String xmMsCk;
/** 物料号 */
@@ -139,14 +142,6 @@ public class RkRecord extends BaseEntity
@Excel(name = "供应商名称")
private String gysMc;
/** 计划交货金额 */
@Excel(name = "计划交货金额")
private BigDecimal jhAmt;
/** 合同单价 */
@Excel(name = "合同单价")
private BigDecimal htDj;
/** SAP订单编号 */
@Excel(name = "SAP订单编号")
private String sapNo;
@@ -155,12 +150,24 @@ public class RkRecord extends BaseEntity
@Excel(name = "行号")
private String xh;
/** 计划交货金额 */
// @Excel(name = "计划交货金额")
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal jhAmt;
/** 合同单价 */
@Excel(name = "合同单价")
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal htDj;
/** 计划交货数量 */
@Excel(name = "计划交货数量")
// @Excel(name = "计划交货数量")
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal jhQty;
/** 合同数量 */
@Excel(name = "合同数量")
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal htQty;
/** 计量单位 */
@@ -168,9 +175,15 @@ public class RkRecord extends BaseEntity
private String dw;
/** 实际入库数量 */
@Excel(name = "实际入库数量")
@Excel(name = "实际出/入库数量")
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal realQty;
/** 导出用:总金额 = realQty * htDj不落库 */
@Excel(name = "总金额")
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal totalAmount;
/** 库位码 */
@Excel(name = "库位码")
private String pcode;
@@ -225,16 +238,15 @@ public class RkRecord extends BaseEntity
// @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
private Long sid;
/** 是否需要配送(0否,1是,2配送中,3配送完成) */
@Excel(name = "是否需要配送(0否,1是,2配送中,3配送完成)")
/** 配送状态:0否 1是 2待接单 3配送中 4配送完成 */
@Excel(name = "是否需要配送", readConverterExp = "0=否,1=是,2=待配送,3=配送中,4=配送完成")
private String isDelivery;
/** 封样编号1 */
// @Excel(name = "封样编号1")
private String fycde1;
/** 封样编号2 */
// @Excel(name = "封样编号2")
// @Excel(name = "封样编号2")2
private String fycde2;
/** 1已更新 */
@@ -599,6 +611,14 @@ public class RkRecord extends BaseEntity
return realQty;
}
public BigDecimal getTotalAmount() {
return totalAmount;
}
public void setTotalAmount(BigDecimal totalAmount) {
this.totalAmount = totalAmount;
}
public void setPcode(String pcode)
{
this.pcode = pcode;
@@ -857,6 +877,7 @@ public class RkRecord extends BaseEntity
.append("htQty", getHtQty())
.append("dw", getDw())
.append("realQty", getRealQty())
.append("totalAmount", totalAmount)
.append("pcode", getPcode())
.append("pcodeId", getPcodeId())
.append("trayCode", getTrayCode())

View File

@@ -0,0 +1,15 @@
package com.zg.project.wisdom.domain.dto;
import lombok.Data;
import java.util.Date;
@Data
public class HomeStatQueryDTO {
/** 开始时间(可空) */
private Date startDate;
/** 结束时间(可空) */
private Date endDate;
}

View File

@@ -10,6 +10,6 @@ public class RkDeliveryUpdateDTO {
/** rk_info 主键ID集合 */
private List<Long> ids;
/** 配送状态0否 1是 2配送中 3已完成 */
/** 配送状态0否 1是 2待接单 3配送中 4配送完成 */
private Integer isDelivery;
}

View File

@@ -1,17 +0,0 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.util.List;
/** 最终返回结构(汇总 + 明细) */
@Data
public class Age3060ResultVO {
/** >30 天的汇总 */
private AgeSummaryVO.Slot gt30;
/** >60 天的汇总 */
private AgeSummaryVO.Slot gt60;
/** >30 天明细 */
private List<RkAgeDetailVO> gt30List;
/** >60 天明细 */
private List<RkAgeDetailVO> gt60List;
}

View File

@@ -1,24 +0,0 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/** 汇总 VO包含两个 Slotgt30 / gt60 */
@Data
public class AgeSummaryVO {
@Data
public static class Slot {
/** 仅用于 resultMap 绑定(无业务含义) */
private Integer dummyId;
/** 项目数DISTINCT xm_no */
private Integer projectCount;
/** 条目数COUNT(*) */
private Integer goodsCount;
/** 总金额SUM(ht_dj * real_qty) */
private BigDecimal sumAmount;
}
private Slot gt30;
private Slot gt60;
}

View File

@@ -1,87 +0,0 @@
package com.zg.project.wisdom.domain.vo;
import com.zg.framework.aspectj.lang.annotation.Excel;
import com.zg.project.wisdom.domain.AuditSignature;
import lombok.Data;
import java.util.Date;
import java.util.List;
/**
* 审核记录封装VO包含一条审核记录 + 多张现场照片)
*/
@Data
public class AuditSignatureReviewVO {
/** 主键ID审核记录的ID */
private Long id;
/** 单据编号 */
private String billNo;
/** 入库记录ID对应rk_info.id */
private Long rkId;
/** 单据类型0入库1出库 */
private String billType;
/** 审核人ID */
private String approverId;
/** 审核人签字图片URL签字图 */
private String approverSignUrl;
/** 审核结果0通过1驳回 */
private String auditResult;
/** 是否当前记录1是0否 */
private String isCurrent;
private String ckType;
private String ckTypeName;
private String teamCode;
private String teamName;
/** 审核时间 */
private Date signTime;
/** 审核备注 */
private String remark;
/** 创建人 */
private String createBy;
/** 创建时间 */
private Date createTime;
/** 更新人 */
private String updateBy;
/** 更新时间 */
private Date updateTime;
/** 逻辑删除标记0正常1删除 */
private String isDelete;
/** 携带入库信息的现场图片列表 */
private List<AuditSignatureVo> scenePhotos;
/** 入库类型名称(中文) */
private String rkTypeName;
/** 所属仓库名称(中文) */
private String cangkuName;
/** 物资类型名称(中文) */
private String wlTypeName;
/** 理货员名称(原本就是中文) */
private String lihuoY;
/** 理货员名称(联查显示用,导出专用) */
private String lihuoYName;
}

View File

@@ -1,5 +1,7 @@
package com.zg.project.wisdom.domain.vo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.zg.project.wisdom.config.BigDecimalSerializer;
import com.zg.project.wisdom.domain.AuditSignature;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -29,6 +31,7 @@ public class AuditSignatureVo extends AuditSignature {
private String wlNo;
/** 实际入库数量 */
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal realQty;
/** 计量单位 */

View File

@@ -1,32 +0,0 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/** 周天级统计(按县局) */
@Data
public class DayCountyStatVO {
/**
* 日期(如果 SQL 未分日统计,该字段可能为 null
* 示例2025-08-19
*/
private String day;
/** 县局(若为空或空串会被归类为“未知县局”) */
private String xj;
/** 项目数(去重 xm_no 计数) */
private Integer projectCount;
/** 条目数明细记录总数COUNT(*) */
private Integer itemCount;
/** 数量总和SUM(real_qty) */
private Long totalQty;
/** 合同金额总和SUM(ht_dj * real_qty) */
private BigDecimal amountHt;
/** 计划金额总和(仅入库时有值,出库恒为 0 */
private BigDecimal amountPlan;
}

View File

@@ -1,7 +1,8 @@
package com.zg.project.wisdom.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.zg.project.wisdom.domain.RkInfo;
import com.zg.framework.aspectj.lang.annotation.Excel;
import com.zg.project.wisdom.domain.RkRecord;
import lombok.Data;
import java.io.Serializable;
@@ -9,24 +10,34 @@ import java.util.Date;
import java.util.List;
/**
* 待配送出库单据 VO1 对多:一个出库单据 + 多条货物明细)
* 待配送出库单据 VO
* 以 rk_bill 为主 + 多条 rk_record 明细
*/
@Data
public class DeliveryBillVO implements Serializable {
/** 出库单据号(rk_info.bill_no_ck */
private String billNoCk;
/** 单据号(对应 rk_bill.bill_no */
private String billNo;
/** 领货人(出库理货员) */
private String ckLihuoY;
/** 施工队/出库班组 */
/** 施工队编码 */
private String teamCode;
/** 领用时间(出库时间) */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date lyTime;
/** 施工队名称 */
private String teamName;
/** 理货员名称 */
private String operatorName;
/** 出库/操作时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date operationTime;
/** 配送状态0否 1是 2待接单 3配送中 4配送完成 */
private String isDelivery;
/** 出入库类型名称(联表) */
private String operationTypeName;
/** 该单据下的货物明细 */
private List<RkRecord> recordList;
/** 该出库单据下的所有货物明细 */
private List<RkInfo> detailList;
}

View File

@@ -1,6 +1,8 @@
// com.zg.project.wisdom.domain.vo.GysJhUndeliveredSummaryVO
package com.zg.project.wisdom.domain.vo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.zg.project.wisdom.config.BigDecimalSerializer;
import lombok.Data;
import java.math.BigDecimal;
@@ -14,5 +16,6 @@ public class GysJhUndeliveredSummaryVO {
/** 总数量jh_qty 合计,表示剩余未到数量) */
private Long totalQty;
/** 总金额ht_dj * jh_qty 合计) */
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal amountPlan;
}

View File

@@ -1,13 +0,0 @@
// com.zg.project.wisdom.domain.vo.GysJhUndeliveredVO
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
/** 返回体:未到 + 部分未到 + 合计 */
@Data
public class GysJhUndeliveredVO {
/** 未到货status='0' */
private GysJhUndeliveredSummaryVO undelivered;
/** 部分未到货status='2' */
private GysJhUndeliveredSummaryVO partialUndelivered;
}

View File

@@ -0,0 +1,34 @@
package com.zg.project.wisdom.domain.vo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.zg.project.wisdom.config.BigDecimalSerializer;
import lombok.Data;
import java.math.BigDecimal;
/**
* 首页顶部 KPI 统计
*/
@Data
public class HomeKpiVO {
/** 月入库条数 */
private Integer monthInCount;
/** 月入库项目数 */
private Integer monthInProjectCount;
/** 月入库金额 */
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal monthInAmount;
/** 月出库条数 */
private Integer monthOutCount;
/** 月出库项目数 */
private Integer monthOutProjectCount;
/** 月出库金额 */
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal monthOutAmount;
}

View File

@@ -0,0 +1,27 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.util.List;
/**
* 首页统计返回 VO最终版
*/
@Data
public class HomeStatVO {
/** 顶部 KPI */
private HomeKpiVO kpi;
/** 当前入库类型统计 */
private List<StockStatisticGroupVO> currentInType;
/** 物资类型统计 */
private List<StockStatisticGroupVO> materialType;
/** 入库类型统计(时间) */
private List<StockStatisticGroupVO> inTypeByTime;
/** 出库类型统计(时间) */
private List<StockStatisticGroupVO> outTypeByTime;
}

View File

@@ -1,26 +0,0 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
/**
* 出/入库统计结果 VO
* 每个时间桶(按日/按月)一行
*/
@Data
public class IOBucketVO {
/** 时间桶:
* range=0最近一周返回 MM-dd
* range=2最近半年返回 yyyy-MM
*/
private String bucket;
/** 入库项目数 */
private Integer inProjectCount;
/** 入库条目数 */
private Integer inItemCount;
/** 出库项目数 */
private Integer outProjectCount;
/** 出库条目数 */
private Integer outItemCount;
}

View File

@@ -1,14 +0,0 @@
// vo/PcodeQtyVO.java
package com.zg.project.wisdom.domain.vo;
import java.math.BigDecimal;
import lombok.Data;
@Data
public class PcodeQtyVO {
/** 库位码 */
private String pcode;
/** 在库总数量real_qty 之和) */
private BigDecimal totalQty;
}

View File

@@ -1,5 +1,7 @@
package com.zg.project.wisdom.domain.vo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.zg.project.wisdom.config.BigDecimalSerializer;
import lombok.Data;
import java.math.BigDecimal;
@@ -8,6 +10,7 @@ public class PlanToMtdVO {
private String wlNo; // 物料号
private String wlMs; // 物料描述
private String dw; // 单位
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal htDj; // 合同单价
}

View File

@@ -1,53 +0,0 @@
package com.zg.project.wisdom.domain.vo;
import com.zg.framework.aspectj.lang.annotation.Excel;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
@Data
public class RkAgeDetailVO {
/* ---------- 仅用于导出分组显示,不落库 ---------- */
@Excel(name = "库龄分组") // “>30天”/“>60天”
private String ageBucket;
@Excel(name = "ID")
private Long id;
/** 项目号 / 项目描述 */
@Excel(name = "项目号")
private String xmNo;
@Excel(name = "项目描述")
private String xmMs;
/** 入库时间 & 库龄天数 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "入库时间", dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date rkTime;
@Excel(name = "库龄(天)")
private Integer kuLingDays;
/** 单价、数量、行金额 */
@Excel(name = "单价")
private BigDecimal htDj;
@Excel(name = "数量")
private BigDecimal realQty;
@Excel(name = "行金额")
private BigDecimal amount;
/** 库位 / 托盘 / 单据号(便于追踪) */
@Excel(name = "库位")
private String pcode;
@Excel(name = "托盘")
private String trayCode;
@Excel(name = "单据号")
private String billNo;
}

View File

@@ -1,21 +0,0 @@
// com/zg/project/wisdom/vo/stat/RkAgeStatVO.java
package com.zg.project.wisdom.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "RkAgeStatVO", description = "库龄统计结果(仅未出库)")
public class RkAgeStatVO {
@ApiModelProperty("大于10天的数量")
private Integer gt10;
@ApiModelProperty("大于20天的数量")
private Integer gt20;
@ApiModelProperty("大于30天的数量")
private Integer gt30;
}

View File

@@ -1,79 +0,0 @@
package com.zg.project.wisdom.domain.vo;
import com.zg.framework.aspectj.lang.annotation.Excel;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* 出入库统计明细 VO
* 仅用于查询与展示,不参与落库
*/
@Data
public class RkCkStatisticVO implements Serializable {
private static final long serialVersionUID = 1L;
/** 操作类型:入库 / 出库 */
@Excel(name = "操作类型")
private String operationType;
/** 详细类型:入库类型名 / 出库类型名 */
@Excel(name = "详细类型")
private String detailType;
/** 库存状态(在库 / 已出库 / 借料等) */
@Excel(name = "库存状态")
private String stockStatus;
/** 单据号(入库单号或出库单号) */
@Excel(name = "单据号")
private String billNo;
/** 物料号 */
@Excel(name = "物料号")
private String materialCode;
/** 物料描述 */
@Excel(name = "物料描述")
private String materialName;
/** 单位 */
@Excel(name = "单位")
private String unit;
/** 数量(实际数量) */
@Excel(name = "数量")
private BigDecimal quantity;
/** 项目号 */
@Excel(name = "项目号")
private String projectCode;
/** 项目描述 */
@Excel(name = "项目描述")
private String projectName;
/** 库位码 */
@Excel(name = "库位码")
private String locationCode;
/** 订单编号SAP 订单号) */
@Excel(name = "订单编号")
private String orderNo;
/** 供应商名称 */
@Excel(name = "供应商名称")
private String supplierName;
/** 备注 */
@Excel(name = "备注")
private String remark;
/** 入库时间(用户操作入库的日期) */
@Excel(name = "操作时间", dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date operationTime;
}

View File

@@ -1,11 +0,0 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.time.LocalDateTime;
/** 入库原始数据(用于代码侧分桶) */
@Data
public class RkInVO {
private String xmNo;
private LocalDateTime rkTime;
}

View File

@@ -1,12 +0,0 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
/** 月度入/出库两段结果 */
@Data
public class RkMonthInOutSummaryVO {
/** 入库汇总rk_time */
private RkMonthSummaryVO inSummary;
/** 出库汇总ly_time, is_chuku=1 */
private RkMonthSummaryVO outSummary;
}

View File

@@ -1,18 +0,0 @@
// com.zg.project.wisdom.domain.vo.RkMonthSummaryVO
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/** 月度汇总(项目数/条目数/总数量/总金额) */
@Data
public class RkMonthSummaryVO {
/** 去重项目数(按 xm_no 去重) */
private Integer projectCount;
/** 条目数(明细行数) */
private Integer itemCount;
/** 总数量real_qty 合计) */
private Long totalQty;
/** 总金额ht_dj * real_qty 合计) */
private BigDecimal amountHt;
}

View File

@@ -1,13 +0,0 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.time.LocalDateTime;
/** 出库原始数据(用于代码侧分桶) */
@Data
public class RkOutVO {
private String xmNoCk;
private String xmNo;
private LocalDateTime lyTime;
private LocalDateTime updateTime;
}

View File

@@ -1,25 +0,0 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.util.List;
/**
* 入库提交返回 VO
* 返回入库单据号、是否需要审核、及托盘信息
*/
@Data
public class RkSubmitResultVO {
/** 单据号 */
private String billNo;
/** 是否需要审核流程 */
private boolean needAudit;
/** 所有库位码 */
private List<String> pcodeList;
/** 所有托盘码 */
private List<String> trayCodeList;
}

View File

@@ -1,24 +0,0 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/** 县局维度汇总结果 */
@Data
public class RkSummaryVO {
/** 分组名称(如县局名、入库类型等) */
private String groupName;
/** 项目数(去重 xm_no */
private Integer projectCount;
/** 条目总数COUNT(*) */
private Integer goodsCountTotal;
/** 总金额 = SUM(ht_dj * real_qty) */
private BigDecimal sumAmount;
/** 总数量 = SUM(real_qty) */
private BigDecimal sumQty;
}

View File

@@ -1,30 +0,0 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* 入库类型饼状图统计 VO
* 每个入库类型对应一条记录,用于前端饼状图展示
*/
@Data
public class RkTypePieVO {
/** 入库类型编码stock_in_type.type_code */
private String rkTypeCode;
/** 入库类型名称stock_in_type.type_name */
private String rkTypeName;
/** 项目数量(去重 xm_no */
private Integer projectCount;
/** 条目总数COUNT(*) */
private Integer goodsCountTotal;
/** 总数量SUM real_qty */
private BigDecimal sumQty;
/** 总金额SUM ht_dj * real_qty */
private BigDecimal sumAmount;
}

View File

@@ -1,31 +0,0 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* 入库类型 + 项目 明细VO
* 用于统计每种入库类型下的每个项目:
* - 项目信息
* - 货物数量
* - 合同单价合计
* - 实际入库数量合计
* - 金额合计
*/
@Data
public class RkTypeProjectDetailVO {
/** 入库类型rk_type */
private Integer rkType;
/** 项目号xm_no */
private String projectNo;
/** 项目名称/描述xm_ms */
private String projectName;
/** 该项目下的货物条数 */
private Integer goodsCount;
/** 合同单价合计 */
private BigDecimal sumHtDj;
/** 实际入库数量合计 */
private BigDecimal sumRealQty;
/** 金额合计 = ht_dj * real_qty */
private BigDecimal sumAmount;
}

View File

@@ -1,24 +0,0 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
/**
* 某入库类型下,单个项目的货物条目数
*/
@Data
public class RkTypeProjectGoodsCountVO {
/** 入库类型(冗余用于归并) */
private Integer rkType;
/** 县局类型统计时可为null */
private String county;
/** 项目号xm_no */
private String projectNo;
/** 项目名称/描述xm_ms取一个示例值 */
private String projectName;
/** 该项目的货物条目数COUNT(*) */
private Integer goodsCount;
}

View File

@@ -1,44 +0,0 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
/**
* 入库类型汇总VO仅统计未出库 is_chuku = 0
* 每种入库类型下聚合:
* - projectCount项目数量去重xm_no
* - goodsCountTotal货物条目总数记录条数
* - sumHtDj合同单价合计
* - sumRealQty实际入库数量合计
* - sumAmount金额合计 = ht_dj * real_qty
* - avgGoodsPerProject每项目平均条目数 = goodsCountTotal / projectCount保留2位小数
* - projectGoodsCounts每个项目的“条目数清单”你要的关键补充
*/
@Data
public class RkTypeSummaryVO {
/** 入库类型rk_type */
private String rkType;
/** 县局类型统计时可为null */
private String county;
/** 项目数量去重xm_no */
private Integer projectCount;
/** 货物条目总数(该类型下的记录条数) */
private Integer goodsCountTotal;
/** 合同单价合计 */
private BigDecimal sumHtDj;
/** 实际入库数量合计 */
private BigDecimal sumRealQty;
/** 金额合计 = ht_dj * real_qty */
private BigDecimal sumAmount;
/** 每个项目的条目数清单(仅含条目数;如需金额/数量可再扩展) */
private List<RkTypeProjectGoodsCountVO> projectGoodsCounts;
}

View File

@@ -1,19 +0,0 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.util.List;
/**
* 场景下的可用库位
*/
@Data
public class SceneAvailableVO {
/** 场景编码pcde_detail.scene对应 scene_mapping.scene_code */
private String sceneCode;
/** 场景名称scene_mapping.scene_name */
private String sceneName;
/** 可用库位数量 */
private Integer availableCount;
/** 可用库位明细 */
private List<PcdeDetailSimpleVO> positions;
}

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,34 @@
package com.zg.project.wisdom.domain.vo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.zg.project.wisdom.config.BigDecimalSerializer;
import lombok.Data;
import java.math.BigDecimal;
/**
* 首页分组统计 VO通用
*/
@Data
public class StockStatisticGroupVO {
/** 分组值(类型编码,如 operation_type / wl_type */
private String groupValue;
/** 分组名称(类型编码,如 operation_type / wl_type */
private String groupName;
/** 总金额 */
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal totalAmount;
/** 使用库位数 */
private Integer locationCount;
/** 总数量 */
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal totalQuantity;
/** 项目数(去重 xm_no */
private Integer projectCount;
}

View File

@@ -1,5 +1,7 @@
package com.zg.project.wisdom.domain.vo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.zg.project.wisdom.config.BigDecimalSerializer;
import lombok.Data;
import java.math.BigDecimal;
@@ -13,6 +15,10 @@ public class StockStatisticVO {
/** 使用库位数量 */
private Integer locationCount;
/** 货物总数量 */
/** 库存总数量 */
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal totalQuantity;
/** 项目总数(去重 xm_no */
private Integer projectCount;
}

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,20 +0,0 @@
// com.zg.project.wisdom.domain.vo.WlTypeStockStatVO
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/** 当前库存-按物资类型统计 */
@Data
public class WlTypeStockStatVO {
/** 物资类型wl_type */
private String typeName;
/** 去重项目数(按 xm_no */
private Integer projectCount;
/** 条目数(明细行数) */
private Integer itemCount;
/** 总数量real_qty 合计) */
private Long totalQty;
/** 总金额ht_dj * real_qty 合计) */
private BigDecimal amountHt;
}

View File

@@ -59,4 +59,21 @@ public interface MoveRecordMapper
* @return 结果
*/
void insertMoveRecord(MoveRecord moveRecord);
/**
* 根据入库信息id查询移库记录id
*
* @param rkInfoId 入库信息id
* @return 移库记录id
*/
Long selectLastMoveIdByRkInfoId(Long rkInfoId);
/**
* 根据移库记录id查询子移库记录数量
*
* @param moveRecordId 移库记录id
* @return 子移库记录数量
*/
int countByParentMoveId(Long moveRecordId);
}

View File

@@ -86,4 +86,13 @@ public interface RkBillMapper
*/
int updateExecStatus(@Param("billNo") String billNo,
@Param("execStatus") String execStatus);
/**
* 修改配送状态
* @param billNo
* @param isDelivery
* @return
*/
int updateDeliveryStatusByBillNo(@Param("billNo") String billNo,
@Param("isDelivery") Integer isDelivery);
}

View File

@@ -1,8 +1,12 @@
package com.zg.project.wisdom.mapper;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.zg.project.inventory.domain.vo.PcdeCntVO;
import com.zg.project.inventory.domain.vo.RkInfoMatchVO;
import com.zg.project.wisdom.domain.RkInfo;
import com.zg.project.wisdom.domain.vo.StockStatisticVO;
import io.lettuce.core.dynamic.annotation.Param;
@@ -108,4 +112,84 @@ public interface RkInfoMapper
*/
StockStatisticVO selectStockStatisticByCondition(RkInfo query);
/**
* 1⃣ 正常匹配的数据
* 扫描到 & 当前库存中 & 属于当前场景
*/
List<RkInfo> getByPcodeIdList(@Param("list") List<String> pcdeIds,
@Param("sceneId") String sceneId);
/**
* 2⃣ 未盘点的数据
* 当前库存中 & 未出现在扫描结果中
*/
List<RkInfo> getMissedPcodeIds(@Param("list") List<String> pcdeIds,
@Param("sceneId") String sceneId);
/**
* 3⃣ 异常数据(批量)
* 扫描到,但在当前库存中不存在
*/
List<String> getNotExistsPcodeIds(@Param("list") List<String> pcdeIds,
@Param("sceneId") String sceneId);
/**
* 图表统计:每个库位有多少个货物
* @param ids
* @return
*/
List<PcdeCntVO> selectPcdeCntFromRkInfo(List<String> ids);
/**
* 根据所属仓库查询所有的库位号和库位对应的货物数量
* @param
* @return
*/
List<RkInfoMatchVO> getUnscannedPcodeByScene(@org.apache.ibatis.annotations.Param("sceneId") String sceneId,
@org.apache.ibatis.annotations.Param("taskId") String taskId);
/**
* 获取指定仓库的盘点数据
* @param warehouse
* @return
*/
int countGetByWh(@org.apache.ibatis.annotations.Param("warehouse") String warehouse);
List<RkInfo> listRkInfoByPcode(String pcode);
/**
* 查询库存单据明细列表(按单据号排序)
*/
List<RkInfo> selectRkInfoListOrderByBillNo(RkInfo rkInfo);
/**
* 根据仓库和场景查询库存单据明细
*/
List<RkInfo> getByWarehouseAndScene(
@Param("warehouseCode") String warehouseCode,
@Param("sceneId") String sceneId
);
List<RkInfo> listRkInfoByPcodes(ArrayList<String> strings);
/**
* 查询今日最大单号
* */
String selectTodayMaxBillNo(String prefix, String date);
/**
* 更新单位
* */
int updateDw(@Param("sapNo") String sapNo,
@Param("wlNo") String wlNo,
@Param("xmNo") String xmNo,
@Param("dw") String dw);
/**
* 条目数
* */
int countByWarehouseAndScene(
@Param("warehouseCode") String warehouseCode,
@Param("sceneId") String sceneId
);
int updateInfoByBillNo(RkInfo rkInfo);
}

View File

@@ -123,15 +123,6 @@ public interface RkRecordMapper
*/
RkRecord selectRkRecordByRkInfoId(@Param("rkInfoId") Long rkInfoId);
/**
* 入库统计
*/
StockStatisticVO selectInRecordStatistic(RkRecord query);
/**
* 出库统计
*/
StockStatisticVO selectOutRecordStatistic(RkRecord query);
/**
* 借出入库
@@ -149,4 +140,47 @@ public interface RkRecordMapper
int countPreOutRecordByBillNo(@Param("billNo") String billNo);
/**
* 查询出入库
* */
StockStatisticVO selectRecordStatisticByCondition(RkRecord query);
/**
* 根据单据号查询出入库记录
* */
List<RkRecord> selectByBillNo(String billNo);
/**
* 修改发货状态
* */
int updateDeliveryStatus(@Param("ids") List<Long> ids,
@Param("isDelivery") Integer isDelivery);
/**
* 根据ID列表查询单据号列表
* */
List<String> selectBillNosByRecordIds(@Param("ids") List<Long> ids);
/**
* 查询该 bill 下 与目标状态不同的记录数
*/
int countDifferentDeliveryStatus(@Param("billNo") String billNo,
@Param("isDelivery") Integer isDelivery);
/**
* 根据 billNo 批量更新 record 明细
*/
int updateRecordByBillNo(RkRecord record);
/**
* 查询bill最小配送状态
*/
Integer selectMinDeliveryStatusByBillNo(
@Param("billNo") String billNo);
/**
* 更新单位
*/
int updateDw(@Param("sapNo") String sapNo,
@Param("wlNo") String wlNo,
@Param("xmNo") String xmNo,
@Param("dw") String dw);
}

View File

@@ -1,114 +1,53 @@
package com.zg.project.wisdom.mapper;
import com.zg.project.wisdom.domain.vo.*;
import io.lettuce.core.dynamic.annotation.Param;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Mapper
public interface RkStatisticsMapper {
/** 库龄统计(未出库):按项目数计数(去重 xm_no */
RkAgeStatVO selectAgeStats();
/** 当前入库类型统计 */
List<StockStatisticGroupVO> statCurrentInByOperationType();
/** 当前物资类型统计 */
List<StockStatisticGroupVO> statCurrentByMaterialType();
/** 入库类型统计(时间) */
List<StockStatisticGroupVO> statInByOperationType(
@Param("startDate") Date startDate,
@Param("endDate") Date endDate
);
/** 出库类型统计(时间) */
List<StockStatisticGroupVO> statOutByOperationType(
@Param("startDate") Date startDate,
@Param("endDate") Date endDate
);
/** 首页 KPI */
HomeKpiVO statHomeKpi();
/**
* 首页库龄统计接口
* */
List<StockAgeStatVO> selectStockAgeStat();
/**
* 首页待办事项接口
* */
List<TodoStatVO> selectTodoStat();
/**
* 入库类型汇总(仅未出库
* @return 汇总结果列表
* 小仓库位使用情况统计(总库位/已使用/未使用
*/
List<RkTypeSummaryVO> selectRkTypeSummary();
List<WarehouseSlotStatVO> selectWarehouseSlotStat();
/**
* 按入库类型 + 项目 统计【每项目的条目数】
* (用于回填到类型汇总的 projectGoodsCounts 中)
* 库龄>=30天明细用于导出
*/
List<RkTypeProjectGoodsCountVO> selectProjectGoodsCountByType();
List<StockAgeExportVO> selectStockAgeExport30();
/** 入库类型汇总未出库rk_type 非空) */
List<RkSummaryVO> selectTypeSummary();
/**
* 县局汇总(未出库,县局非空)
*/
List<RkSummaryVO> selectCountySummary();
/** 入库(每天+县局):按 rk_time */
List<DayCountyStatVO> selectWeekDailyIn(@Param("startDate") String startDate,
@Param("endDate") String endDate);
/** 出库(每天+县局):按 ly_time */
List<DayCountyStatVO> selectWeekDailyOut(@Param("startDate") String startDate,
@Param("endDate") String endDate);
/** 汇总(>30 & >60项目数、条目数、总金额 */
AgeSummaryVO selectAge3060Summary();
/** 明细列表:返回超过 minDays 天的明细minDays 取 30 或 60 */
List<RkAgeDetailVO> selectAgeDetails(@Param("minDays") int minDays);
/**
* 按入库类型统计饼状图数据
* @return 每个入库类型的项目数、条目数、总数量、总金额
*/
List<RkTypePieVO> selectTypePie();
/**
* 按入库类型统计(联查 stock_in_type时间范围过滤
* 仅统计未删除、未出库
*/
List<RkTypePieVO> selectTypeSummaryByRange(@Param("start") String start,
@Param("end") String end);
/**
* 按出库类型统计(时间范围;仅 is_chuku=1
* 维度:项目数、条目数、总数量、总金额
*/
List<RkTypePieVO> selectOutTypeSummaryByRange(@Param("start") String start,
@Param("end") String end);
/** 本月入库汇总(按 rk_time 过滤) */
RkMonthSummaryVO selectMonthlyInSummary(@Param("start") String start,
@Param("end") String end);
/** 本月出库汇总is_chuku=1 且按 ly_time 过滤) */
RkMonthSummaryVO selectMonthlyOutSummary(@Param("start") String start,
@Param("end") String end);
/** 应到未到status='0' */
GysJhUndeliveredSummaryVO selectUndelivered();
/** 部分未到status='2' */
GysJhUndeliveredSummaryVO selectPartialUndelivered();
/** 当前库存:按物资类型统计(过滤 wl_type 空/无) */
List<WlTypeStockStatVO> selectCurrentStockByWlType();
/** 当前库存:入库时间 >30 天的四项汇总SQL 聚合计算) */
RkTypePieVO selectAgeGt30Summary();
/** 入库:在 [startTs, endTs) 时间范围内的原始行(仅最小过滤) */
List<RkInVO> selectInRange(@Param("startTs") String startTs,
@Param("endTs") String endTs);
/** 出库:在 [startTs, endTs) 时间范围内、is_chuku=1 的原始行(仅最小过滤) */
List<RkOutVO> selectOutRange(@Param("startTs") String startTs,
@Param("endTs") String endTs);
/**
* 外层:按场景汇总可用数量,并级联查询可用库位明细(返回 SceneAvailableVO 列表)
*/
List<SceneAvailableVO> selectAvailableByWarehouse(@Param("warehouseCode") String warehouseCode);
/**
* 内层:指定仓库 + 场景,查询该场景下的可用库位明细
*/
List<PcdeDetailSimpleVO> selectAvailablePositionsByWarehouseAndScene(
@Param("warehouseCode") String warehouseCode,
@Param("sceneCode") String sceneCode);
}

View File

@@ -4,6 +4,7 @@ import java.util.List;
import com.zg.project.wisdom.domain.RkBill;
import com.zg.project.wisdom.domain.dto.BorrowReturnDTO;
import com.zg.project.wisdom.domain.dto.RkBillCreateDTO;
import com.zg.project.wisdom.domain.vo.DeliveryBillVO;
/**
* 库存单据Service接口
@@ -77,4 +78,11 @@ public interface IRkBillService
* 还料入库
*/
int returnBorrow(BorrowReturnDTO dto);
/**
* 配送出库单据查询
* @param
* @return
*/
List<DeliveryBillVO> selectDeliveryBillList(RkBill query);
}

View File

@@ -1,6 +1,10 @@
package com.zg.project.wisdom.service;
import java.util.ArrayList;
import java.util.List;
import com.zg.project.inventory.domain.dto.QueryDTO;
import com.zg.project.inventory.domain.vo.ChartDataVO;
import com.zg.project.wisdom.domain.RkInfo;
import com.zg.project.wisdom.domain.vo.StockStatisticVO;
@@ -66,5 +70,54 @@ public interface IRkInfoService
StockStatisticVO getStockStatistic(RkInfo query);
/**
* 盘点开始匹配
* @param dto
* @return
*/
void matchWithStatus(QueryDTO dto);
/**
* 盘点开始匹配(纯数据)
* @param dto
* @return
*/
void matchPure(QueryDTO dto);
/**
* 图表统计:每个库位有多少个货物
* @param dto
* @return
*/
ChartDataVO matchWithAll(QueryDTO dto);
/**
* 统计指定仓库的库存数量
* @param warehouse
* @return
*/
int countGetByWh(String warehouse);
/**
* 按库位查询货物数量
* @param pcode
* @return
*/
List<RkInfo> listRkInfoByPcode(String pcode);
/**
* 查询库存单据明细列表(按单据号排序)
*/
List<RkInfo> selectRkInfoListOrderByBillNo(RkInfo rkInfo);
/**
* 按多个pcode查询
* @param strings
* @return
*/
List<RkInfo> listRkInfoByPcodes(ArrayList<String> strings);
/**
*条目数
* */
int countByWarehouseAndScene(String warehouseCode, String sceneId);
}

View File

@@ -3,6 +3,7 @@ package com.zg.project.wisdom.service;
import java.util.List;
import com.zg.project.wisdom.domain.RkRecord;
import com.zg.project.wisdom.domain.vo.RecordStatisticVO;
import com.zg.project.wisdom.domain.vo.StockStatisticVO;
/**
* 出入库记录Service接口
@@ -92,6 +93,13 @@ public interface IRkRecordService
*/
int deletePreOutRecords(Long[] ids);
/**
* 出入库总数统计
*/
StockStatisticVO selectRecordStatisticByCondition(RkRecord query);
RecordStatisticVO getRecordStatistic(RkRecord query);
/**
* 修改指定入库记录的配送状态
*/
int updateDeliveryStatus(List<Long> ids, Integer isDelivery);
}

View File

@@ -1,98 +1,44 @@
package com.zg.project.wisdom.service;
import com.zg.project.wisdom.domain.dto.RkInfoQueryDTO;
import com.zg.project.wisdom.domain.dto.HomeStatQueryDTO;
import com.zg.project.wisdom.domain.vo.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
/** 首页统计服务 */
public interface RkStatisticsService {
/** 库龄统计(仅未出库) */
// RkAgeStatVO getAgeStats();
/** 新增:返回 Map */
List<Map<String, Object>> getAgeStatsAsList();
/** 入库类型汇总(未出库) */
List<RkSummaryVO> getTypeSummary();
/**
* 县局汇总(未出库)
* 首页统计
*/
List<RkSummaryVO> getCountySummary();
HomeStatVO getHomeStatistics(HomeStatQueryDTO query);
/**
* 一周天级统计(一个接口返回入库+出库)
* @param start 可空yyyy-MM-dd
* @param end 可空yyyy-MM-dd
* @return { "in": List<DayCountyStatVO>, "out": List<DayCountyStatVO> }
* 按时间查询入库类型统计
*/
Map<String, Object> weekDaily(String start, String end);
List<StockStatisticGroupVO> getInTypeByTime(HomeStatQueryDTO query);
/**
* 库龄统计(>30天 & >60天项目数、条目数、总金额 + 两个明细列表
* 按时间查询出库类型统计
*/
Age3060ResultVO getAge3060Detail();
List<Map<String, Object>> getAge3060();
/** 导出库龄明细(>30 与 >60 合并导出) */
void exportAge3060(HttpServletResponse response);
List<StockStatisticGroupVO> getOutTypeByTime(HomeStatQueryDTO query);
/**
* 获取入库类型饼状图数据
* @return 入库类型饼图统计列表
*/
List<RkTypePieVO> getTypePie();
* 首页库龄统计接口
* */
List<StockAgeStatVO> selectStockAgeStat();
/**
* 按入库类型统计(时间范围)
* @param start 开始日期(建议 yyyy-MM-dd
* @param end 结束日期(建议 yyyy-MM-dd
* @return 每个入库类型的项目数、条目数、总数量、总金额
*/
List<RkTypePieVO> getTypeSummaryByRange(String start, String end);
* 首页待办事项接口
* */
List<TodoStatVO> selectTodoStat();
/**
* 按出库类型统计(时间范围)
* 口径仅统计已出库is_chuku = 1排除删除出库类型非空
* 时间字段ly_time自然日闭区间 [start, end],参数建议 yyyy-MM-dd
* 小仓库位使用情况统计
*/
List<RkTypePieVO> getOutTypeSummaryByRange(String start, String end);
/** 本月入/出库汇总(项目数/条目数/总数量/总金额) */
RkMonthInOutSummaryVO getThisMonthInOutSummary();
/** 供应计划:应到未到统计(未到、部分未到、合计) */
GysJhUndeliveredVO getUndeliveredSummary();
/** 当前库存:按物资类型统计 */
List<WlTypeStockStatVO> getCurrentStockByWlType();
/** 当前库存 “入库时间 >30 天”的四项汇总 */
RkTypePieVO getAgeGt30Summary();
List<WarehouseSlotStatVO> selectWarehouseSlotStat();
/**
* range: 0=最近一周(日)2=最近半年(月)
* 返回每个时间桶的一行数据(固定 7 行或 6 行)
* 库龄>=30天明细用于导出
*/
List<IOBucketVO> getIOBuckets(Integer range);
/**
* 根据仓库编码,返回各场景的可用库位(含数量与明细)
* 规则:排除 rk_info 中未出库is_chuku=0 或 NULL的占用库位
*/
List<SceneAvailableVO> listAvailableByWarehouse(String warehouseCode);
/**
* 库存统计
* @param dto 查询参数
* @return { "total": 0, "rows": List<StockStatVO> }
*/
// Map<String, Object> selectStockStatistics(RkInfoQueryDTO dto);
List<StockAgeExportVO> selectStockAgeExport30();
}

View File

@@ -20,6 +20,7 @@ import com.zg.project.wisdom.mapper.AgvWcsMapper;
import com.zg.project.wisdom.mapper.DdTaskMapper;
import com.zg.project.wisdom.mapper.WcsTaskResultMapper;
import com.zg.project.wisdom.service.IDdTaskService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@@ -27,13 +28,12 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import static com.zg.framework.datasource.DynamicDataSourceContextHolder.log;
/**
* 调度任务 Service 实现类
*
* @author zg
*/
@Slf4j
@Service
public class DdTaskServiceImpl implements IDdTaskService {

View File

@@ -10,6 +10,8 @@ import com.zg.common.utils.SecurityUtils;
import com.zg.common.utils.StringUtils;
import com.zg.framework.web.domain.AjaxResult;
import com.zg.project.wisdom.domain.dto.ExcelFieldMapping;
import com.zg.project.wisdom.mapper.RkInfoMapper;
import com.zg.project.wisdom.mapper.RkRecordMapper;
import org.apache.poi.ss.usermodel.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -30,7 +32,11 @@ public class GysJhServiceImpl implements IGysJhService
{
@Autowired
private GysJhMapper gysJhMapper;
@Autowired
private RkInfoMapper rkInfoMapper;
@Autowired
private RkRecordMapper rkRecordMapper;
/**
* 查询供应计划
*
@@ -78,7 +84,24 @@ public class GysJhServiceImpl implements IGysJhService
public int updateGysJh(GysJh gysJh)
{
gysJh.setUpdateTime(DateUtils.getNowDate());
return gysJhMapper.updateGysJh(gysJh);
int rows = gysJhMapper.updateGysJh(gysJh);
// 2⃣ 同步更新 rk_info
rkInfoMapper.updateDw(
gysJh.getSapNo(),
gysJh.getWlNo(),
gysJh.getXmNo(),
gysJh.getDw()
);
// 3⃣ 同步更新 rk_record
rkRecordMapper.updateDw(
gysJh.getSapNo(),
gysJh.getWlNo(),
gysJh.getXmNo(),
gysJh.getDw()
);
return rows;
}
/**

View File

@@ -120,7 +120,6 @@ public class MoveRecordServiceImpl implements IMoveRecordService
@Transactional(rollbackFor = Exception.class)
public void processMove(MoveRequestDTO dto) {
/* ====================== 0. 基础校验 ====================== */
if (dto == null || dto.getFromRkId() == null) {
throw new ServiceException("原库存ID不能为空");
}
@@ -151,16 +150,18 @@ public class MoveRecordServiceImpl implements IMoveRecordService
String userId = String.valueOf(SecurityUtils.getUserId());
Date now = DateUtils.getNowDate();
/* ====================== 3. 原库存快照 ====================== */
/* ====================== 3. 查询上一条移库记录 ====================== */
Long lastMoveId = moveRecordMapper.selectLastMoveIdByRkInfoId(dto.getFromRkId());
/* ====================== 4. 原库存快照 ====================== */
RkInfo snapshot = new RkInfo();
BeanUtils.copyProperties(original, snapshot);
/* ====================== 4. 全量移库(不拆分) ====================== */
/* ====================== 5. 全量移库 ====================== */
if (dto.getTargets().size() == 1 && moveTotalQty.compareTo(originQty) == 0) {
MoveTargetItem target = dto.getTargets().get(0);
// 4.1 更新原库存(仅换库位)
original.setCangku(target.getToCangku());
original.setPcode(target.getToPcode());
original.setTrayCode(target.getToTrayCode());
@@ -169,10 +170,13 @@ public class MoveRecordServiceImpl implements IMoveRecordService
original.setUpdateTime(now);
rkInfoMapper.updateRkInfo(original);
// 4.2 移库流水(全量移库,无新库存)
MoveRecord record = new MoveRecord();
record.setRkInfoId(snapshot.getId());
record.setNewRkInfoId(null); // ✅ 关键:全量移库没有新库存
record.setNewRkInfoId(null);
// ★★ 新增关键 ★★
record.setParentMoveId(lastMoveId);
record.setEntityId(snapshot.getEntityId());
record.setRealQty(originQty);
@@ -198,16 +202,14 @@ public class MoveRecordServiceImpl implements IMoveRecordService
return;
}
/* ====================== 5. 部分移库(拆分库存) ====================== */
/* ====================== 6. 部分移库 ====================== */
// 5.1 原库存扣减数量
original.setRealQty(originQty.subtract(moveTotalQty));
original.setHasMoved("1");
original.setUpdateBy(userId);
original.setUpdateTime(now);
rkInfoMapper.updateRkInfo(original);
// 5.2 生成新库存 + 移库流水
for (MoveTargetItem target : dto.getTargets()) {
/* ---------- 新库存 ---------- */
@@ -227,10 +229,14 @@ public class MoveRecordServiceImpl implements IMoveRecordService
rkInfoMapper.insertRkInfo(newInfo);
/* ---------- 移库流水(重点) ---------- */
/* ---------- 移库流水 ---------- */
MoveRecord record = new MoveRecord();
record.setRkInfoId(snapshot.getId()); // 原库存
record.setNewRkInfoId(newInfo.getId()); // ✅ 关键新库存ID
record.setRkInfoId(snapshot.getId());
record.setNewRkInfoId(newInfo.getId());
// ★★ 新增关键 ★★
record.setParentMoveId(lastMoveId);
record.setEntityId(snapshot.getEntityId());
record.setRealQty(target.getRealQty());
@@ -273,10 +279,16 @@ public class MoveRecordServiceImpl implements IMoveRecordService
throw new ServiceException("移库记录不存在或已撤销");
}
/* ====================== 2. 关键:校验是否有子移库 ====================== */
int childCount = moveRecordMapper.countByParentMoveId(moveRecordId);
if (childCount > 0) {
throw new ServiceException("该移库已产生后续移库,请先撤销后续移库");
}
Long originRkInfoId = record.getRkInfoId();
Long newRkInfoId = record.getNewRkInfoId();
/* ====================== 2. 查询原库存 ====================== */
/* ====================== 3. 查询原库存 ====================== */
RkInfo origin = rkInfoMapper.selectRkInfoById(originRkInfoId);
if (origin == null || "1".equals(origin.getIsDelete())) {
throw new ServiceException("原库存不存在或已删除,无法撤销");
@@ -285,12 +297,11 @@ public class MoveRecordServiceImpl implements IMoveRecordService
String userId = String.valueOf(SecurityUtils.getUserId());
Date now = DateUtils.getNowDate();
/* ====================== 3. 分情况处理 ====================== */
/* ====================== 4. 分情况处理 ====================== */
// ---------- 情况一:全量移库 ----------
// -------- 全量移库 --------
if (newRkInfoId == null) {
// 回滚库位
origin.setCangku(record.getFromCangku());
origin.setPcode(record.getFromPcode());
origin.setTrayCode(record.getFromTrayCode());
@@ -299,25 +310,31 @@ public class MoveRecordServiceImpl implements IMoveRecordService
origin.setUpdateTime(now);
rkInfoMapper.updateRkInfo(origin);
}
// ---------- 情况二:部分移库 ----------
// -------- 部分移库 --------
else {
/* 3.1 原库存回补数量 */
BigDecimal qty = record.getRealQty();
if (qty == null || qty.compareTo(BigDecimal.ZERO) <= 0) {
throw new ServiceException("移库数量异常,无法撤销");
}
// 回补数量
origin.setRealQty(origin.getRealQty().add(qty));
// 恢复位置
origin.setCangku(record.getFromCangku());
origin.setPcode(record.getFromPcode());
origin.setTrayCode(record.getFromTrayCode());
origin.setHasMoved("0");
origin.setUpdateBy(userId);
origin.setUpdateTime(now);
origin.setHasMoved("0");
rkInfoMapper.updateRkInfo(origin);
/* 3.2 删除移库生成的新库存 */
// 删除新库存
RkInfo newInfo = rkInfoMapper.selectRkInfoById(newRkInfoId);
if (newInfo != null && !"1".equals(newInfo.getIsDelete())) {
newInfo.setIsDelete("1");
@@ -327,10 +344,11 @@ public class MoveRecordServiceImpl implements IMoveRecordService
}
}
/* ====================== 4. 标记移库记录已撤销 ====================== */
/* ====================== 5. 标记移库记录已撤销 ====================== */
record.setIsDelete("1");
record.setUpdateBy(userId);
record.setUpdateTime(now);
moveRecordMapper.updateMoveRecord(record);
}
}

View File

@@ -1,6 +1,7 @@
package com.zg.project.wisdom.service.impl;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -18,6 +19,7 @@ import com.zg.project.wisdom.domain.RkRecord;
import com.zg.project.wisdom.domain.dto.BorrowReturnDTO;
import com.zg.project.wisdom.domain.dto.BorrowReturnItemDTO;
import com.zg.project.wisdom.domain.dto.RkBillCreateDTO;
import com.zg.project.wisdom.domain.vo.DeliveryBillVO;
import com.zg.project.wisdom.mapper.GysJhMapper;
import com.zg.project.wisdom.mapper.RkInfoMapper;
import com.zg.project.wisdom.mapper.RkRecordMapper;
@@ -100,7 +102,7 @@ public class RkBillServiceImpl implements IRkBillService
throw new RuntimeException("入库明细不能为空");
}
String billNo = BillNoUtil.generateTodayBillNo("RK", null);
String billNo = BillNoUtil.generateTodayBillNo("RK", rkInfoMapper);
Date now = DateUtils.getNowDate();
String userId = String.valueOf(SecurityUtils.getUserId());
@@ -131,14 +133,20 @@ public class RkBillServiceImpl implements IRkBillService
for (RkInfo info : rkInfoList) {
info.setExecStatus(bill.getExecStatus());
String scene = "";
// 库位校验
if (StringUtils.isNotBlank(info.getPcode())) {
PcdeDetail pcde = pcdeDetailMapper.selectByPcode(info.getPcode());
PcdeDetail pcde = pcdeDetailMapper.selectByPcodeAndWarehouse(
info.getPcode(),
bill.getCangku()
);
if (pcde == null) {
throw new RuntimeException("库位不存在:" + info.getPcode());
}
info.setPcodeId(pcde.getEncodedId());
if(StringUtils.isNotBlank(pcde.getScene())){
scene = pcde.getScene();
}
}
// rk_info
@@ -154,7 +162,7 @@ public class RkBillServiceImpl implements IRkBillService
info.setHasMoved("0");
info.setIsBorrowed("0");
info.setIsDelete("0");
info.setScene(scene);
info.setCreateTime(now);
info.setCreateBy(userId);
@@ -329,38 +337,54 @@ public class RkBillServiceImpl implements IRkBillService
throw new ServiceException("单据不存在:" + billNo);
}
/* ================== 2执行状态规则 ================== */
/* ================== 2取前端选择仓库 ================== */
String frontCangku = dto.getRkBill().getCangku();
if (StringUtils.isBlank(frontCangku)) {
frontCangku = bill.getCangku();
}
/* ================== 3⃣ 执行状态 ================== */
String execStatus = dto.getRkBill().getExecStatus();
if (StringUtils.isBlank(execStatus)) {
execStatus = bill.getExecStatus();
}
// 标记:本次是否包含预入库
boolean hasPreIn = false;
List<RkInfo> rkInfoList = dto.getRkInfoList();
/* ================== 3️⃣ 追加前:供应计划【批量】校验 ================== */
/* ================== 4️⃣ 供应计划校验 ================== */
checkGysJhQtyBeforeInStockBatch(rkInfoList);
/* ================== 4️⃣ 插入明细 & 事件 ================== */
/* ================== 5️⃣ 追加明细 ================== */
for (RkInfo info : rkInfoList) {
/* 4.1 库位校验 */
/* 5.1 库位校验 → 用【前端仓库】 */
if (StringUtils.isNotBlank(info.getPcode())) {
PcdeDetail pcde = pcdeDetailMapper.selectByPcode(info.getPcode());
PcdeDetail pcde = pcdeDetailMapper.selectByPcodeAndWarehouse(
info.getPcode(),
frontCangku
);
if (pcde == null) {
throw new ServiceException("库位不存在:" + info.getPcode());
throw new ServiceException(
"库位不存在或不属于当前仓库:"
+ info.getPcode()
+ ",仓库:" + frontCangku
);
}
info.setPcodeId(pcde.getEncodedId());
info.setScene(pcde.getScene());
}
/* 4.2 继承主单字段 */
/* 5.2 继承主单字段 */
info.setBillNo(bill.getBillNo());
info.setOperationType(bill.getOperationType());
info.setBizType(bill.getBizType());
info.setWlType(bill.getWlType());
info.setCangku(bill.getCangku());
info.setCangku(frontCangku);
info.setOperationTime(now);
info.setOperator(bill.getOperator());
@@ -374,36 +398,36 @@ public class RkBillServiceImpl implements IRkBillService
info.setHasMoved("0");
info.setIsBorrowed("0");
/* 4.3 备注兜底 */
String finalRemark = StringUtils.isNotBlank(info.getRemark())
? info.getRemark()
: bill.getRemark();
info.setRemark(finalRemark);
/* 4.4 审计字段 */
info.setCreateTime(now);
info.setCreateBy(bill.getCreateBy());
info.setIsDelete("0");
/* 4.5 插入 rk_info */
/* 5.3 插入 rk_info */
rkInfoMapper.insertRkInfo(info);
/* 4.6 插入 rk_record */
/* 5.4 插入 rk_record */
RkRecord record = buildInRkRecord(bill, info, now);
record.setExecStatus(execStatus);
record.setRkInfoId(info.getId());
record.setPcodeId(info.getPcodeId());
record.setRemark(finalRemark);
record.setCangku(frontCangku);
rkRecordMapper.insertRkRecord(record);
}
/* ================== 5️⃣ 追加后:供应计划【仅完成入库】 ================== */
/* ================== 6️⃣ 供应计划回写 ================== */
if ("1".equals(execStatus)) {
handleGysJhAfterInStockBatch(rkInfoList);
}
/* ================== 6️⃣ 同步回退主单状态 ================== */
/* ================== 7️⃣ 主单状态回退 ================== */
if (hasPreIn && !"0".equals(bill.getExecStatus())) {
rkBillMapper.updateExecStatusByBillNo(billNo, "0");
}
@@ -418,11 +442,49 @@ public class RkBillServiceImpl implements IRkBillService
* @return 结果
*/
@Override
public int updateRkBill(RkBill rkBill)
{
return rkBillMapper.updateRkBill(rkBill);
@Transactional(rollbackFor = Exception.class)
public int updateRkBill(RkBill rkBill) {
if (rkBill == null || StringUtils.isBlank(rkBill.getBillNo())) {
throw new ServiceException("单据号不能为空");
}
// ====================== 1. 更新主单 rk_bill ======================
int rows = rkBillMapper.updateRkBill(rkBill);
// ====================== 2. 同步更新对应 record 明细 ======================
RkRecord updateRecord = new RkRecord();
// 以 billNo 为条件
updateRecord.setBillNo(rkBill.getBillNo());
// ---- 需要同步的关键字段 ----
updateRecord.setOperationType(rkBill.getOperationType());
updateRecord.setTeamCode(rkBill.getTeamCode());
updateRecord.setOperator(rkBill.getOperator());
updateRecord.setIsDelivery(rkBill.getIsDelivery());
// 执行批量更新
rkRecordMapper.updateRecordByBillNo(updateRecord);
// ====================== 3. 同步更新 rk_info ======================
RkInfo updateInfo = new RkInfo();
updateInfo.setBillNo(rkBill.getBillNo());
// 同步字段
updateInfo.setUpdateTime(DateUtils.getNowDate());
updateInfo.setOperationType(rkBill.getOperationType());
updateInfo.setTeamCode(rkBill.getTeamCode());
updateInfo.setOperator(rkBill.getOperator());
updateInfo.setIsDelivery(rkBill.getIsDelivery());
rkInfoMapper.updateInfoByBillNo(updateInfo);
return rows;
}
/**
* 批量删除库存单据
*
@@ -494,11 +556,11 @@ public class RkBillServiceImpl implements IRkBillService
throw new RuntimeException("出库明细不能为空");
}
String billNo = BillNoUtil.generateTodayBillNo("CK", null);
String billNo = BillNoUtil.generateTodayBillNo("CK", rkInfoMapper);
Date now = DateUtils.getNowDate();
String userId = String.valueOf(SecurityUtils.getUserId());
// ================== 1. 主单 rk_bill ==================
/* ================== 1. 主单 rk_bill ================== */
RkBill bill = new RkBill();
if (dto.getRkBill() != null) {
BeanUtils.copyProperties(dto.getRkBill(), bill);
@@ -525,16 +587,14 @@ public class RkBillServiceImpl implements IRkBillService
boolean isPreOut = "0".equals(bill.getExecStatus());
// ===== 借料出库字段 =====
// 借料出库字段
if ("2".equals(bill.getBizType())) {
bill.setBorrowTime(bill.getBorrowTime() != null ? bill.getBorrowTime() : now);
bill.setXmNoCk(bill.getXmNoCk());
bill.setXmMsCk(bill.getXmMsCk());
}
rkBillMapper.insertRkBill(bill);
// ================== 2. 出库处理 ==================
/* ================== 2. 出库处理 ================== */
for (RkInfo outInfo : dto.getRkInfoList()) {
if (outInfo.getId() == null) {
@@ -556,18 +616,17 @@ public class RkBillServiceImpl implements IRkBillService
throw new RuntimeException("出库数量不能大于库存数量库存ID" + dbInfo.getId());
}
// ===== ① 非预出库才扣库存 =====
// ① 非预出库才扣库存
if (!isPreOut) {
BigDecimal newQty = remainQty.subtract(outQty);
rkInfoMapper.updateRealQtyById(dbInfo.getId(), newQty);
rkInfoMapper.updateIsChukuById(
dbInfo.getId(),
newQty.compareTo(BigDecimal.ZERO) == 0 ? "1" : "0"
);
}
// ===== ② 出库记录(重点:传 outInfo=====
// ② 出库记录(已修复 cangku
RkRecord record = buildOutRkRecord(bill, dbInfo, outInfo, outQty, now);
record.setExecStatus(isPreOut ? "0" : "1");
@@ -577,6 +636,7 @@ public class RkBillServiceImpl implements IRkBillService
return 1;
}
private RkRecord buildOutRkRecord(
RkBill bill,
RkInfo dbInfo,
@@ -586,26 +646,30 @@ public class RkBillServiceImpl implements IRkBillService
RkRecord record = new RkRecord();
// ① 库存快照
/* ① 库存快照 */
BeanUtils.copyProperties(dbInfo, record);
record.setId(null);
// ② 本次出库数量
/* ② 本次出库数量 */
record.setRealQty(outQty);
// ③ 主单上下文
/* ③ 主单上下文 */
record.setBillNo(bill.getBillNo());
record.setBizType(bill.getBizType());
record.setOperationType(bill.getOperationType());
record.setOperationTime(bill.getOperationTime());
record.setOperator(bill.getOperator());
record.setCangku(bill.getCangku());
record.setTeamCode(bill.getTeamCode());
record.setIsDelivery(bill.getIsDelivery());
// ===== ★ 关键:备注来自【出库明细 outInfo】=====
/* ================== ✅ 关键修复 ================== */
// 仓库必须来源于【库存快照】,而不是 bill
record.setCangku(dbInfo.getCangku());
/* ④ 备注来自出库明细 */
record.setRemark(outInfo.getRemark());
// ④ 借料专属字段
/* ⑤ 借料字段 */
if ("2".equals(bill.getBizType())) {
record.setIsBorrowed("1");
record.setBorrowTime(bill.getBorrowTime());
@@ -616,17 +680,17 @@ public class RkBillServiceImpl implements IRkBillService
record.setIsBorrowed("0");
}
// ⑤ 状态字段
/* ⑥ 状态字段 */
record.setIsChuku("1");
record.setHasMoved("0");
record.setIsDelete("0");
record.setExecStatus(bill.getExecStatus());
// ⑥ 审计字段
/* ⑦ 审计字段 */
record.setCreateTime(now);
record.setCreateBy(bill.getCreateBy());
// ⑦ 关联库存
/* ⑧ 关联库存 */
record.setRkInfoId(dbInfo.getId());
record.setRdid(dbInfo.getId());
@@ -695,4 +759,30 @@ public class RkBillServiceImpl implements IRkBillService
return dto.getItems().size();
}
@Override
public List<DeliveryBillVO> selectDeliveryBillList(RkBill query) {
// 1. 查询出库单据
List<RkBill> billList = rkBillMapper.selectRkBillList(query);
List<DeliveryBillVO> result = new ArrayList<>();
for (RkBill bill : billList) {
DeliveryBillVO vo = new DeliveryBillVO();
BeanUtils.copyProperties(bill, vo);
// 2. 查询该单据对应的record明细
List<RkRecord> records =
rkRecordMapper.selectByBillNo(bill.getBillNo());
vo.setRecordList(records);
System.out.println("单据详情=="+records);
result.add(vo);
}
System.out.println("反序列化前的list=="+result);
return result;
}
}

View File

@@ -1,7 +1,22 @@
package com.zg.project.wisdom.service.impl;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import com.zg.common.exception.ServiceException;
import com.zg.common.utils.DateUtils;
import com.zg.common.utils.StringUtils;
import com.zg.project.inventory.AutoInventory.mapper.InventoryMatchScanMapper;
import com.zg.project.inventory.Task.mapper.InventoryTaskMapper;
import com.zg.project.inventory.domain.dto.QueryDTO;
import com.zg.project.inventory.domain.entity.InventoryMatchScan;
import com.zg.project.inventory.domain.entity.InventoryTask;
import com.zg.project.inventory.domain.vo.ChartDataVO;
import com.zg.project.inventory.domain.vo.PcdeCntVO;
import com.zg.project.wisdom.domain.vo.StockStatisticVO;
import com.zg.project.wisdom.mapper.RkRecordMapper;
import org.springframework.beans.factory.annotation.Autowired;
@@ -26,6 +41,12 @@ public class RkInfoServiceImpl implements IRkInfoService
@Autowired
private RkRecordMapper rkRecordMapper;
@Autowired
private InventoryTaskMapper taskMapper;
@Autowired
private InventoryMatchScanMapper matchScanMapper;
/**
* 查询库存单据明细
*
@@ -112,4 +133,232 @@ public class RkInfoServiceImpl implements IRkInfoService
public StockStatisticVO getStockStatistic(RkInfo query) {
return rkInfoMapper.selectStockStatisticByCondition(query);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void matchWithStatus(QueryDTO dto) {
/* ================== 1. 基础参数校验 ================== */
List<String> pcdeIds = dto.getIds();
if (pcdeIds == null || pcdeIds.isEmpty()) {
return;
}
String taskId = dto.getTaskId();
String sceneId = taskMapper.selectSceneIdById(taskId);
if (sceneId == null || sceneId.trim().isEmpty()) {
throw new ServiceException("匹配失败缺少场景ID");
}
int scanType = dto.getScanType() != null ? dto.getScanType() : 0;
String deviceId = dto.getDeviceId();
if (scanType == 1 && StringUtils.isBlank(deviceId)) {
throw new ServiceException("自动盘点必须传递设备ID");
}
/* ================== 2. 三类盘点数据匹配 ================== */
// 1⃣ 正常:扫描到 & 当前库存存在
List<RkInfo> matchedAll =
rkInfoMapper.getByPcodeIdList(pcdeIds, sceneId);
matchedAll.forEach(r -> r.setStatus("0"));
// 2⃣ 未盘:库存中存在但未扫描
List<RkInfo> missedAll =
rkInfoMapper.getMissedPcodeIds(pcdeIds, sceneId);
missedAll.forEach(r -> r.setStatus("1"));
// 3⃣ 异常:扫描到但库存中不存在(批量)
List<String> errorIds =
rkInfoMapper.getNotExistsPcodeIds(pcdeIds, sceneId);
List<RkInfo> errorAll = errorIds.stream().map(id -> {
RkInfo r = new RkInfo();
r.setPcodeId(id);
r.setStatus("2");
return r;
}).collect(Collectors.toList());
/* ================== 3. 扫描结果落库 ================== */
String tmeStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
List<InventoryMatchScan> toSave = new ArrayList<>();
for (RkInfo r : matchedAll) {
toSave.add(buildScanRecord(taskId, r.getPcode(), "0", deviceId, tmeStr, scanType));
}
for (RkInfo r : missedAll) {
toSave.add(buildScanRecord(taskId, r.getPcode(), "1", deviceId, tmeStr, scanType));
}
for (RkInfo r : errorAll) {
toSave.add(buildScanRecord(taskId, r.getPcodeId(), "2", deviceId, tmeStr, scanType));
}
if (!toSave.isEmpty()) {
matchScanMapper.insertBatch(toSave);
}
/* ================== 4. 更新任务状态 ================== */
taskMapper.updateStatus(taskId, "1");
}
/**
* 封装一个盘点扫描记录
*
* @param taskId 任务 ID
* @param pcode 库位编码 ID
* @param status 盘点状态0=匹配成功1=未盘点2=错误)
* @param deviceId 设备 ID自动盘点时才有
* @param tme 扫描时间
* @param scanType 扫描类型0=手动盘点1=自动盘点)
* @return 封装好的 InventoryMatchScan 对象
*/
private InventoryMatchScan buildScanRecord(String taskId, String pcode, String status,
String deviceId, String tme, int scanType) {
InventoryMatchScan record = new InventoryMatchScan();
record.setPcode(pcode);
record.setTaskId(taskId);
record.setStatus(status);
// 自动盘点时才保存设备 ID
if (scanType == 1 && deviceId != null) {
record.setDeviceId(deviceId);
}
record.setTme(tme);
record.setScanType(scanType);
return record;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void matchPure(QueryDTO dto) {
/* ================== 1. 取任务信息 ================== */
String taskId = dto.getTaskId();
InventoryTask task = taskMapper.selectInventoryTaskById(Long.valueOf(taskId));
if (task == null) {
throw new ServiceException("任务不存在");
}
String sceneId = task.getSceneId();
String warehouseCode = task.getWarehouseCode();
if (StringUtils.isBlank(sceneId)) {
throw new ServiceException("缺少场景ID");
}
if (StringUtils.isBlank(warehouseCode)) {
throw new ServiceException("缺少仓库信息");
}
int scanType = dto.getScanType() != null ? dto.getScanType() : 0;
String deviceId = dto.getDeviceId();
if (scanType == 1 && StringUtils.isBlank(deviceId)) {
throw new ServiceException("自动盘点必须传递设备ID");
}
/* =====================================================
* 2. 🚀 核心逻辑(你要求的口径)
* → 只查 rk_info
* → 不再做任何 pcodeId 匹配
* ===================================================== */
List<RkInfo> normalAll =
rkInfoMapper.getByWarehouseAndScene(warehouseCode, sceneId);
/* 全部视为正常 */
String tmeStr =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
List<InventoryMatchScan> toSave = new ArrayList<>();
for (RkInfo r : normalAll) {
InventoryMatchScan scan = new InventoryMatchScan();
scan.setTaskId(taskId);
scan.setPcode(r.getPcode());
// 全部都是正常
scan.setStatus("0");
scan.setDeviceId(deviceId);
scan.setTme(tmeStr);
scan.setScanType(scanType);
toSave.add(scan);
}
/* 批量写入 */
if (!toSave.isEmpty()) {
matchScanMapper.insertBatch(toSave);
}
/* ================== 5. 完成任务 ================== */
taskMapper.updateStatus(taskId, "1");
}
/**
* 图表统计:每个库位有多少个货物
* @param dto
* @return
*/
@Override
public ChartDataVO matchWithAll(QueryDTO dto) {
List<String> ids = dto.getIds();
if (ids == null || ids.isEmpty()) {
return new ChartDataVO(Collections.emptyList(),
Collections.emptyList(),
0L);
}
List<PcdeCntVO> rows = rkInfoMapper.selectPcdeCntFromRkInfo(ids);
List<String> pcdeList = new ArrayList<>(rows.size());
List<Long> fycdeCntList = new ArrayList<>(rows.size());
for (PcdeCntVO r : rows) {
pcdeList.add(r.getPcde());
fycdeCntList.add(r.getCnt());
}
return new ChartDataVO(pcdeList, fycdeCntList, (long) rows.size());
}
@Override
public int countGetByWh(String warehouse) {
return rkInfoMapper.countGetByWh(warehouse);
}
@Override
public List<RkInfo> listRkInfoByPcode(String pcode) {
return rkInfoMapper.listRkInfoByPcode(pcode);
}
@Override
public List<RkInfo> selectRkInfoListOrderByBillNo(RkInfo rkInfo)
{
return rkInfoMapper.selectRkInfoListOrderByBillNo(rkInfo);
}
@Override
public List<RkInfo> listRkInfoByPcodes(ArrayList<String> strings) {
return rkInfoMapper.listRkInfoByPcodes(strings);
}
@Override
public int countByWarehouseAndScene(String warehouseCode, String sceneId)
{
return rkInfoMapper.countByWarehouseAndScene(warehouseCode, sceneId);
}
}

View File

@@ -7,9 +7,12 @@ import java.util.stream.Collectors;
import com.zg.common.exception.ServiceException;
import com.zg.common.utils.DateUtils;
import com.zg.common.utils.StringUtils;
import com.zg.project.information.domain.PcdeDetail;
import com.zg.project.information.mapper.PcdeDetailMapper;
import com.zg.project.wisdom.domain.GysJh;
import com.zg.project.wisdom.domain.RkInfo;
import com.zg.project.wisdom.domain.vo.RecordStatisticVO;
import com.zg.project.wisdom.domain.vo.StockStatisticVO;
import com.zg.project.wisdom.mapper.GysJhMapper;
import com.zg.project.wisdom.mapper.RkBillMapper;
import com.zg.project.wisdom.mapper.RkInfoMapper;
@@ -42,6 +45,8 @@ public class RkRecordServiceImpl implements IRkRecordService
@Autowired
private GysJhMapper gysJhMapper;
@Autowired
private PcdeDetailMapper pcdeDetailMapper;
/**
* 查询出入库记录
*
@@ -120,13 +125,13 @@ public class RkRecordServiceImpl implements IRkRecordService
rkRecord.setUpdateTime(DateUtils.getNowDate());
/* ====================== 2. 查询原记录(唯一可信来源) ====================== */
/* ====================== 2. 查询原记录 ====================== */
RkRecord oldRecord = rkRecordMapper.selectRkRecordById(rkRecord.getId());
if (oldRecord == null) {
throw new RuntimeException("原出入库记录不存在");
}
/* ====================== 3. 获取并校验库存ID(必须来自 oldRecord ====================== */
/* ====================== 3. 获取库存ID ====================== */
Long rkInfoId = oldRecord.getRkInfoId();
if (rkInfoId == null) {
throw new RuntimeException("出入库记录未关联库存,禁止修改");
@@ -140,7 +145,7 @@ public class RkRecordServiceImpl implements IRkRecordService
String bizType = rkRecord.getBizType();
/* =========================================================
* 【A】入库记录修改bizType = 0
* 【A】入库记录修改
* ========================================================= */
if ("0".equals(bizType)) {
@@ -154,7 +159,6 @@ public class RkRecordServiceImpl implements IRkRecordService
BigDecimal diff = newInQty.subtract(oldInQty);
/* ---------- 供应计划处理 ---------- */
Long gysJhId = oldRecord.getGysJhId();
if (gysJhId != null) {
@@ -177,11 +181,11 @@ public class RkRecordServiceImpl implements IRkRecordService
gysJh.setRealQty(newPlanRealQty);
if (newPlanRealQty.compareTo(BigDecimal.ZERO) == 0) {
gysJh.setStatus("0"); // 未到货
gysJh.setStatus("0");
} else if (planQty != null && newPlanRealQty.compareTo(planQty) >= 0) {
gysJh.setStatus("1"); // 已入库
gysJh.setStatus("1");
} else {
gysJh.setStatus("2"); // 部分入库
gysJh.setStatus("2");
}
gysJh.setWlNo(rkRecord.getWlNo());
@@ -197,7 +201,25 @@ public class RkRecordServiceImpl implements IRkRecordService
gysJhMapper.updateGysJh(gysJh);
}
/* ---------- 同步库存(明确字段,禁止 BeanUtils ---------- */
String scene = "";
if (StringUtils.isNotBlank(info.getPcode())) {
PcdeDetail pcde = pcdeDetailMapper.selectByPcodeAndWarehouse(
rkRecord.getPcode(),
rkRecord.getCangku()
);
if (pcde == null) {
throw new RuntimeException("库位不存在:" + info.getPcode());
}
info.setPcodeId(pcde.getEncodedId());
if (StringUtils.isNotBlank(pcde.getScene())) {
scene = pcde.getScene();
info.setScene(scene);
}
}
info.setRealQty(newInQty);
info.setWlNo(rkRecord.getWlNo());
info.setWlMs(rkRecord.getWlMs());
@@ -209,30 +231,29 @@ public class RkRecordServiceImpl implements IRkRecordService
info.setHtDj(rkRecord.getHtDj());
info.setCangku(rkRecord.getCangku());
info.setPcode(rkRecord.getPcode());
info.setRemark(rkRecord.getRemark());
info.setIsChuku("0");
}
/* =========================================================
* 【B】出库记录修改bizType = 1
* 【B】出库记录修改修改逻辑
* ========================================================= */
if ("1".equals(bizType)) {
BigDecimal oldOutQty = oldRecord.getRealQty();
BigDecimal newOutQty = rkRecord.getRealQty();
BigDecimal stockQty = info.getRealQty();
if (oldOutQty == null || newOutQty == null || stockQty == null) {
if (newOutQty == null || stockQty == null) {
throw new RuntimeException("数量数据异常,无法修改出库记录");
}
BigDecimal diff = newOutQty.subtract(oldOutQty);
BigDecimal newStockQty = stockQty.subtract(diff);
/* 关键修改:直接用 newOutQty 计算库存 */
BigDecimal newStockQty = stockQty.subtract(newOutQty);
if (newStockQty.compareTo(BigDecimal.ZERO) < 0) {
throw new RuntimeException("库存不足,无法修改出库数量");
}
info.setRealQty(newStockQty);
info.setIsChuku(newStockQty.compareTo(BigDecimal.ZERO) == 0 ? "1" : "0");
info.setWlNo(rkRecord.getWlNo());
@@ -245,6 +266,7 @@ public class RkRecordServiceImpl implements IRkRecordService
info.setHtDj(rkRecord.getHtDj());
info.setCangku(rkRecord.getCangku());
info.setPcode(rkRecord.getPcode());
info.setRemark(rkRecord.getRemark());
}
/* ====================== 4. 更新库存表 ====================== */
@@ -254,8 +276,8 @@ public class RkRecordServiceImpl implements IRkRecordService
throw new RuntimeException("同步更新库存表失败");
}
/* ====================== 5. 最后更新 rk_record ====================== */
rkRecord.setRkInfoId(rkInfoId); // 强制回填,防止被置空
/* ====================== 5. 更新记录表 ====================== */
rkRecord.setRkInfoId(rkInfoId);
int rows = rkRecordMapper.updateRkRecord(rkRecord);
if (rows <= 0) {
throw new RuntimeException("更新出入库记录失败");
@@ -263,6 +285,7 @@ public class RkRecordServiceImpl implements IRkRecordService
return rows;
}
@Override
@Transactional(rollbackFor = Exception.class)
public int deleteRkRecordByIds(Long[] ids) {
@@ -794,12 +817,66 @@ public class RkRecordServiceImpl implements IRkRecordService
return deleteCount;
}
@Override
public RecordStatisticVO getRecordStatistic(RkRecord query) {
RecordStatisticVO vo = new RecordStatisticVO();
vo.setInStatistic(rkRecordMapper.selectInRecordStatistic(query));
vo.setOutStatistic(rkRecordMapper.selectOutRecordStatistic(query));
return vo;
@Override
public StockStatisticVO selectRecordStatisticByCondition(RkRecord query) {
return rkRecordMapper.selectRecordStatisticByCondition(query);
}
@Override
@Transactional(rollbackFor = Exception.class)
public int updateDeliveryStatus(List<Long> ids, Integer isDelivery)
{
if (ids == null || ids.isEmpty()) {
return 0;
}
// ===============================
// 1. 更新 record 状态
// ===============================
int rows =
rkRecordMapper.updateDeliveryStatus(ids, isDelivery);
// ===============================
// 2. 查询受影响 billNo
// ===============================
List<String> billNos =
rkRecordMapper.selectBillNosByRecordIds(ids);
if (billNos == null || billNos.isEmpty()) {
return rows;
}
// ===============================
// 3. 更新 bill 状态
// 规则:
//
// bill状态 = MIN(record.is_delivery)
//
// 示例:
//
// 2,4,4 → 2
// 3,4 → 3
// 4,4 → 4
//
// ===============================
for (String billNo : billNos)
{
Integer billStatus =
rkRecordMapper.selectMinDeliveryStatusByBillNo(billNo);
if (billStatus != null)
{
rkBillMapper.updateDeliveryStatusByBillNo(
billNo,
billStatus
);
}
}
return rows;
}
}

View File

@@ -1,23 +1,14 @@
package com.zg.project.wisdom.service.impl;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.zg.common.exception.ServiceException;
import com.zg.common.utils.DateUtils;
import com.zg.common.utils.StringUtils;
import com.zg.common.utils.poi.ExcelUtil;
import com.zg.project.wisdom.domain.dto.RkInfoQueryDTO;
import com.zg.project.wisdom.domain.dto.HomeStatQueryDTO;
import com.zg.project.wisdom.domain.vo.*;
import com.zg.project.wisdom.mapper.RkInfoMapper;
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 javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.Date;
import java.util.List;
@Service
public class RkStatisticsServiceImpl implements RkStatisticsService {
@@ -25,427 +16,66 @@ public class RkStatisticsServiceImpl implements RkStatisticsService {
@Autowired
private RkStatisticsMapper rkStatisticsMapper;
@Autowired
private RkInfoMapper rkInfoMapper;
@Override
public List<Map<String, Object>> getAgeStatsAsList() {
// 这里的 gt10/gt20/gt30 均为“项目数”COUNT DISTINCT xm_no
RkAgeStatVO vo = rkStatisticsMapper.selectAgeStats();
public HomeStatVO getHomeStatistics(HomeStatQueryDTO query) {
List<Map<String, Object>> list = new ArrayList<>(3);
Map<String, Object> m1 = new HashMap<>();
m1.put("name", ">10天");
m1.put("value", vo.getGt10());
list.add(m1);
HomeStatVO vo = new HomeStatVO();
Map<String, Object> m2 = new HashMap<>();
m2.put("name", ">20天");
m2.put("value", vo.getGt20());
list.add(m2);
// ========== 1. 顶部 KPI ==========
vo.setKpi(rkStatisticsMapper.statHomeKpi());
Map<String, Object> m3 = new HashMap<>();
m3.put("name", ">30天");
m3.put("value", vo.getGt30());
list.add(m3);
// ========== 2. 当前库存统计 ==========
vo.setCurrentInType(rkStatisticsMapper.statCurrentInByOperationType());
vo.setMaterialType(rkStatisticsMapper.statCurrentByMaterialType());
return list;
}
/**
* 库存类型统计(仅未出库)
*/
@Override
public List<RkSummaryVO> getTypeSummary() {
List<RkSummaryVO> list = rkStatisticsMapper.selectTypeSummary();
return CollectionUtils.isEmpty(list) ? Collections.emptyList() : list;
}
/**
* 县局统计(仅未出库)
*/
@Override
public List<RkSummaryVO> getCountySummary() {
List<RkSummaryVO> list = rkStatisticsMapper.selectCountySummary();
return CollectionUtils.isEmpty(list) ? Collections.emptyList() : list;
}
private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@Override
public Map<String, Object> weekDaily(String start, String end) {
// 如果携带了 start 和 end就直接用否则end=今天start=本周周一
if (StringUtils.isBlank(start) || StringUtils.isBlank(end)) {
LocalDate today = LocalDate.now();
LocalDate monday = today.with(DayOfWeek.MONDAY);
start = monday.format(F);
end = today.format(F);
}
Map<String, Object> ret = new HashMap<>(2);
ret.put("in", rkStatisticsMapper.selectWeekDailyIn(start, end));
ret.put("out", rkStatisticsMapper.selectWeekDailyOut(start, end));
return ret;
}
@Override
public Age3060ResultVO getAge3060Detail() {
// 1) 汇总(同时返回 >30 / >60 的项目数、条目数、总金额)
AgeSummaryVO summary = rkStatisticsMapper.selectAge3060Summary();
// 2) 明细(>30
List<RkAgeDetailVO> gt30List = rkStatisticsMapper.selectAgeDetails(30);
// 3) 明细(>60
List<RkAgeDetailVO> gt60List = rkStatisticsMapper.selectAgeDetails(60);
// 4) 组装返回
Age3060ResultVO vo = new Age3060ResultVO();
vo.setGt30(summary.getGt30());
vo.setGt60(summary.getGt60());
vo.setGt30List(gt30List);
vo.setGt60List(gt60List);
return vo;
}
@Override
public List<Map<String, Object>> getAge3060() {
// 1) 汇总(同时返回 >30 / >60 的项目数、条目数、总金额)
AgeSummaryVO summary = rkStatisticsMapper.selectAge3060Summary();
// 2) 用 List<Map> 封装成数组结构
List<Map<String, Object>> list = new ArrayList<>();
Map<String, Object> gt30 = new HashMap<>();
gt30.put("name", ">30天");
gt30.put("value", summary.getGt30().getGoodsCount()); // 这里取“货物数量”作 value
list.add(gt30);
Map<String, Object> gt60 = new HashMap<>();
gt60.put("name", ">60天");
gt60.put("value", summary.getGt60().getGoodsCount());
list.add(gt60);
return list;
}
/**
* 导出库龄明细(>30天 与 >60天 合并导出为一张表)
* - 所有业务逻辑均在 Service 层完成
* - 使用若依 ExcelUtil + @Excel 注解导出
*/
@Override
public void exportAge3060(HttpServletResponse response) {
// 1) 调用已有统计逻辑
Age3060ResultVO res = this.getAge3060Detail();
// 2) 合并两组明细,加上导出用的分组字段
List<RkAgeDetailVO> out = new ArrayList<>();
if (res.getGt30List() != null) {
for (RkAgeDetailVO row : res.getGt30List()) {
normalizeRowForExport(row, ">30天");
out.add(row);
}
}
if (res.getGt60List() != null) {
for (RkAgeDetailVO row : res.getGt60List()) {
normalizeRowForExport(row, ">60天");
out.add(row);
}
}
// 3) 导出 Excel
ExcelUtil<RkAgeDetailVO> util = new ExcelUtil<>(RkAgeDetailVO.class);
util.exportExcel(response, out, "库龄明细");
}
private void normalizeRowForExport(RkAgeDetailVO row, String bucket) {
row.setAgeBucket(bucket);
if (row.getAmount() == null) {
BigDecimal dj = row.getHtDj() == null ? BigDecimal.ZERO : row.getHtDj();
BigDecimal qty = row.getRealQty() == null ? BigDecimal.ZERO : row.getRealQty();
row.setAmount(dj.multiply(qty));
}
}
/**
* 获取入库类型饼状图数据
*/
@Override
public List<RkTypePieVO> getTypePie() {
return rkStatisticsMapper.selectTypePie();
}
@Override
public List<RkTypePieVO> getTypeSummaryByRange(String start, String end) {
// 基础入参校验(避免全表扫描/人为误操作)
if (StringUtils.isBlank(start) || StringUtils.isBlank(end)) {
throw new ServiceException("start/end 不能为空建议格式yyyy-MM-dd");
}
return rkStatisticsMapper.selectTypeSummaryByRange(start, end);
}
@Override
public List<RkTypePieVO> getOutTypeSummaryByRange(String start, String end) {
if (StringUtils.isBlank(start) || StringUtils.isBlank(end)) {
throw new ServiceException("start/end 不能为空建议格式yyyy-MM-dd");
}
return rkStatisticsMapper.selectOutTypeSummaryByRange(start, end);
}
private static final DateTimeFormatter DF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public RkMonthInOutSummaryVO getThisMonthInOutSummary() {
// 本月起止: [YYYY-MM-01 00:00:00, 下月01 00:00:00)
LocalDate firstDay = LocalDate.now().withDayOfMonth(1);
LocalDate nextMonthFirst = firstDay.plusMonths(1);
String start = firstDay.atStartOfDay().format(DF);
String end = nextMonthFirst.atStartOfDay().format(DF);
RkMonthSummaryVO inSummary = rkStatisticsMapper.selectMonthlyInSummary(start, end);
RkMonthSummaryVO outSummary = rkStatisticsMapper.selectMonthlyOutSummary(start, end);
// null 兜底
if (inSummary == null) {
inSummary = new RkMonthSummaryVO();
}
if (outSummary == null) {
outSummary = new RkMonthSummaryVO();
}
RkMonthInOutSummaryVO vo = new RkMonthInOutSummaryVO();
vo.setInSummary(inSummary);
vo.setOutSummary(outSummary);
return vo;
}
@Override
public GysJhUndeliveredVO getUndeliveredSummary() {
// 分别查询未到status=0、部分未到status=2
GysJhUndeliveredSummaryVO undelivered = rkStatisticsMapper.selectUndelivered();
GysJhUndeliveredSummaryVO partial = rkStatisticsMapper.selectPartialUndelivered();
public List<StockStatisticGroupVO> getInTypeByTime(HomeStatQueryDTO query) {
// 对象兜底
if (undelivered == null) undelivered = new GysJhUndeliveredSummaryVO();
if (partial == null) partial = new GysJhUndeliveredSummaryVO();
Date start = query == null ? null : query.getStartDate();
Date end = query == null ? null : query.getEndDate();
// 字段兜底(防止仍有 null 透出)
if (undelivered.getProjectCount() == null) undelivered.setProjectCount(0);
if (undelivered.getItemCount() == null) undelivered.setItemCount(0);
if (undelivered.getTotalQty() == null) undelivered.setTotalQty(0L);
if (undelivered.getAmountPlan() == null) undelivered.setAmountPlan(BigDecimal.ZERO);
if (partial.getProjectCount() == null) partial.setProjectCount(0);
if (partial.getItemCount() == null) partial.setItemCount(0);
if (partial.getTotalQty() == null) partial.setTotalQty(0L);
if (partial.getAmountPlan() == null) partial.setAmountPlan(BigDecimal.ZERO);
// 返回
GysJhUndeliveredVO vo = new GysJhUndeliveredVO();
vo.setUndelivered(undelivered);
vo.setPartialUndelivered(partial);
return vo;
return rkStatisticsMapper.statInByOperationType(start, end);
}
@Override
public List<WlTypeStockStatVO> getCurrentStockByWlType() {
List<WlTypeStockStatVO> list = rkStatisticsMapper.selectCurrentStockByWlType();
return list == null ? Collections.emptyList() : list;
}
public List<StockStatisticGroupVO> getOutTypeByTime(HomeStatQueryDTO query) {
Date start = query == null ? null : query.getStartDate();
Date end = query == null ? null : query.getEndDate();
return rkStatisticsMapper.statOutByOperationType(start, end);
}
/**
* 首页库龄统计接口
* */
@Override
public RkTypePieVO getAgeGt30Summary() {
RkTypePieVO vo = rkStatisticsMapper.selectAgeGt30Summary();
if (vo == null) vo = new RkTypePieVO();
// 兜底,避免前端拿到 null
if (vo.getProjectCount() == null) vo.setProjectCount(0);
if (vo.getGoodsCountTotal() == null) vo.setGoodsCountTotal(0);
if (vo.getSumQty() == null) vo.setSumQty(BigDecimal.ZERO);
if (vo.getSumAmount() == null) vo.setSumAmount(BigDecimal.ZERO);
// 本接口不设置 rkTypeCode / rkTypeName
vo.setRkTypeCode(null);
vo.setRkTypeName(null);
return vo;
public List<StockAgeStatVO> selectStockAgeStat() {
return rkStatisticsMapper.selectStockAgeStat();
}
private static final DateTimeFormatter TS_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter DAY_FMT = DateTimeFormatter.ofPattern("MM-dd");
private static final DateTimeFormatter MON_FMT = DateTimeFormatter.ofPattern("yyyy-MM");
/**
* 首页待办事项接口
* */
@Override
public List<IOBucketVO> getIOBuckets(Integer range) {
if (range == null || (range != 0 && range != 2)) {
throw new IllegalArgumentException("range 仅支持 0(最近一周) 或 2(最近半年)");
}
Date now = DateUtils.getNowDate();
LocalDateTime nowLdt = LocalDateTime.ofInstant(now.toInstant(), ZoneId.systemDefault());
if (range == 0) {
// ===== 最近一周(按日),输出 MM-dd =====
LocalDate endDay = nowLdt.toLocalDate();
LocalDate startDay = endDay.minusDays(6);
LocalDateTime startTs = startDay.atStartOfDay();
LocalDateTime endTs = endDay.plusDays(1).atStartOfDay();
List<RkInVO> inRows = rkStatisticsMapper.selectInRange(startTs.format(TS_FMT), endTs.format(TS_FMT));
List<RkOutVO> outRows = rkStatisticsMapper.selectOutRange(startTs.format(TS_FMT), endTs.format(TS_FMT));
// 改为以 LocalDate 作为桶键,防止键格式与输出格式不一致导致匹配失败
LinkedHashMap<LocalDate, BucketAcc> acc = new LinkedHashMap<>();
for (int i = 0; i < 7; i++) {
LocalDate d = startDay.plusDays(i);
acc.put(d, new BucketAcc());
}
// 入库聚合(按 LocalDate
for (RkInVO r : inRows) {
if (r.getRkTime() == null) continue;
LocalDate d = r.getRkTime().toLocalDate();
BucketAcc b = acc.get(d);
if (b == null) continue;
b.inItemCount++;
if (StringUtils.isNotBlank(r.getXmNo())) {
b.inProjects.add(r.getXmNo().trim());
}
}
// 出库聚合(按 LocalDately_time 优先,否则 update_time
for (RkOutVO r : outRows) {
LocalDateTime t = (r.getLyTime() != null) ? r.getLyTime() : r.getUpdateTime();
if (t == null) continue;
LocalDate d = t.toLocalDate();
BucketAcc b = acc.get(d);
if (b == null) continue;
b.outItemCount++;
String proj = StringUtils.isNotBlank(r.getXmNoCk()) ? r.getXmNoCk().trim() : r.getXmNo();
if (StringUtils.isNotBlank(proj)) b.outProjects.add(proj);
}
ArrayList<IOBucketVO> out = new ArrayList<>(7);
for (Map.Entry<LocalDate, BucketAcc> e : acc.entrySet()) {
IOBucketVO row = new IOBucketVO();
row.setBucket(e.getKey().format(DAY_FMT)); // 仅返回 MM-dd
row.setInProjectCount(e.getValue().inProjects.size());
row.setInItemCount(e.getValue().inItemCount);
row.setOutProjectCount(e.getValue().outProjects.size());
row.setOutItemCount(e.getValue().outItemCount);
out.add(row);
}
return out;
} else {
// ===== 最近半年(按月),输出 yyyy-MM =====
YearMonth endYm = YearMonth.from(nowLdt.toLocalDate());
YearMonth startYm = endYm.minusMonths(5);
LocalDateTime startTs = startYm.atDay(1).atStartOfDay();
LocalDateTime endTs = endYm.plusMonths(1).atDay(1).atStartOfDay();
List<RkInVO> inRows = rkStatisticsMapper.selectInRange(startTs.format(TS_FMT), endTs.format(TS_FMT));
List<RkOutVO> outRows = rkStatisticsMapper.selectOutRange(startTs.format(TS_FMT), endTs.format(TS_FMT));
LinkedHashMap<YearMonth, BucketAcc> acc = new LinkedHashMap<>();
for (int i = 0; i < 6; i++) {
YearMonth ym = startYm.plusMonths(i);
acc.put(ym, new BucketAcc());
}
// 入库按月
for (RkInVO r : inRows) {
if (r.getRkTime() == null) continue;
YearMonth ym = YearMonth.from(r.getRkTime().toLocalDate());
BucketAcc b = acc.get(ym);
if (b == null) continue;
b.inItemCount++;
if (StringUtils.isNotBlank(r.getXmNo())) {
b.inProjects.add(r.getXmNo().trim());
}
}
// 出库按月
for (RkOutVO r : outRows) {
LocalDateTime t = (r.getLyTime() != null) ? r.getLyTime() : r.getUpdateTime();
if (t == null) continue;
YearMonth ym = YearMonth.from(t.toLocalDate());
BucketAcc b = acc.get(ym);
if (b == null) continue;
b.outItemCount++;
String proj = StringUtils.isNotBlank(r.getXmNoCk()) ? r.getXmNoCk().trim() : r.getXmNo();
if (StringUtils.isNotBlank(proj)) b.outProjects.add(proj);
}
ArrayList<IOBucketVO> out = new ArrayList<>(6);
for (Map.Entry<YearMonth, BucketAcc> e : acc.entrySet()) {
IOBucketVO row = new IOBucketVO();
row.setBucket(e.getKey().format(MON_FMT)); // yyyy-MM
row.setInProjectCount(e.getValue().inProjects.size());
row.setInItemCount(e.getValue().inItemCount);
row.setOutProjectCount(e.getValue().outProjects.size());
row.setOutItemCount(e.getValue().outItemCount);
out.add(row);
}
return out;
}
public List<TodoStatVO> selectTodoStat() {
return rkStatisticsMapper.selectTodoStat();
}
/** 桶内累加器 */
private static class BucketAcc {
int inItemCount = 0;
int outItemCount = 0;
Set<String> inProjects = new HashSet<>();
Set<String> outProjects = new HashSet<>();
}
/**
* 首页统计仓库使用情况
* */
@Override
public List<SceneAvailableVO> listAvailableByWarehouse(String warehouseCode) {
List<SceneAvailableVO> list = rkStatisticsMapper.selectAvailableByWarehouse(warehouseCode);
if (list == null || list.isEmpty()) {
// 没有查到任何数据时,返回一个默认对象
SceneAvailableVO vo = new SceneAvailableVO();
// vo.setSceneCode("N/A"); // 默认场景编码
// vo.setSceneName("无可用场景"); // 默认场景名称
vo.setAvailableCount(0); // 可用数量为0
vo.setPositions(new ArrayList<>()); // 空数组
list = new ArrayList<>();
list.add(vo);
} else {
// 保证 positions 不为 null
for (SceneAvailableVO vo : list) {
if (vo.getPositions() == null) {
vo.setPositions(new ArrayList<>());
}
}
}
return list;
public List<WarehouseSlotStatVO> selectWarehouseSlotStat() {
return rkStatisticsMapper.selectWarehouseSlotStat();
}
// @Override
// public Map<String, Object> selectStockStatistics(RkInfoQueryDTO query) {
//
// if (query.getIsChuku() == null) {
// query.setIsChuku("0"); // 默认只查在库
// }
//
// Map<String, Object> result = rkInfoMapper.selectStockStatistics(query);
//
// // 处理空值情况,防止前端拿到 null 报错
// if (result == null) {
// result = new HashMap<>();
// }
// result.putIfAbsent("sumMoney", 0);
// result.putIfAbsent("sumQty", 0);
// result.putIfAbsent("pcdeCount", 0);
//
// return result;
// }
/**
* 库龄>=30天明细用于导出
*/
@Override
public List<StockAgeExportVO> selectStockAgeExport30() {
return rkStatisticsMapper.selectStockAgeExport30();
}
}

View File

@@ -1,6 +1,7 @@
package com.zg.project.wisdom.utils;
import com.zg.project.wisdom.mapper.RkInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -22,6 +23,8 @@ public class BillNoUtil {
private static final String DEFAULT_PREFIX = "RK";
/**
* 生成单据号(短号,不查数据库)
* 格式PREFIX + yyMMdd + HHmmss + 2位随机数
@@ -32,20 +35,25 @@ public class BillNoUtil {
*/
public static synchronized String generateTodayBillNo(String prefix, RkInfoMapper rkInfoMapper) {
// 前缀处理
final String p = (prefix == null || prefix.trim().isEmpty())
? DEFAULT_PREFIX
: prefix.trim().toUpperCase();
// 日期 yyMMdd
String date = new SimpleDateFormat("yyMMdd").format(new Date());
// 时间到秒yyMMddHHmmss12位
String time = new SimpleDateFormat("yyMMddHHmmss").format(new Date());
// 查询今日最大单号
String maxBillNo = rkInfoMapper.selectTodayMaxBillNo(prefix, date);
// 2位随机数10~99避免同一秒内多次点击撞号
int rnd = ThreadLocalRandom.current().nextInt(10, 100);
int seq = 1;
return p + time + rnd;
if (maxBillNo != null && maxBillNo.length() > 8) {
// 取最后8位流水号
String seqStr = maxBillNo.substring(prefix.length() + date.length());
seq = Integer.parseInt(seqStr) + 1;
}
// 8位补零
String seqFormat = String.format("%08d", seq);
return prefix + date + seqFormat;
}
/**
* 需要更短的版本(可选)
* 格式PREFIX + yyMMdd + 4位随机数

View File

@@ -0,0 +1,43 @@
package com.zg.project.wisdom.utils;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class DecimalUtil {
private DecimalUtil() {
}
/**
* 核心方法:去除多余小数 0不补、不截
*
* @param value BigDecimal
* @return String用于返回给前端 / 导出 / 业务使用)
*/
public static String toPlain(BigDecimal value) {
if (value == null) {
return null;
}
return value.stripTrailingZeros().toPlainString();
}
/**
* 用于数值计算后再次规范化
*/
public static BigDecimal normalize(BigDecimal value) {
if (value == null) {
return null;
}
return value.stripTrailingZeros();
}
/**
* 字符串安全转 BigDecimal避免 double 精度问题)
*/
public static BigDecimal parse(String value) {
if (value == null || value.trim().isEmpty()) {
return null;
}
return new BigDecimal(value.trim());
}
}

View File

@@ -8,6 +8,7 @@ spring:
master:
# url: jdbc:mysql://47.100.212.83:3306/wisdom?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
url: jdbc:mysql://192.168.1.28:3306/wisdomnew?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# url: jdbc:mysql://192.168.1.251:3306/wisdomnew?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# url: jdbc:mysql://192.168.1.28:3306/wisdom?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# url: jdbc:mysql://192.168.1.192:3306/wisdom?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# url: jdbc:mysql://192.168.1.251:3306/wisdom?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8

View File

@@ -16,7 +16,7 @@ zg:
# 开发环境配置
server:
# 服务器的HTTP端口默认为8080
port: 8086
port: 8087
servlet:
# 应用的访问路径
context-path: /
@@ -77,6 +77,7 @@ spring:
# 数据库索引
database: 0
# 密码
# password: shzg
password:
# 连接超时时间
timeout: 10s

View File

@@ -4,6 +4,11 @@
<mapper namespace="com.zg.project.inventory.AutoInventory.mapper.InventoryMatchScanMapper">
<!--
批量插入盘点匹配扫描记录
用于将一批扫描记录批量插入到库存匹配扫描表中
@param list 扫描记录列表包含任务ID、设备ID、物资编码、时间、扫描类型和状态
-->
<insert id="insertBatch">
INSERT INTO inventory_match_scan (task_id, device_id, pcode, tme, scan_type, status)
VALUES
@@ -12,16 +17,33 @@
</foreach>
</insert>
<!--
查询所有盘点匹配扫描记录
获取库存匹配扫描表中的所有记录
@return InventoryMatchScan 扫描记录实体列表
-->
<select id="getAll" resultType="com.zg.project.inventory.domain.entity.InventoryMatchScan">
SELECT * FROM inventory_match_scan
</select>
<!--
查询所有库存匹配扫描记录并关联SOD表
左连接SOD表获取与扫描记录匹配的物资信息仅返回剩余数量大于0的记录
@return Sod 物资信息实体列表
-->
<select id="getAllSod" resultType="com.zg.project.inventory.domain.entity.Sod">
SELECT * FROM inventory_match_scan i
LEFT JOIN sod s ON i.pcde = s.fycde_1
WHERE s.rmn > 0
</select>
<!--
按任务统计物资编码数量
根据任务名称和状态筛选扫描记录关联SOD表统计每个物资编码的出现次数仅统计剩余数量大于0的物资
@param taskName 任务名称(模糊查询,可选)
@param status 扫描状态(可选)
@return PcdeCountVO 物资编码统计信息,包含物资编码和出现次数
-->
<select id="getPcdeCountByTask" resultType="com.zg.project.inventory.domain.vo.PcdeCountVO">
SELECT
s.pcde,
@@ -43,6 +65,14 @@
GROUP BY s.pcde
</select>
<!--
按任务名称分组查询扫描历史记录(分页)
根据多个条件筛选扫描记录,按任务名称分组,返回每个任务的最新时间和扫描类型,支持分页查询
@param dto 查询条件对象包含设备ID、任务名称、物资编码、时间范围、扫描类型和状态
@param limit 每页记录数
@param offset 偏移量
@return InventoryMatchScanSimpleVO 扫描记录简单视图列表
-->
<select id="getGroupedHistoryList" resultType="com.zg.project.inventory.domain.vo.InventoryMatchScanSimpleVO">
SELECT
task_name AS taskName,
@@ -75,6 +105,12 @@
LIMIT #{limit} OFFSET #{offset}
</select>
<!--
统计分组后的任务数量
根据查询条件统计不同任务名称的数量,用于分页查询的总数计算
@param dto 查询条件对象包含设备ID、任务名称、物资编码、时间范围、扫描类型和状态
@return int 任务名称数量
-->
<select id="countGroupedHistory" resultType="int">
SELECT COUNT(DISTINCT task_name)
FROM inventory_match_scan
@@ -101,6 +137,13 @@
</where>
</select>
<!--
根据任务名称查询扫描记录及关联的SOD物资信息
左连接SOD表获取物资详细信息支持按任务名称和状态筛选当状态不为2时仅返回剩余数量大于0的记录
@param taskName 任务名称(模糊查询,可选)
@param status 扫描状态可选当不为2时会额外筛选rmn>0的记录
@return InventoryMatchScanVO 扫描记录详细信息列表,包含扫描信息和物资信息
-->
<select id="getByTaskName" resultType="com.zg.project.inventory.domain.vo.InventoryMatchScanVO">
SELECT
i.id,
@@ -132,6 +175,12 @@
</if>
</select>
<!--
条件查询盘点匹配扫描记录列表(多表关联)
关联入库信息表和盘点任务表,获取完整的扫描记录信息,支持多个条件筛选并按时间倒序排列
@param param 查询条件对象包含任务ID、设备ID、物资编码、状态、扫描类型和时间
@return InventoryMatchScan 扫描记录实体列表
-->
<select id="selectInventoryMatchScanList"
resultType="com.zg.project.inventory.domain.entity.InventoryMatchScan"
parameterType="com.zg.project.inventory.domain.entity.InventoryMatchScan">
@@ -171,6 +220,12 @@
ORDER BY i.tme DESC
</select>
<!--
仅从扫描表中查询物资编码(用于匹配对比)
根据条件查询扫描记录中的物资编码,实际数量字段置空,用于与入库信息进行匹配对比
@param param 查询条件对象包含任务ID、设备ID和状态
@return RkInfoMatchVO 入库信息匹配视图列表包含物资编码和实际数量NULL
-->
<select id="selectOnlyFromMatchScan"
parameterType="InventoryMatchScan"
resultType="com.zg.project.inventory.domain.vo.RkInfoMatchVO">
@@ -192,6 +247,12 @@
ORDER BY i.tme DESC
</select>
<!--
关联入库信息查询物资的实际入库数量
根据扫描表中的物资编码,查询入库信息表中的实际入库数量汇总,用于盘点数量对比
@param param 查询条件对象包含任务ID、设备ID和状态
@return RkInfoMatchVO 入库信息匹配视图列表,包含物资编码和实际入库数量汇总
-->
<select id="selectJoinRkInfo"
parameterType="InventoryMatchScan"
resultType="com.zg.project.inventory.domain.vo.RkInfoMatchVO">
@@ -212,4 +273,17 @@
ORDER BY MAX(r.create_time) DESC
</select>
<select id="selectUnscanFromMatchScan"
resultType="com.zg.project.inventory.domain.vo.RkInfoMatchVO">
SELECT
pcode AS rkPcode,
0 AS realQty,
status
FROM inventory_match_scan
WHERE task_id = #{taskId}
AND status = '1'
</select>
</mapper>

View File

@@ -140,6 +140,33 @@
AND is_delete = '0'
</select>
<!-- 根据 pcode + 小仓编码 精准定位 -->
<select id="selectByPcodeAndWarehouse"
resultType="com.zg.project.information.domain.PcdeDetail">
SELECT
id,
pcode,
scene,
parent_warehouse_code AS parentWarehouseCode,
parent_warehouse_name AS parentWarehouseName,
warehouse_code AS warehouseCode,
warehouse_name AS warehouseName,
encoded_id AS encodedId,
tag,
is_delete AS isDelete,
created_by AS createdBy,
created_at AS createdAt,
updated_by AS updatedBy,
updated_at AS updatedAt,
remark
FROM pcde_detail
WHERE pcode = #{pcode}
AND warehouse_code = #{warehouseCode}
AND is_delete = '0'
LIMIT 1
</select>
<!-- 根据库位编码查编码后ID -->
<select id="selectEncodedIdByPcode"
resultType="java.lang.String"

View File

@@ -12,6 +12,9 @@
<!-- 关键字段 -->
<result property="rkInfoId" column="rk_info_id"/>
<result property="newRkInfoId" column="new_rk_info_id"/>
<result property="parentMoveId" column="parent_move_id"/>
<result property="entityId" column="entity_id"/>
<!-- 数量 -->
@@ -55,6 +58,10 @@
id,
rk_info_id,
new_rk_info_id,
-- ★★★ 新增字段 ★★★
parent_move_id,
entity_id,
real_qty,
from_cangku,
@@ -82,6 +89,9 @@
INSERT INTO move_record (
rk_info_id,
new_rk_info_id,
parent_move_id,
entity_id,
real_qty,
from_cangku,
@@ -101,6 +111,9 @@
) VALUES (
#{rkInfoId},
#{newRkInfoId},
#{parentMoveId},
#{entityId},
#{realQty},
#{fromCangku},
@@ -172,6 +185,9 @@
<set>
<if test="rkInfoId != null">rk_info_id = #{rkInfoId},</if>
<if test="newRkInfoId != null">new_rk_info_id = #{newRkInfoId},</if>
<if test="parentMoveId != null">parent_move_id = #{parentMoveId},</if>
<if test="entityId != null">entity_id = #{entityId},</if>
<if test="realQty != null">real_qty = #{realQty},</if>
<if test="fromCangku != null">from_cangku = #{fromCangku},</if>
@@ -203,4 +219,20 @@
</foreach>
</delete>
<select id="selectLastMoveIdByRkInfoId" resultType="java.lang.Long">
SELECT id
FROM move_record
WHERE new_rk_info_id = #{rkInfoId}
AND is_delete = '0'
ORDER BY create_time DESC
LIMIT 1
</select>
<select id="countByParentMoveId" resultType="int">
SELECT COUNT(1)
FROM move_record
WHERE parent_move_id = #{parentMoveId}
AND is_delete = '0'
</select>
</mapper>

View File

@@ -104,7 +104,6 @@
<select id="selectRkBillList" parameterType="RkBill" resultMap="RkBillResult">
<include refid="selectRkBillVo"/>
<where>
<if test="wlType != null and wlType != ''">
AND rb.wl_type = #{wlType}
</if>
@@ -117,11 +116,11 @@
AND rb.bill_no = #{billNo}
</if>
<if test="operationType != null and operationType != ''">
<if test="operationType != null and operationType.trim() != ''">
AND rb.operation_type = #{operationType}
</if>
<if test="bizTypeList != null and bizTypeList.size > 0">
<if test="bizTypeList != null and bizTypeList.size() > 0">
AND rb.biz_type IN
<foreach collection="bizTypeList" item="bt" open="(" separator="," close=")">
#{bt}
@@ -303,4 +302,11 @@
</foreach>
</delete>
<update id="updateDeliveryStatusByBillNo">
UPDATE rk_bill
SET is_delivery = #{isDelivery},
update_time = NOW()
WHERE bill_no = #{billNo}
AND is_delete = '0'
</update>
</mapper>

View File

@@ -43,6 +43,7 @@
<result property="htQty" column="ht_qty"/>
<result property="dw" column="dw"/>
<result property="realQty" column="real_qty"/>
<result property="totalAmount" column="total_amount"/>
<result property="pcode" column="pcode"/>
<result property="pcodeId" column="pcode_id"/>
<result property="trayCode" column="tray_code"/>
@@ -65,39 +66,40 @@
<result property="fycde1" column="fycde_1"/>
<result property="fycde2" column="fycde_2"/>
<result property="isUpdate" column="is_update"/>
<result property="scene" column="scene"/>
<result property="sceneName" column="sceneName"/>
</resultMap>
<!-- ========================= 公共查询 SQL联表完整版 ========================= -->
<sql id="selectRkInfoVo">
SELECT
ri.*,
CASE
WHEN ri.scene = 'HJ' THEN '货架'
WHEN ri.scene = 'DC' THEN '堆场'
ELSE ri.scene
END AS sceneName,
/* ===== 新增:计算总金额 ===== */
ri.real_qty * ri.ht_dj AS total_amount,
/* 出入库类型名称 */
COALESCE(sit.type_name, sot.type_name) AS operation_type_name,
/* 物资类型名称 */
mt.type_name AS wl_type_name,
/* 仓库信息 */
wh.warehouse_name,
wh.parent_warehouse_code,
wh.parent_warehouse_name,
/* 理货员 */
su.nick_name AS operator_name,
/* 库龄 */
DATEDIFF(CURRENT_DATE, ri.operation_time) AS stock_age
FROM rk_info ri
LEFT JOIN stock_in_type sit ON ri.operation_type = sit.type_code
LEFT JOIN stock_out_type sot ON ri.operation_type = sot.type_code
LEFT JOIN material_type mt ON ri.wl_type = mt.type_code
LEFT JOIN warehouse_info wh ON ri.cangku = wh.warehouse_code
LEFT JOIN sys_user su ON ri.operator = su.user_id
</sql>
<!-- ========================= 查询列表 ========================= -->
<select id="selectRkInfoList" parameterType="RkInfo" resultMap="RkInfoResult">
<include refid="selectRkInfoVo"/>
@@ -105,53 +107,75 @@
ri.exec_status = 1
AND ri.is_chuku = 0
AND ri.is_delete = 0
<if test="operationType != null and operationType != ''">
AND ri.operation_type LIKE CONCAT('%', #{operationType}, '%')
</if>
<if test="sapNo != null and sapNo != ''">
AND ri.sap_no LIKE CONCAT('%', #{sapNo}, '%')
</if>
<if test="xmNo != null and xmNo != ''">
AND ri.xm_no LIKE CONCAT('%', #{xmNo}, '%')
</if>
<if test="xmMs != null and xmMs != ''">
AND ri.xm_ms LIKE CONCAT('%', #{xmMs}, '%')
</if>
<if test="wlNo != null and wlNo != ''">
AND ri.wl_no LIKE CONCAT('%', #{wlNo}, '%')
</if>
<if test="wlMs != null and wlMs != ''">
AND ri.wl_ms LIKE CONCAT('%', #{wlMs}, '%')
</if>
<if test="gysMc != null and gysMc != ''">
AND ri.gys_mc LIKE CONCAT('%', #{gysMc}, '%')
</if>
<if test="pcode != null and pcode != ''">
AND ri.pcode LIKE CONCAT('%', #{pcode}, '%')
</if>
<if test="bizType != null and bizType != ''">
AND ri.biz_type LIKE CONCAT('%', #{bizType}, '%')
</if>
<if test="wlType != null and wlType != ''">
AND ri.wl_type LIKE CONCAT('%', #{wlType}, '%')
</if>
<if test="cangku != null and cangku != ''">
AND ri.cangku LIKE CONCAT('%', #{cangku}, '%')
</if>
<if test="billNo != null and billNo != ''">
AND ri.bill_no LIKE CONCAT('%', #{billNo}, '%')
</if>
<!-- ★★★ 新增:按备注模糊搜索 ★★★ -->
<if test="remark != null and remark != ''">
AND ri.remark LIKE CONCAT('%', #{remark}, '%')
</if>
<if test="scene != null and scene != ''">
AND ri.scene LIKE CONCAT('%', #{scene}, '%')
</if>
<if test="isDelete != null and isDelete != ''">
AND ri.is_delete = #{isDelete}
</if>
<!-- 出入库时间范围 -->
<if test="startDate != null">
AND ri.operation_time &gt;= #{startDate}
</if>
<if test="endDate != null">
AND ri.operation_time &lt;= #{endDate}
</if>
</where>
ORDER BY ri.operation_time DESC
</select>
@@ -181,7 +205,10 @@
parameterType="java.util.List"
resultMap="RkInfoResult">
SELECT
ri.*
ri.*,
ri.real_qty * ri.ht_dj AS total_amount
FROM rk_info ri
WHERE ri.id IN
<foreach collection="rkInfoIds" item="id" open="(" separator="," close=")">
@@ -221,6 +248,9 @@
<if test="wlMs != null and wlMs != ''">
AND ri.wl_ms LIKE CONCAT('%', #{wlMs}, '%')
</if>
<if test="scene != null and scene != ''">
AND ri.scene LIKE CONCAT('%', #{scene}, '%')
</if>
<if test="gysMc != null and gysMc != ''">
AND ri.gys_mc LIKE CONCAT('%', #{gysMc}, '%')
</if>
@@ -279,7 +309,7 @@
is_delivery,
fycde_1, fycde_2,
is_update,
create_by, create_time, is_delete
create_by, create_time, is_delete,scene
)
VALUES (
#{operationType}, #{bizType}, #{wlType}, #{cangku}, #{operationTime}, #{operator},
@@ -297,7 +327,7 @@
#{isDelivery},
#{fycde1}, #{fycde2},
#{isUpdate},
#{createBy}, #{createTime}, #{isDelete}
#{createBy}, #{createTime}, #{isDelete},#{scene}
)
</insert>
@@ -353,6 +383,7 @@
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="isDelete != null">is_delete = #{isDelete},</if>
<if test="scene != null">scene = #{scene}</if>
</trim>
WHERE id = #{id}
</update>
@@ -389,5 +420,278 @@
AND is_delete = '0'
</update>
<!-- ========================= 1⃣ 正常匹配 ========================= -->
<select id="getByPcodeIdList" resultMap="RkInfoResult">
SELECT
ri.*
FROM rk_info ri
JOIN pcde_detail pd ON ri.pcode_id = pd.encoded_id
WHERE ri.is_delete = '0'
AND ri.is_chuku = '0'
AND pd.is_delete = '0'
AND pd.scene = #{sceneId}
AND ri.pcode_id IN
<foreach collection="pcdeIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<!-- ========================= 2⃣ 未盘点 ========================= -->
<select id="getMissedPcodeIds" resultMap="RkInfoResult">
SELECT
ri.*
FROM rk_info ri
JOIN pcde_detail pd ON ri.pcode_id = pd.encoded_id
WHERE ri.is_delete = '0'
AND ri.is_chuku = '0'
AND pd.is_delete = '0'
AND pd.scene = #{sceneId}
<if test="pcdeIds != null and pcdeIds.size() > 0">
AND ri.pcode_id NOT IN
<foreach collection="pcdeIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</select>
<!-- ========================= 3⃣ 异常(不存在) ========================= -->
<select id="getNotExistsPcodeIds" resultType="java.lang.String">
SELECT t.pcode_id
FROM (
<foreach collection="pcdeIds" item="id" separator="UNION ALL">
SELECT #{id} AS pcode_id
</foreach>
) t
LEFT JOIN (
SELECT ri.pcode_id
FROM rk_info ri
JOIN pcde_detail pd ON ri.pcode_id = pd.encoded_id
WHERE ri.is_delete = '0'
AND ri.is_chuku = '0'
AND pd.is_delete = '0'
AND pd.scene = #{sceneId}
) s ON t.pcode_id = s.pcode_id
WHERE s.pcode_id IS NULL
</select>
<select id="selectPcdeCntFromRkInfo" resultType="com.zg.project.inventory.domain.vo.PcdeCntVO">
SELECT pcode AS pcde,
COUNT(*) AS cnt
FROM rk_info
WHERE pcode_id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
GROUP BY pcode
</select>
<select id="getUnscannedPcodeByScene"
resultType="com.zg.project.inventory.domain.vo.RkInfoMatchVO">
SELECT
r.pcode AS rkPcode,
COALESCE(SUM(r.real_qty), 0) AS realQty
FROM pcde_detail d
JOIN rk_info r ON r.pcode = d.pcode
LEFT JOIN (
SELECT DISTINCT pcode
FROM inventory_match_scan
WHERE task_id = #{taskId}
AND status = '0'
) s ON s.pcode = r.pcode
WHERE (d.is_delete IS NULL OR d.is_delete = '0')
AND d.scene = #{sceneId}
AND r.is_chuku = '0'
AND s.pcode IS NULL
GROUP BY r.pcode
ORDER BY MAX(r.create_time) DESC
</select>
<select id="countGetByWh" resultType="java.lang.Integer" parameterType="java.lang.String">
SELECT COUNT(1) FROM rk_info
WHERE is_delete = '0'
AND cangku = #{warehouse}
AND is_chuku = '0'
</select>
<select id="listRkInfoByPcode"
parameterType="string"
resultMap="RkInfoResult">
SELECT
t.*,
wh.warehouse_name AS cangku_name,
wh.parent_warehouse_code AS parent_warehouse_code,
wh.parent_warehouse_name AS parent_warehouse_name,
wh.warehouse_code AS warehouse_code,
wh.warehouse_name AS warehouse_name
FROM rk_info t
LEFT JOIN warehouse_info wh
ON wh.warehouse_code = t.cangku
WHERE t.is_delete = 0
AND t.is_chuku = 0
AND t.pcode = #{pcode}
ORDER BY t.operation_time DESC, t.id DESC
</select>
<!-- ========================= 按单据号排序查询列表 ========================= -->
<select id="selectRkInfoListOrderByBillNo" parameterType="RkInfo" resultMap="RkInfoResult">
<include refid="selectRkInfoVo"/>
<where>
ri.exec_status = 1
AND ri.is_chuku = 0
AND ri.is_delete = 0
<if test="operationType != null and operationType != ''">
AND ri.operation_type LIKE CONCAT('%', #{operationType}, '%')
</if>
<if test="sapNo != null and sapNo != ''">
AND ri.sap_no LIKE CONCAT('%', #{sapNo}, '%')
</if>
<if test="xmNo != null and xmNo != ''">
AND ri.xm_no LIKE CONCAT('%', #{xmNo}, '%')
</if>
<if test="xmMs != null and xmMs != ''">
AND ri.xm_ms LIKE CONCAT('%', #{xmMs}, '%')
</if>
<if test="wlNo != null and wlNo != ''">
AND ri.wl_no LIKE CONCAT('%', #{wlNo}, '%')
</if>
<if test="wlMs != null and wlMs != ''">
AND ri.wl_ms LIKE CONCAT('%', #{wlMs}, '%')
</if>
<if test="gysMc != null and gysMc != ''">
AND ri.gys_mc LIKE CONCAT('%', #{gysMc}, '%')
</if>
<if test="pcode != null and pcode != ''">
AND ri.pcode LIKE CONCAT('%', #{pcode}, '%')
</if>
<if test="bizType != null and bizType != ''">
AND ri.biz_type LIKE CONCAT('%', #{bizType}, '%')
</if>
<if test="wlType != null and wlType != ''">
AND ri.wl_type LIKE CONCAT('%', #{wlType}, '%')
</if>
<if test="cangku != null and cangku != ''">
AND ri.cangku LIKE CONCAT('%', #{cangku}, '%')
</if>
<if test="billNo != null and billNo != ''">
AND ri.bill_no LIKE CONCAT('%', #{billNo}, '%')
</if>
<if test="isDelete != null and isDelete != ''">
AND ri.is_delete = #{isDelete}
</if>
<!-- 出入库时间范围 -->
<if test="startDate != null">
AND ri.operation_time &gt;= #{startDate}
</if>
<if test="endDate != null">
AND ri.operation_time &lt;= #{endDate}
</if>
</where>
ORDER BY
/* 1. 纯数字优先 */
(ri.sap_no REGEXP '^[0-9]+$') DESC,
/* 2. 纯数字按【从小到大】 */
CASE
WHEN ri.sap_no REGEXP '^[0-9]+$'
THEN CAST(ri.sap_no AS UNSIGNED)
ELSE 999999999999
END ASC,
/* 3. 字母混合按字符串 */
ri.sap_no ASC
</select>
<select id="getByWarehouseAndScene" resultMap="RkInfoResult">
SELECT
ri.*,
CASE
WHEN pd.scene = 'HJ' THEN '货架'
WHEN pd.scene = 'DC' THEN '堆场'
ELSE pd.scene
END AS sceneName,
sit.type_name AS operationTypeName,
/* ===== 新增:计算总金额 ===== */
ri.real_qty * ri.ht_dj AS total_amount,
COALESCE(sit.type_name, sot.type_name) AS operation_type_name,
mt.type_name AS wl_type_name,
wh.warehouse_name,
wh.parent_warehouse_code,
wh.parent_warehouse_name,
su.nick_name AS operator_name,
DATEDIFF(CURRENT_DATE, ri.operation_time) AS stock_age
FROM rk_info ri
LEFT JOIN stock_in_type sit ON ri.operation_type = sit.type_code
LEFT JOIN stock_out_type sot ON ri.operation_type = sot.type_code
LEFT JOIN material_type mt ON ri.wl_type = mt.type_code
LEFT JOIN warehouse_info wh ON ri.cangku = wh.warehouse_code
LEFT JOIN sys_user su ON ri.operator = su.user_id
LEFT JOIN pcde_detail pd ON pd.pcode = ri.pcode
WHERE ri.exec_status = 1
AND ri.is_delete = '0'
AND ri.is_chuku = '0'
AND ri.cangku = #{warehouseCode}
AND ri.scene = #{sceneId}
</select>
<select id="listRkInfoByPcodes"
resultMap="RkInfoResult"
parameterType="java.util.ArrayList">
SELECT *
FROM rk_info
WHERE is_delete = 0
AND is_chuku = 0
AND pcode IN
<foreach collection="list" item="p" open="(" separator="," close=")">
#{p}
</foreach>
</select>
<select id="selectTodayMaxBillNo" resultType="java.lang.String">
SELECT MAX(bill_no)
FROM rk_record
WHERE bill_no LIKE CONCAT(#{prefix}, #{date}, '%')
</select>
<update id="updateDw">
UPDATE rk_info
SET dw = #{dw}
WHERE sap_no = #{sapNo}
AND wl_no = #{wlNo}
AND xm_no = #{xmNo}
</update>
<select id="countByWarehouseAndScene" resultType="int">
SELECT COUNT(1)
FROM rk_info ri
WHERE ri.exec_status = 1
AND ri.is_delete = '0'
AND ri.is_chuku = '0'
AND ri.cangku = #{warehouseCode}
AND ri.scene = #{sceneId}
</select>
<update id="updateInfoByBillNo">
UPDATE rk_info
<set>
<if test="operationType != null">
operation_type = #{operationType},
</if>
<if test="teamCode != null">
team_code = #{teamCode},
</if>
<if test="operator != null">
operator = #{operator},
</if>
<if test="isDelivery != null">
is_delivery = #{isDelivery},
</if>
</set>
WHERE bill_no = #{billNo}
</update>
</mapper>

View File

@@ -53,7 +53,7 @@
<result property="htQty" column="ht_qty"/>
<result property="dw" column="dw"/>
<result property="realQty" column="real_qty"/>
<result property="totalAmount" column="total_amount"/>
<result property="pcode" column="pcode"/>
<result property="pcodeId" column="pcode_id"/>
<result property="trayCode" column="tray_code"/>
@@ -81,35 +81,46 @@
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="isDelete" column="is_delete"/>
<result property="operationTypeName" column="operationTypeName"/>
<result property="wlTypeName" column="wlTypeName"/>
<result property="operatorName" column="operatorName"/>
<result property="parentWarehouseName" column="parentWarehouseName"/>
<result property="warehouseName" column="warehouseName"/>
</resultMap>
<!-- ===================== 查询字段(统一视图) ===================== -->
<sql id="selectRkRecordVo">
SELECT
rr.*,
COALESCE(rr.real_qty, 0) * COALESCE(rr.ht_dj, 0) AS total_amount,
su.nick_name AS operator_name,
mt.type_name AS wl_type_name,
COALESCE(sit.type_name, sot.type_name) AS operation_type_name,
-- 小仓
wh.warehouse_name AS warehouse_name,
-- ✅ 大仓(直接取小仓行里的父仓字段)
wh.parent_warehouse_code AS parent_warehouse_code,
wh.parent_warehouse_name AS parent_warehouse_name,
ct.team_name AS team_name,
ct.team_name AS team_name
(
SELECT MIN(gj.id)
FROM gys_jh gj
WHERE gj.sap_no = rr.sap_no
AND gj.wl_no = rr.wl_no
AND gj.xm_no = rr.xm_no
AND gj.is_delete = '0'
) AS gys_jh_id
FROM rk_record rr
LEFT JOIN sys_user su ON rr.operator = su.user_id
LEFT JOIN material_type mt ON rr.wl_type = mt.type_code
LEFT JOIN stock_in_type sit ON rr.operation_type = sit.type_code
LEFT JOIN stock_out_type sot ON rr.operation_type = sot.type_code
LEFT JOIN warehouse_info wh ON rr.cangku = wh.warehouse_code
LEFT JOIN sys_user su
ON rr.operator = su.user_id
LEFT JOIN material_type mt
ON rr.wl_type = mt.type_code
LEFT JOIN stock_in_type sit
ON rr.operation_type = sit.type_code
LEFT JOIN stock_out_type sot
ON rr.operation_type = sot.type_code
LEFT JOIN warehouse_info wh
ON rr.cangku = wh.warehouse_code
LEFT JOIN construction_team ct
ON rr.team_code = ct.team_code
AND ct.is_delete = '0'
@@ -275,7 +286,11 @@
<include refid="selectRkRecordVo"/>
WHERE rr.bill_no = #{billNo}
AND (rr.is_delete = '0' OR rr.is_delete = 0 OR rr.is_delete IS NULL)
ORDER BY rr.exec_status = '0' DESC, rr.operation_time DESC
ORDER BY
rr.exec_status = '0' DESC,
rr.create_time ASC,
gys_jh_id ASC
</select>
<select id="selectRkRecordByIds" resultMap="RkRecordResult">
@@ -581,79 +596,150 @@
<!-- 入库统计 -->
<select id="selectInRecordStatistic"
<select id="selectRecordStatisticByCondition"
parameterType="com.zg.project.wisdom.domain.RkRecord"
resultType="com.zg.project.wisdom.domain.vo.StockStatisticVO">
SELECT
IFNULL(SUM(rr.real_qty * rr.ht_dj), 0) AS total_amount,
COUNT(DISTINCT rr.pcode) AS location_count,
IFNULL(SUM(rr.real_qty), 0) AS total_quantity
IFNULL(SUM(rr.real_qty * rr.ht_dj), 0) AS totalAmount,
COUNT(DISTINCT rr.pcode) AS locationCount,
IFNULL(SUM(rr.real_qty), 0) AS totalQuantity
FROM rk_record rr
<where>
rr.is_delete = 0
AND rr.exec_status = 1
AND rr.biz_type = 0 <!-- 入库 -->
AND rr.exec_status = '1'
<if test="operationType != null and operationType != ''">
AND rr.operation_type LIKE CONCAT('%', #{operationType}, '%')
AND rr.operation_type = #{operationType}
</if>
<if test="xmNo != null and xmNo != ''">
AND rr.xm_no LIKE CONCAT('%', #{xmNo}, '%')
<!-- 多 bizType -->
<if test="bizTypeList != null and bizTypeList.size > 0">
AND rr.biz_type IN
<foreach collection="bizTypeList"
item="bt"
open="("
separator=","
close=")">
#{bt}
</foreach>
</if>
<if test="wlNo != null and wlNo != ''">
AND rr.wl_no LIKE CONCAT('%', #{wlNo}, '%')
<if test="bizType != null and bizType != ''">
AND rr.biz_type = #{bizType}
</if>
<if test="pcode != null and pcode != ''">
AND rr.pcode LIKE CONCAT('%', #{pcode}, '%')
AND rr.pcode = #{pcode}
</if>
<if test="wlType != null and wlType != ''">
AND rr.wl_type = #{wlType}
</if>
<if test="cangku != null and cangku != ''">
AND rr.cangku LIKE CONCAT('%', #{cangku}, '%')
AND rr.cangku = #{cangku}
</if>
<if test="startDate != null">
AND rr.operation_time &gt;= #{startDate}
</if>
<if test="endDate != null">
AND rr.operation_time &lt;= #{endDate}
</if>
</where>
</select>
<!-- 出库统计 -->
<select id="selectOutRecordStatistic"
parameterType="com.zg.project.wisdom.domain.RkRecord"
resultType="com.zg.project.wisdom.domain.vo.StockStatisticVO">
SELECT
IFNULL(SUM(rr.real_qty * rr.ht_dj), 0) AS total_amount,
COUNT(DISTINCT rr.pcode) AS location_count,
IFNULL(SUM(rr.real_qty), 0) AS total_quantity
FROM rk_record rr
<where>
rr.is_delete = 0
AND rr.exec_status = 1
AND rr.biz_type = 1 <!-- 出库 -->
<if test="operationType != null and operationType != ''">
AND rr.operation_type LIKE CONCAT('%', #{operationType}, '%')
<if test="operator != null and operator != ''">
AND rr.operator = #{operator}
</if>
<if test="isChuku != null and isChuku != ''">
AND rr.is_chuku = #{isChuku}
</if>
<if test="status != null and status != ''">
AND rr.status = #{status}
</if>
<if test="execStatus != null and execStatus != ''">
AND rr.exec_status = #{execStatus}
</if>
<if test="billNo != null and billNo != ''">
AND rr.bill_no = #{billNo}
</if>
<if test="isDelivery != null and isDelivery != ''">
AND rr.is_delivery = #{isDelivery}
</if>
<!-- 项目 -->
<if test="xmNo != null and xmNo != ''">
AND rr.xm_no LIKE CONCAT('%', #{xmNo}, '%')
AND rr.xm_no LIKE concat('%', #{xmNo}, '%')
</if>
<if test="xmMs != null and xmMs != ''">
AND rr.xm_ms LIKE concat('%', #{xmMs}, '%')
</if>
<!-- 订单 -->
<if test="sapNo != null and sapNo != ''">
AND rr.sap_no LIKE concat('%', #{sapNo}, '%')
</if>
<!-- 供应商 -->
<if test="gysMc != null and gysMc != ''">
AND rr.gys_mc LIKE concat('%', #{gysMc}, '%')
</if>
<if test="wlNo != null and wlNo != ''">
AND rr.wl_no LIKE CONCAT('%', #{wlNo}, '%')
AND rr.wl_no LIKE concat('%', #{wlNo}, '%')
</if>
<if test="pcode != null and pcode != ''">
AND rr.pcode LIKE CONCAT('%', #{pcode}, '%')
<if test="wlMs != null and wlMs != ''">
AND rr.wl_ms LIKE concat('%', #{wlMs}, '%')
</if>
<if test="cangku != null and cangku != ''">
AND rr.cangku LIKE CONCAT('%', #{cangku}, '%')
<!-- 是否借料 -->
<if test="isBorrowed != null and isBorrowed != ''">
AND rr.is_borrowed = #{isBorrowed}
</if>
<if test="startDate != null">
AND rr.operation_time &gt;= #{startDate}
<!-- 删除标识 -->
<if test="isDelete == null">
AND (rr.is_delete = '0' OR rr.is_delete = 0 OR rr.is_delete IS NULL)
</if>
<if test="endDate != null">
AND rr.operation_time &lt;= #{endDate}
<if test="isDelete != null and isDelete != ''">
AND rr.is_delete = #{isDelete}
</if>
<!-- ================= 时间条件(最终正确版) ================= -->
<if test="startDate != null or endDate != null">
AND (
<!-- 普通入库 / 出库:精确到时分秒 -->
(
rr.biz_type IN ('0','1')
<if test="startDate != null">
AND rr.operation_time &gt;= #{startDate}
</if>
<if test="endDate != null">
AND rr.operation_time &lt;= #{endDate}
</if>
)
OR
<!-- 借料出库:按 borrow_time按天 -->
(
rr.biz_type = '2'
<if test="startDate != null">
AND rr.borrow_time &gt;= DATE(#{startDate})
</if>
<if test="endDate != null">
AND rr.borrow_time &lt; DATE_ADD(DATE(#{endDate}), INTERVAL 1 DAY)
</if>
)
OR
<!-- 还料入库:按 return_time按天 -->
(
rr.biz_type = '3'
<if test="startDate != null">
AND rr.return_time &gt;= DATE(#{startDate})
</if>
<if test="endDate != null">
AND rr.return_time &lt; DATE_ADD(DATE(#{endDate}), INTERVAL 1 DAY)
</if>
)
)
</if>
</where>
</select>
@@ -689,4 +775,176 @@
AND is_delete = '0'
</update>
<select id="selectInOutStatisticByCondition"
parameterType="com.zg.project.wisdom.domain.RkRecord"
resultType="com.zg.project.wisdom.domain.vo.StockStatisticVO">
SELECT
IFNULL(SUM(rr.real_qty * rr.ht_dj), 0) AS totalAmount,
COUNT(DISTINCT rr.pcode) AS locationCount,
IFNULL(SUM(rr.real_qty), 0) AS totalQuantity
FROM rk_record rr
<where>
rr.exec_status = '1'
AND rr.is_delete = '0'
<if test="operationType != null and operationType != ''">
AND rr.operation_type LIKE CONCAT('%', #{operationType}, '%')
</if>
<if test="wlType != null and wlType != ''">
AND rr.wl_type LIKE CONCAT('%', #{wlType}, '%')
</if>
<if test="cangku != null and cangku != ''">
AND rr.cangku LIKE CONCAT('%', #{cangku}, '%')
</if>
<if test="billNo != null and billNo != ''">
AND rr.bill_no LIKE CONCAT('%', #{billNo}, '%')
</if>
<if test="xmNo != null and xmNo != ''">
AND rr.xm_no LIKE CONCAT('%', #{xmNo}, '%')
</if>
<if test="xmMs != null and xmMs != ''">
AND rr.xm_ms LIKE CONCAT('%', #{xmMs}, '%')
</if>
<if test="wlNo != null and wlNo != ''">
AND rr.wl_no LIKE CONCAT('%', #{wlNo}, '%')
</if>
<if test="wlMs != null and wlMs != ''">
AND rr.wl_ms LIKE CONCAT('%', #{wlMs}, '%')
</if>
<if test="gysMc != null and gysMc != ''">
AND rr.gys_mc LIKE CONCAT('%', #{gysMc}, '%')
</if>
<if test="sapNo != null and sapNo != ''">
AND rr.sap_no LIKE CONCAT('%', #{sapNo}, '%')
</if>
<if test="pcode != null and pcode != ''">
AND rr.pcode LIKE CONCAT('%', #{pcode}, '%')
</if>
<!-- 时间:建议用 “< endDate+1天” 防止漏当天 -->
<if test="startDate != null">
AND rr.operation_time &gt;= #{startDate}
</if>
<if test="endDate != null">
AND rr.operation_time &lt; DATE_ADD(#{endDate}, INTERVAL 1 DAY)
</if>
</where>
</select>
<select id="selectByBillNo"
resultMap="RkRecordResult">
SELECT
rr.*,
sit.type_name AS operationTypeName,
mt.type_name AS wlTypeName,
su.user_name AS operatorName,
IFNULL(wh.parent_warehouse_name, '') AS parentWarehouseName,
IFNULL(wh.warehouse_name, '') AS warehouseName
FROM rk_record rr
LEFT JOIN stock_out_type sit
ON rr.operation_type = sit.type_code
LEFT JOIN material_type mt
ON rr.wl_type = mt.type_code
LEFT JOIN sys_user su
ON rr.operator = su.user_id
LEFT JOIN warehouse_info wh
ON rr.cangku = wh.warehouse_code
WHERE rr.bill_no = #{billNo}
AND rr.is_delete = '0'
AND rr.is_delivery = '1'
</select>
<!-- 修改 record 状态 -->
<update id="updateDeliveryStatus">
UPDATE rk_record
SET is_delivery = #{isDelivery},
update_time = NOW()
WHERE is_delete = '0'
AND id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</update>
<!-- 找到受影响的 bill -->
<select id="selectBillNosByRecordIds" resultType="string">
SELECT DISTINCT bill_no
FROM rk_record
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
AND is_delete = '0'
</select>
<!-- 统计不同状态数量 -->
<select id="countDifferentDeliveryStatus" resultType="int">
SELECT COUNT(1)
FROM rk_record
WHERE bill_no = #{billNo}
AND is_delete = '0'
AND is_delivery != #{isDelivery}
</select>
<!-- 查询bill最小配送状态 -->
<select id="selectMinDeliveryStatusByBillNo"
resultType="int">
SELECT MIN(is_delivery)
FROM rk_record
WHERE bill_no = #{billNo}
AND is_delete = '0'
AND is_delivery >= 2
</select>
<update id="updateRecordByBillNo" parameterType="com.zg.project.wisdom.domain.RkRecord">
UPDATE rk_record
<set>
<if test="operationType != null">
operation_type = #{operationType},
</if>
<if test="teamCode != null">
team_code = #{teamCode},
</if>
<if test="operator != null">
operator = #{operator},
</if>
<if test="isDelivery != null">
is_delivery = #{isDelivery},
</if>
update_time = NOW()
</set>
WHERE bill_no = #{billNo}
AND is_delete = '0'
</update>
<update id="updateDw">
UPDATE rk_record
SET dw = #{dw}
WHERE sap_no = #{sapNo}
AND wl_no = #{wlNo}
AND xm_no = #{xmNo}
</update>
</mapper>

View File

@@ -2,541 +2,331 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zg.project.wisdom.mapper.RkStatisticsMapper">
<!-- 当前入库类型统计 -->
<select id="statCurrentInByOperationType"
resultType="com.zg.project.wisdom.domain.vo.StockStatisticGroupVO">
<!-- 库龄统计未出库按项目数统计COUNT DISTINCT xm_no -->
<select id="selectAgeStats" resultType="com.zg.project.wisdom.domain.vo.RkAgeStatVO">
SELECT
/* >10 天的项目数(至少一条记录满足 >10 天的 xm_no 个数) */
COUNT(DISTINCT CASE
WHEN ri.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), DATE(ri.rk_time)) > 10
THEN ri.xm_no END) AS gt10,
ri.operation_type AS groupValue,
sit.type_name AS groupName,
/* >20 天的项目数 */
COUNT(DISTINCT CASE
WHEN ri.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), DATE(ri.rk_time)) > 20
THEN ri.xm_no END) AS gt20,
IFNULL(SUM(ri.real_qty * ri.ht_dj), 0) AS totalAmount,
COUNT(DISTINCT ri.pcode) AS locationCount,
IFNULL(SUM(ri.real_qty), 0) AS totalQuantity,
COUNT(DISTINCT ri.xm_no) AS projectCount
/* >30 天的项目数 */
COUNT(DISTINCT CASE
WHEN ri.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), DATE(ri.rk_time)) > 30
THEN ri.xm_no END) AS gt30
FROM rk_info ri
WHERE ri.is_delete = 0
AND (ri.is_chuku = 0 OR ri.is_chuku IS NULL)
</select>
<!--
入库类型汇总SQL
统计每种入库类型(rk_type)下:
- 项目数量去重xm_no
- 合同单价合计(sumHtDj)
- 实际入库数量合计(sumRealQty)
- 金额合计(sumAmount = ht_dj * real_qty)
条件:
- 未删除(is_delete = 0)
- 未出库(is_chuku = 0)
-->
<select id="selectRkTypeSummary" resultType="com.zg.project.wisdom.domain.vo.RkTypeSummaryVO">
SELECT
ri.rk_type AS rkType,
COUNT(DISTINCT ri.xm_no) AS projectCount,
SUM(IFNULL(ri.ht_dj, 0)) AS sumHtDj,
SUM(IFNULL(ri.real_qty, 0)) AS sumRealQty,
SUM(IFNULL(ri.ht_dj, 0) * IFNULL(ri.real_qty, 0)) AS sumAmount
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 0
GROUP BY ri.rk_type
ORDER BY rkType
</select>
<!--
2) 入库类型 × 项目:统计每个项目的“条目数”
字段:
- rkType入库类型
- projectNoxm_no
- projectNameMAX(xm_ms)
- goodsCountCOUNT(*)
过滤is_delete=0 AND is_chuku=0
-->
<select id="selectProjectGoodsCountByType"
resultType="com.zg.project.wisdom.domain.vo.RkTypeProjectGoodsCountVO">
SELECT
ri.rk_type AS rkType,
ri.xm_no AS projectNo,
MAX(ri.xm_ms) AS projectName,
COUNT(*) AS goodsCount
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 0
GROUP BY ri.rk_type, ri.xm_no
ORDER BY rkType, projectNo
</select>
<!-- 入库类型汇总(未出库 & 过滤 NULL/空串/“无”) -->
<select id="selectTypeSummary" resultType="com.zg.project.wisdom.domain.vo.RkSummaryVO">
SELECT
sit.type_name AS groupName,
COUNT(DISTINCT ri.xm_no) AS projectCount,
COUNT(*) AS goodsCountTotal,
COALESCE(SUM(COALESCE(ri.real_qty, 0)), 0) AS sumQty,
COALESCE(SUM(COALESCE(ri.ht_dj, 0) * COALESCE(ri.real_qty, 0)), 0) AS sumAmount
FROM rk_info ri
LEFT JOIN stock_in_type sit
ON ri.rk_type = sit.type_code
WHERE ri.is_delete = 0
ON sit.type_code = ri.operation_type
AND sit.status = 1
WHERE ri.exec_status = 1
AND ri.is_chuku = 0
-- 入库类型过滤:剔除 NULL / 空串 / '无'
AND ri.rk_type IS NOT NULL
AND TRIM(ri.rk_type) &lt;&gt; ''
AND ri.rk_type &lt;&gt; '无'
-- 名称做一层过滤,避免出现“无”或空的分组
AND sit.type_name IS NOT NULL
AND TRIM(sit.type_name) &lt;&gt; ''
AND sit.type_name &lt;&gt; '无'
GROUP BY sit.type_name
ORDER BY groupName
AND ri.is_delete = 0
GROUP BY ri.operation_type, sit.type_name
</select>
<!-- 当前物资类型统计 -->
<select id="statCurrentByMaterialType"
resultType="com.zg.project.wisdom.domain.vo.StockStatisticGroupVO">
<!-- 县局汇总(未出库 & county 非空) -->
<select id="selectCountySummary" resultType="com.zg.project.wisdom.domain.vo.RkSummaryVO">
SELECT
ri.xj AS groupName, -- 县局
COUNT(DISTINCT ri.xm_no) AS projectCount, -- 项目数
COUNT(*) AS goodsCountTotal, -- 条目总数
SUM(IFNULL(ri.ht_dj, 0) * IFNULL(ri.real_qty, 0)) AS sumAmount -- 总金额
ri.wl_type AS groupValue,
mt.type_name AS groupName,
IFNULL(SUM(ri.real_qty * ri.ht_dj), 0) AS totalAmount,
COUNT(DISTINCT ri.pcode) AS locationCount,
IFNULL(SUM(ri.real_qty), 0) AS totalQuantity,
COUNT(DISTINCT ri.xm_no) AS projectCount
FROM rk_info ri
WHERE ri.is_delete = 0
LEFT JOIN material_type mt
ON mt.type_code = ri.wl_type
AND mt.status = 1
WHERE ri.exec_status = 1
AND ri.is_chuku = 0
AND ri.xj IS NOT NULL
AND TRIM(ri.xj) &lt;&gt; ''
GROUP BY ri.xj
ORDER BY groupName
AND ri.is_delete = 0
GROUP BY ri.wl_type, mt.type_name
</select>
<!-- 入库(整段时间 + 县局rk_time 在区间内xj 为空归类为“未知县局” -->
<select id="selectWeekDailyIn" resultType="com.zg.project.wisdom.domain.vo.DayCountyStatVO">
<!-- 入库类型统计(时间) -->
<select id="statInByOperationType"
resultType="com.zg.project.wisdom.domain.vo.StockStatisticGroupVO">
SELECT
COALESCE(NULLIF(TRIM(ri.xj), ''), '未知县局') AS xj,
COUNT(DISTINCT ri.xm_no) AS projectCount,
COUNT(*) AS itemCount,
SUM(COALESCE(ri.real_qty, 0)) AS totalQty,
SUM(COALESCE(ri.ht_dj, 0) * COALESCE(ri.real_qty, 0)) AS amountHt,
SUM(COALESCE(ri.jh_amt, 0)) AS amountPlan
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.rk_time BETWEEN #{startDate} AND #{endDate}
GROUP BY COALESCE(NULLIF(TRIM(ri.xj), ''), '未知县局')
ORDER BY xj
</select>
ri.operation_type AS groupValue,
sit.type_name AS groupName,
<!-- 出库(整段时间 + 县局ly_time 在区间内;只统计已出库 is_chuku=1xj 为空归类为“未知县局” -->
<select id="selectWeekDailyOut" resultType="com.zg.project.wisdom.domain.vo.DayCountyStatVO">
SELECT
COALESCE(NULLIF(TRIM(ri.xj), ''), '未知县局') AS xj,
COUNT(DISTINCT ri.xm_no) AS projectCount,
COUNT(*) AS itemCount,
SUM(COALESCE(ri.real_qty, 0)) AS totalQty,
SUM(COALESCE(ri.ht_dj, 0) * COALESCE(ri.real_qty, 0)) AS amountHt,
0 AS amountPlan
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 1
AND ri.ly_time BETWEEN #{startDate} AND #{endDate}
GROUP BY COALESCE(NULLIF(TRIM(ri.xj), ''), '未知县局')
ORDER BY xj
</select>
IFNULL(SUM(ri.real_qty * ri.ht_dj), 0) AS totalAmount,
COUNT(DISTINCT ri.pcode) AS locationCount,
IFNULL(SUM(ri.real_qty), 0) AS totalQuantity,
COUNT(DISTINCT ri.xm_no) AS projectCount
<!-- ======================
结果映射:汇总 AgeSummaryVO
====================== -->
<resultMap id="AgeSummaryMap" type="com.zg.project.wisdom.domain.vo.AgeSummaryVO">
<!-- 通过两个 association 绑定到内部 Slot 对象 -->
<association property="gt30" javaType="com.zg.project.wisdom.domain.vo.AgeSummaryVO$Slot">
<id property="dummyId" column="gt30_dummy_id"/>
<result property="projectCount" column="gt30ProjectCount"/>
<result property="goodsCount" column="gt30GoodsCount"/>
<result property="sumAmount" column="gt30Amount"/>
</association>
<association property="gt60" javaType="com.zg.project.wisdom.domain.vo.AgeSummaryVO$Slot">
<id property="dummyId" column="gt60_dummy_id"/>
<result property="projectCount" column="gt60ProjectCount"/>
<result property="goodsCount" column="gt60GoodsCount"/>
<result property="sumAmount" column="gt60Amount"/>
</association>
</resultMap>
<!-- 汇总查询:>30 / >60 的项目数、条目数、总金额 -->
<select id="selectAge3060Summary" resultMap="AgeSummaryMap">
SELECT
1 AS gt30_dummy_id,
(SELECT COUNT(DISTINCT ri.xm_no)
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 0
AND ri.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), ri.rk_time) > 30) AS gt30ProjectCount,
(SELECT COUNT(*)
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 0
AND ri.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), ri.rk_time) > 30) AS gt30GoodsCount,
(SELECT SUM(IFNULL(ri.ht_dj, 0) * IFNULL(ri.real_qty, 0))
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 0
AND ri.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), ri.rk_time) > 30) AS gt30Amount,
1 AS gt60_dummy_id,
(SELECT COUNT(DISTINCT ri.xm_no)
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 0
AND ri.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), ri.rk_time) > 60) AS gt60ProjectCount,
(SELECT COUNT(*)
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 0
AND ri.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), ri.rk_time) > 60) AS gt60GoodsCount,
(SELECT SUM(IFNULL(ri.ht_dj, 0) * IFNULL(ri.real_qty, 0))
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 0
AND ri.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), ri.rk_time) > 60) AS gt60Amount
FROM dual
</select>
<!-- ======================
结果映射:明细 RkAgeDetailVO
====================== -->
<resultMap id="RkAgeDetailMap" type="com.zg.project.wisdom.domain.vo.RkAgeDetailVO">
<id property="id" column="id"/>
<result property="xmNo" column="xmNo"/>
<result property="xmMs" column="xmMs"/>
<result property="rkTime" column="rkTime"/>
<result property="kuLingDays" column="kuLingDays"/>
<result property="htDj" column="htDj"/>
<result property="realQty" column="realQty"/>
<result property="amount" column="amount"/>
<result property="pcode" column="pcode"/>
<result property="trayCode" column="trayCode"/>
<result property="billNo" column="billNo"/>
</resultMap>
<!-- 明细查询:超过 minDays 天的所有未出库记录 -->
<select id="selectAgeDetails" parameterType="int" resultMap="RkAgeDetailMap">
SELECT
ri.id,
ri.xm_no AS xmNo,
ri.xm_ms AS xmMs,
ri.rk_time AS rkTime,
DATEDIFF(CURDATE(), ri.rk_time) AS kuLingDays,
ri.ht_dj AS htDj,
ri.real_qty AS realQty,
IFNULL(ri.ht_dj, 0) * IFNULL(ri.real_qty, 0) AS amount,
ri.pcode AS pcode,
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.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), ri.rk_time) > #{minDays}
ORDER BY ri.rk_time ASC
</select>
<resultMap id="RkTypePieMap" type="com.zg.project.wisdom.domain.vo.RkTypePieVO">
<result property="rkTypeCode" column="rkTypeCode"/> <!-- 类型编码 -->
<result property="rkTypeName" column="rkTypeName"/> <!-- 类型名称 -->
<result property="projectCount" column="projectCount"/> <!-- 项目数 -->
<result property="goodsCountTotal" column="goodsCountTotal"/> <!-- 条目总数 -->
<result property="sumQty" column="sumQty"/> <!-- 总数量 -->
<result property="sumAmount" column="sumAmount"/> <!-- 总金额 -->
</resultMap>
<!-- 入库类型饼图统计(联查 stock_in_type未删除 + 未出库rk_type 非空) -->
<select id="selectTypePie" resultMap="RkTypePieMap">
SELECT
COALESCE(sit.type_code, ri.rk_type) AS rkTypeCode,
COALESCE(sit.type_name, ri.rk_type) AS rkTypeName,
COUNT(DISTINCT ri.xm_no) AS projectCount,
COUNT(*) AS goodsCountTotal,
SUM(IFNULL(ri.real_qty, 0)) AS sumQty,
SUM(IFNULL(ri.ht_dj, 0) * IFNULL(ri.real_qty, 0)) AS sumAmount
FROM rk_info ri
LEFT JOIN stock_in_type sit
ON ri.rk_type = sit.type_code
WHERE ri.is_delete = 0
ON sit.type_code = ri.operation_type
AND sit.status = 1
WHERE ri.exec_status = 1
AND ri.is_chuku = 0
AND ri.rk_type IS NOT NULL
AND TRIM(ri.rk_type) != ''
GROUP BY COALESCE(sit.type_code, ri.rk_type),
COALESCE(sit.type_name, ri.rk_type)
ORDER BY sumAmount DESC
AND ri.is_delete = 0
<if test="startDate != null">
AND ri.operation_time <![CDATA[>=]]> #{startDate}
</if>
<if test="endDate != null">
AND ri.operation_time <![CDATA[<=]]> #{endDate}
</if>
GROUP BY ri.operation_type, sit.type_name
</select>
<!-- 按入库类型统计(时间范围;只统计类型有效,过滤 NULL/空/“无” -->
<select id="selectTypeSummaryByRange" resultMap="RkTypePieMap">
<![CDATA[
<!-- 库类型统计(时间) -->
<select id="statOutByOperationType"
resultType="com.zg.project.wisdom.domain.vo.StockStatisticGroupVO">
SELECT
COALESCE(sit.type_code, ri.rk_type) AS rkTypeCode,
COALESCE(sit.type_name, ri.rk_type) AS rkTypeName,
COUNT(DISTINCT ri.xm_no) AS projectCount,
COUNT(*) AS goodsCountTotal,
COALESCE(SUM(COALESCE(ri.real_qty, 0)), 0) AS sumQty,
COALESCE(SUM(COALESCE(ri.ht_dj, 0) * COALESCE(ri.real_qty, 0)), 0) AS sumAmount
rr.operation_type AS groupValue,
sot.type_name AS groupName,
IFNULL(SUM(rr.real_qty * rr.ht_dj), 0) AS totalAmount,
COUNT(DISTINCT rr.pcode) AS locationCount,
IFNULL(SUM(rr.real_qty), 0) AS totalQuantity,
COUNT(DISTINCT rr.xm_no) AS projectCount
FROM rk_record rr
LEFT JOIN stock_out_type sot
ON sot.type_code = rr.operation_type
AND sot.is_delete = '0'
WHERE rr.exec_status = '1'
AND rr.biz_type = '1' <!-- 出库 -->
AND rr.is_delete = '0'
<if test="startDate != null">
AND rr.operation_time <![CDATA[>=]]> #{startDate}
</if>
<if test="endDate != null">
AND rr.operation_time <![CDATA[<=]]> #{endDate}
</if>
GROUP BY rr.operation_type, sot.type_name
</select>
<select id="statHomeKpi"
resultType="com.zg.project.wisdom.domain.vo.HomeKpiVO">
SELECT
/* ===== 月入库 ===== */
COUNT(CASE WHEN rr.biz_type = '0'
AND DATE_FORMAT(rr.operation_time,'%Y-%m') = DATE_FORMAT(CURDATE(),'%Y-%m')
THEN 1 END) AS monthInCount,
COUNT(DISTINCT CASE WHEN rr.biz_type = '0'
AND DATE_FORMAT(rr.operation_time,'%Y-%m') = DATE_FORMAT(CURDATE(),'%Y-%m')
THEN rr.xm_no END) AS monthInProjectCount,
IFNULL(SUM(CASE WHEN rr.biz_type = '0'
AND DATE_FORMAT(rr.operation_time,'%Y-%m') = DATE_FORMAT(CURDATE(),'%Y-%m')
THEN rr.real_qty * rr.ht_dj END),0) AS monthInAmount,
/* ===== 月出库 ===== */
COUNT(CASE WHEN rr.biz_type = '1'
AND DATE_FORMAT(rr.operation_time,'%Y-%m') = DATE_FORMAT(CURDATE(),'%Y-%m')
THEN 1 END) AS monthOutCount,
COUNT(DISTINCT CASE WHEN rr.biz_type = '1'
AND DATE_FORMAT(rr.operation_time,'%Y-%m') = DATE_FORMAT(CURDATE(),'%Y-%m')
THEN rr.xm_no END) AS monthOutProjectCount,
IFNULL(SUM(CASE WHEN rr.biz_type = '1'
AND DATE_FORMAT(rr.operation_time,'%Y-%m') = DATE_FORMAT(CURDATE(),'%Y-%m')
THEN rr.real_qty * rr.ht_dj END),0) AS monthOutAmount
FROM rk_record rr
WHERE rr.exec_status = '1'
AND rr.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
LEFT JOIN stock_in_type sit
ON ri.rk_type = sit.type_code
WHERE ri.is_delete = 0
AND (ri.is_chuku = 0 OR ri.is_chuku IS NULL) -- 当前库存口径(保持你原条件)
-- 入库类型过滤:剔除 NULL / 空串 / '无'
AND ri.rk_type IS NOT NULL
AND TRIM(ri.rk_type) <> ''
AND ri.rk_type <> '无'
-- 若字典表匹配到了名称且名称为“无”,也剔除;未匹配(NULL)则保留
AND (sit.type_name IS NULL OR (TRIM(sit.type_name) <> '' AND sit.type_name <> '无'))
-- 时间范围:闭区间 [start, end]
AND ri.rk_time >= #{start}
AND ri.rk_time < DATE_ADD(#{end}, INTERVAL 1 DAY)
GROUP BY COALESCE(sit.type_code, ri.rk_type), COALESCE(sit.type_name, ri.rk_type)
ORDER BY sumAmount DESC
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 ri.xm_no) AS projectCnt,
ROUND(
COALESCE(SUM(ri.real_qty), 0),
3
) AS totalQty,
ROUND(
COALESCE(SUM(ri.real_qty * ri.ht_dj), 0),
3
) AS totalAmt
FROM rk_info ri
WHERE ri.is_delete = '0'
AND ri.is_chuku = '0'
AND TIMESTAMPDIFF(DAY, ri.operation_time, NOW()) > 30;
]]>
</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(p.pcode) AS totalSlot,
COUNT(u.pcode) AS usedSlot,
COUNT(p.pcode) - COUNT(u.pcode) AS unusedSlot
FROM pcde_detail p
LEFT JOIN (
SELECT DISTINCT
pcode,
cangku
FROM rk_info
WHERE is_delete = '0'
AND is_chuku = '0'
AND exec_status = '1'
) u
ON u.pcode = p.pcode
AND u.cangku = p.warehouse_code
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="selectOutTypeSummaryByRange"
resultType="com.zg.project.wisdom.domain.vo.RkTypePieVO">
<select id="selectStockAgeExport30"
resultType="com.zg.project.wisdom.domain.vo.StockAgeExportVO">
<![CDATA[
SELECT
COALESCE(sot.type_code, ri.ck_type) AS rkTypeCode,
COALESCE(sot.type_name, ri.ck_type) AS rkTypeName,
COUNT(DISTINCT COALESCE(NULLIF(TRIM(ri.xm_no_ck), ''), ri.xm_no)) AS projectCount,
COUNT(*) AS goodsCountTotal,
SUM(IFNULL(ri.real_qty, 0)) AS sumQty,
SUM(IFNULL(ri.ht_dj, 0) * IFNULL(ri.real_qty, 0)) AS sumAmount
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
LEFT JOIN stock_out_type sot ON ri.ck_type = sot.type_code
WHERE ri.is_delete = 0
AND ri.is_chuku = 1
-- 过滤出库类型为 NULL / 空串 / '无'
AND ri.ck_type IS NOT NULL
AND TRIM(ri.ck_type) <> ''
AND ri.ck_type <> '无'
AND (sot.type_name IS NULL OR TRIM(sot.type_name) <> '无')
-- 时间范围:闭区间 [start, end]
AND ri.ly_time >= #{start}
AND ri.ly_time < DATE_ADD(#{end}, INTERVAL 1 DAY)
GROUP BY COALESCE(sot.type_code, ri.ck_type), COALESCE(sot.type_name, ri.ck_type)
ORDER BY sumAmount DESC
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>
<!-- 入库rk_time 在区间内(不限制是否已出库,统计本月入库流水) -->
<select id="selectMonthlyInSummary" resultType="com.zg.project.wisdom.domain.vo.RkMonthSummaryVO">
SELECT
COUNT(DISTINCT ri.xm_no) AS projectCount,
COUNT(*) AS itemCount,
SUM(COALESCE(ri.real_qty, 0)) AS totalQty,
SUM(COALESCE(ri.ht_dj, 0) * COALESCE(ri.real_qty, 0)) AS amountHt
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.rk_time &gt;= #{start}
AND ri.rk_time &lt; #{end}
</select>
<!-- 出库is_chuku=1ly_time 在区间内 -->
<select id="selectMonthlyOutSummary" resultType="com.zg.project.wisdom.domain.vo.RkMonthSummaryVO">
SELECT
COUNT(DISTINCT ri.xm_no) AS projectCount,
COUNT(*) AS itemCount,
SUM(COALESCE(ri.real_qty, 0)) AS totalQty,
SUM(COALESCE(ri.ht_dj, 0) * COALESCE(ri.real_qty, 0)) AS amountHt
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 1
AND ri.ly_time IS NOT NULL
AND ri.ly_time &gt;= #{start}
AND ri.ly_time &lt; #{end}
</select>
<!-- 未到货status='0' -->
<select id="selectUndelivered" resultType="com.zg.project.wisdom.domain.vo.GysJhUndeliveredSummaryVO">
SELECT
COUNT(DISTINCT jh.xm_no) AS projectCount,
COUNT(*) AS itemCount,
SUM(COALESCE(jh.jh_qty, 0)) AS totalQty,
/* 金额 = 有效单价 × 剩余未到数量jh_qty */
SUM(
COALESCE(
NULLIF(jh.ht_dj, 0),
CASE WHEN COALESCE(jh.ht_qty, 0) > 0
THEN jh.jh_amt / jh.ht_qty
ELSE 0
END
) * COALESCE(jh.jh_qty, 0)
) AS amountPlan
FROM gys_jh jh
WHERE jh.is_delete = '0'
AND jh.status = '0'
</select>
<!-- 部分未到status='2' -->
<select id="selectPartialUndelivered" resultType="com.zg.project.wisdom.domain.vo.GysJhUndeliveredSummaryVO">
SELECT
COUNT(DISTINCT jh.xm_no) AS projectCount,
COUNT(*) AS itemCount,
SUM(COALESCE(jh.jh_qty, 0)) AS totalQty,
SUM(
COALESCE(
NULLIF(jh.ht_dj, 0),
CASE WHEN COALESCE(jh.ht_qty, 0) > 0
THEN jh.jh_amt / jh.ht_qty
ELSE 0
END
) * COALESCE(jh.jh_qty, 0)
) AS amountPlan
FROM gys_jh jh
WHERE jh.is_delete = '0'
AND jh.status = '2'
</select>
<!-- 当前库存:按物资类型统计
- 仅统计未出库is_chuku = 0 或 NULL
- 仅统计未删除is_delete = 0
- 过滤 wl_type 为空/空白/'无'
-->
<select id="selectCurrentStockByWlType"
resultType="com.zg.project.wisdom.domain.vo.WlTypeStockStatVO">
SELECT
mt.type_name AS typeName,
COUNT(DISTINCT ri.xm_no) AS projectCount,
COUNT(*) AS itemCount,
COALESCE(SUM(COALESCE(ri.real_qty, 0)), 0) AS totalQty,
COALESCE(SUM(COALESCE(ri.ht_dj, 0) * COALESCE(ri.real_qty, 0)), 0) AS amountHt
FROM rk_info ri
JOIN material_type mt
ON mt.type_code = ri.wl_type
AND mt.status = 1
WHERE ri.is_delete = 0
AND (ri.is_chuku = 0 OR ri.is_chuku IS NULL)
AND ri.wl_type IS NOT NULL
AND TRIM(ri.wl_type) != ''
AND ri.wl_type != '无'
AND mt.type_name != '无'
GROUP BY mt.type_code, mt.type_name
ORDER BY amountHt DESC
</select>
<!-- 当前库存:入库时间 > 30 天(仅未出库 & 未删除 & rk_time 非空) -->
<select id="selectAgeGt30Summary"
resultType="com.zg.project.wisdom.domain.vo.RkTypePieVO">
SELECT
COUNT(DISTINCT ri.xm_no) AS projectCount, -- 去重项目数
COUNT(*) AS goodsCountTotal, -- 条目数
COALESCE(SUM(COALESCE(ri.real_qty, 0)), 0) AS sumQty, -- 数量合计
COALESCE(SUM(COALESCE(ri.ht_dj, 0) * COALESCE(ri.real_qty, 0)), 0) AS sumAmount -- 金额合计
FROM rk_info ri
WHERE ri.is_delete = 0
AND (ri.is_chuku = 0 OR ri.is_chuku IS NULL)
AND ri.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), DATE(ri.rk_time)) > 30
</select>
<!-- 入库原始行 -->
<select id="selectInRange" resultType="com.zg.project.wisdom.domain.vo.RkInVO">
SELECT
ri.xm_no AS xmNo,
ri.rk_time AS rkTime
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.rk_time IS NOT NULL
AND ri.rk_time <![CDATA[>=]]> #{startTs}
AND ri.rk_time <![CDATA[<]]> #{endTs}
</select>
<!-- 出库原始行(有效时间=ly_time 优先,否则 update_time项目号=xm_no_ck 优先,否则 xm_no -->
<select id="selectOutRange" resultType="com.zg.project.wisdom.domain.vo.RkOutVO">
SELECT
ri.xm_no_ck AS xmNoCk,
ri.xm_no AS xmNo,
ri.ly_time AS lyTime,
ri.update_time AS updateTime
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 1
AND (
(ri.ly_time IS NOT NULL
AND ri.ly_time <![CDATA[>=]]> #{startTs}
AND ri.ly_time <![CDATA[<]]> #{endTs})
OR (ri.ly_time IS NULL AND ri.update_time IS NOT NULL
AND ri.update_time <![CDATA[>=]]> #{startTs}
AND ri.update_time <![CDATA[<]]> #{endTs})
)
</select>
<!-- 外层聚合到 SceneAvailableVO并级联 positionsPcdeDetailSimpleVO 列表) -->
<resultMap id="SceneAvailableMap"
type="com.zg.project.wisdom.domain.vo.SceneAvailableVO">
<result property="sceneCode" column="sceneCode"/>
<result property="sceneName" column="sceneName"/>
<result property="availableCount" column="availableCount"/>
<!-- 把外层 select 的 warehouseCode、sceneCode 作为参数传给内层查询 -->
<collection property="positions"
ofType="com.zg.project.wisdom.domain.vo.PcdeDetailSimpleVO"
select="selectAvailablePositionsByWarehouseAndScene"
column="{warehouseCode=warehouseCode,sceneCode=sceneCode}"/>
</resultMap>
<!-- 外层:按场景统计可用数量(并输出 warehouseCode、sceneCode 提供给内层) -->
<select id="selectAvailableByWarehouse" parameterType="string"
resultMap="SceneAvailableMap">
SELECT
#{warehouseCode} AS warehouseCode, -- 传给内层用
p.scene AS sceneCode,
sm.scene_name AS sceneName,
COUNT(*) AS availableCount
FROM pcde_detail p
LEFT JOIN scene_mapping sm
ON sm.scene_code = p.scene
LEFT JOIN rk_info ri
ON ri.pcode = p.pcode
AND ri.is_delete = 0
AND (ri.is_chuku = 0 OR ri.is_chuku IS NULL) -- 未出库 => 占用
WHERE p.is_delete = '0'
AND p.warehouse = #{warehouseCode}
AND ri.id IS NULL -- 无占用 => 可用
GROUP BY p.scene, sm.scene_name
ORDER BY p.scene
</select>
<!-- 内层:指定仓库 + 场景,查询该场景的可用库位明细 -->
<select id="selectAvailablePositionsByWarehouseAndScene"
resultType="com.zg.project.wisdom.domain.vo.PcdeDetailSimpleVO">
SELECT
p.pcode,
p.tag,
p.encoded_id AS encodedId
FROM pcde_detail p
LEFT JOIN rk_info ri
ON ri.pcode = p.pcode
AND ri.is_delete = 0
AND (ri.is_chuku = 0 OR ri.is_chuku IS NULL)
WHERE p.is_delete = '0'
AND p.warehouse = #{warehouseCode}
AND p.scene = #{sceneCode}
AND ri.id IS NULL
ORDER BY p.pcode
</select>
</mapper>