Compare commits

...

10 Commits

Author SHA1 Message Date
14944d2a99 出入库记录接口更新 2026-01-20 14:02:20 +08:00
a82797c475 移库记录去掉库位和仓库的对应关系校验
施工队新增时去掉编号,由代码生成
新增出入库记录查询接口
2026-01-20 10:20:39 +08:00
f8600107ec bug修改 2026-01-19 10:16:14 +08:00
79807279c1 bug修改 2026-01-16 11:40:44 +08:00
e4f617fc99 Merge remote-tracking branch 'origin/master' 2026-01-14 08:14:19 +08:00
1a565dba0d 生成单据号逻辑修改
线程池线程数修改
2026-01-14 08:14:04 +08:00
c3731b8b31 修改test 2026-01-09 15:09:34 +08:00
5d227b7b6b 去除mqtt功能
修改查询接口
2026-01-08 11:02:34 +08:00
f3d993b33a bug修复20260106 2026-01-06 14:19:58 +08:00
048d52b449 bug修复 2026-01-05 08:10:39 +08:00
47 changed files with 1497 additions and 1107 deletions

View File

@@ -15,7 +15,6 @@
<packaging>jar</packaging>
<name>zg</name>
<url>http://www.zg.vip</url>
<description>智慧实物管理系统</description>
<!-- ======================= -->

View File

@@ -1116,6 +1116,9 @@ public class ExcelUtil<T>
}
}
/**
* 添加单元格
*/
/**
* 添加单元格
*/
@@ -1126,11 +1129,13 @@ public class ExcelUtil<T>
{
// 设置行高
row.setHeight(maxHeight);
// 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列.
// 根据Excel中设置情况决定是否导出
if (attr.isExport())
{
// 创建cell
cell = row.createCell(column);
if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge())
{
if (subMergedLastRowNum >= subMergedFirstRowNum)
@@ -1138,14 +1143,45 @@ public class ExcelUtil<T>
sheet.addMergedRegion(new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column));
}
}
cell.setCellStyle(styles.get(StringUtils.format("data_{}_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType(), attr.wrapText())));
// 用于读取对象中的属性
Object value = getTargetValue(vo, field, attr);
// =========================
// ✅ BigDecimal按“实际几位显示几位”去掉末尾0核心修改点
// =========================
if (value instanceof BigDecimal)
{
BigDecimal bd = (BigDecimal) value;
if (-1 != attr.scale())
{
bd = bd.setScale(attr.scale(), attr.roundingMode());
}
// 去掉末尾01.000000 -> 11.2300 -> 1.23
String text = bd.stripTrailingZeros().toPlainString();
// 强制按字符串写入,避免 Excel 数值格式导致显示成固定小数位
cell.setCellType(CellType.STRING);
cell.setCellValue(StringUtils.isEmpty(text) ? attr.defaultValue() : text + attr.suffix());
// 统计
addStatisticsData(column, text, attr);
return cell;
}
// =========================
// 原有逻辑(非 BigDecimal保持不变
// =========================
String dateFormat = attr.dateFormat();
String readConverterExp = attr.readConverterExp();
String separator = attr.separator();
String dictType = attr.dictType();
if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value))
{
cell.getCellStyle().setDataFormat(this.wb.getCreationHelper().createDataFormat().getFormat(dateFormat));
@@ -1164,10 +1200,6 @@ public class ExcelUtil<T>
}
cell.setCellValue(sysDictMap.get(dictType + value));
}
else if (value instanceof BigDecimal && -1 != attr.scale())
{
cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).doubleValue());
}
else if (!attr.handler().equals(ExcelHandlerAdapter.class))
{
cell.setCellValue(dataFormatHandlerAdapter(value, attr, cell));
@@ -1177,6 +1209,7 @@ public class ExcelUtil<T>
// 设置列类型
setCellVo(value, attr, cell);
}
addStatisticsData(column, Convert.toStr(value), attr);
}
}

View File

@@ -18,16 +18,16 @@ import java.util.concurrent.ThreadPoolExecutor;
public class ThreadPoolConfig
{
// 核心线程池大小
private int corePoolSize = 50;
private int corePoolSize = 16;
// 最大可创建的线程数
private int maxPoolSize = 200;
private int maxPoolSize = 32;
// 队列最大长度
private int queueCapacity = 1000;
// 线程池维护线程所允许的空闲时间
private int keepAliveSeconds = 300;
private int keepAliveSeconds = 120;
@Bean(name = "threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor()

View File

@@ -54,7 +54,7 @@ public class ConstructionTeamController extends BaseController
}
/**
* 导入施工队数据(全部新增,不校验重复
* 导入施工队数据(全部新增)
*/
@PreAuthorize("@ss.hasPermi('information:construction:import')")
@Log(title = "施工队信息", businessType = BusinessType.IMPORT)

View File

@@ -38,6 +38,7 @@ 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

@@ -19,7 +19,7 @@ public class Mtd extends BaseEntity
private Long Id;
/** 物料号 */
@Excel(name = "物料号")
@Excel(name = "物料")
private String mid;
/** 物料描述 */

View File

@@ -2,6 +2,7 @@ package com.zg.project.information.mapper;
import java.util.List;
import com.zg.project.information.domain.ConstructionTeam;
import io.lettuce.core.dynamic.annotation.Param;
/**
* 施工队信息Mapper接口
@@ -35,6 +36,12 @@ public interface ConstructionTeamMapper
*/
public int insertConstructionTeam(ConstructionTeam constructionTeam);
/**
* 查询当前最大施工队编号
*/
String selectMaxTeamCode();
/**
* 修改施工队信息
*
@@ -58,4 +65,8 @@ public interface ConstructionTeamMapper
* @return 结果
*/
public int deleteConstructionTeamByIds(Long[] ids);
ConstructionTeam selectByTeamCode(@Param("teamCode") String teamCode);
ConstructionTeam selectByTeamName(@Param("teamName") String teamName);
}

View File

@@ -89,4 +89,7 @@ public interface PcdeDetailMapper
String selectWarehouseCodeByPcode(String pcode);
String selectEncodedIdByPcodeAndWarehouse(@Param("pcode") String pcode,
@Param("warehouseCode") String warehouseCode);
}

View File

@@ -1,7 +1,9 @@
package com.zg.project.information.service.impl;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.zg.common.exception.ServiceException;
import com.zg.common.utils.StringUtils;
@@ -11,6 +13,8 @@ import com.zg.project.information.mapper.ConstructionTeamMapper;
import com.zg.project.information.domain.ConstructionTeam;
import com.zg.project.information.service.IConstructionTeamService;
import static com.zg.common.utils.SecurityUtils.getUsername;
/**
* 施工队信息Service业务层处理
*
@@ -56,9 +60,36 @@ public class ConstructionTeamServiceImpl implements IConstructionTeamService
@Override
public int insertConstructionTeam(ConstructionTeam constructionTeam)
{
// 1⃣ 自动生成施工队编号6 位)
String teamCode = generateTeamCode();
constructionTeam.setTeamCode(teamCode);
// 2⃣ 补充若依通用字段(如果你没用 BaseEntity 自动处理)
constructionTeam.setCreatedBy(getUsername());
constructionTeam.setCreatedAt(new Date());
constructionTeam.setIsDelete("0");
return constructionTeamMapper.insertConstructionTeam(constructionTeam);
}
/**
* 生成施工队编号CT + 6位数字
* 示例CT000001
*/
private String generateTeamCode() {
String maxCode = constructionTeamMapper.selectMaxTeamCode();
if (maxCode == null) {
return "CT000001";
}
// 取数字部分
int num = Integer.parseInt(maxCode.substring(2));
num++;
return String.format("CT%06d", num);
}
/**
* 修改施工队信息
*
@@ -96,7 +127,7 @@ public class ConstructionTeamServiceImpl implements IConstructionTeamService
}
/**
* 导入施工队信息列表
* 导入施工队信息列表(名称 / 编号 去重)
*
* @param teamList 导入的施工队信息列表
* @param operName 操作人员
@@ -113,54 +144,106 @@ public class ConstructionTeamServiceImpl implements IConstructionTeamService
int successNum = 0;
int failureNum = 0;
StringBuilder failureMsg = new StringBuilder();
Date now = new Date();
// ===== ① Excel 内去重 =====
Set<String> teamCodeSet = new HashSet<>();
Set<String> teamNameSet = new HashSet<>();
for (ConstructionTeam team : teamList)
{
String teamCode = team.getTeamCode();
String teamName = team.getTeamName();
if (StringUtils.isBlank(teamCode) || StringUtils.isBlank(teamName))
{
failureNum++;
failureMsg.append("<br/>施工队名称/编号不能为空,已跳过。");
continue;
}
if (!teamCodeSet.add(teamCode))
{
failureNum++;
failureMsg.append("<br/>施工队编号重复Excel 内):").append(teamCode);
continue;
}
if (!teamNameSet.add(teamName))
{
failureNum++;
failureMsg.append("<br/>施工队名称重复Excel 内):").append(teamName);
continue;
}
}
// ===== ② 数据库内去重 =====
for (ConstructionTeam team : teamList)
{
try
{
// 可选:最低限度必填校验(建议保留,不然空行也会入库)
if (StringUtils.isBlank(team.getTeamName()) || StringUtils.isBlank(team.getTeamCode()))
// 跳过前面校验失败的数据
if (StringUtils.isBlank(team.getTeamCode()) || StringUtils.isBlank(team.getTeamName()))
{
failureNum++;
failureMsg.append("<br/>施工队名称/编号不能为空,已跳过。");
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)
{
failureNum++;
failureMsg.append("<br/>施工队名称已存在:").append(team.getTeamName());
continue;
}
// ===== 新增 =====
team.setCreatedBy(operName);
team.setCreatedAt(now);
team.setUpdatedBy(operName);
team.setUpdatedAt(now);
// isDelete 默认 0
if (StringUtils.isBlank(team.getIsDelete()))
{
team.setIsDelete("0");
}
// 走你现有 insertMapper/XML 已存在)
insertConstructionTeam(team);
constructionTeamMapper.insertConstructionTeam(team);
successNum++;
}
catch (Exception e)
{
failureNum++;
failureMsg.append("<br/>施工队编号:").append(team.getTeamCode())
.append(",施工队名称:").append(team.getTeamName())
.append(" 导入失败").append(e.getMessage());
failureMsg.append("<br/>施工队编号:")
.append(team.getTeamCode())
.append(",施工队名称")
.append(team.getTeamName())
.append(" 导入失败:")
.append(e.getMessage());
}
}
if (failureNum > 0)
{
failureMsg.insert(0, "导入完成,但有 " + failureNum + " 条记录失败:");
failureMsg.insert(0,
"导入完成,成功 " + successNum + " 条,失败 " + failureNum + " 条:");
throw new ServiceException(failureMsg.toString());
}
return "导入成功,共 " + successNum + " 条数据";
}
}

View File

@@ -7,6 +7,8 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import com.zg.common.exception.ServiceException;
import com.zg.common.utils.StringUtils;
import com.zg.project.wisdom.domain.GysJh;
import com.zg.project.wisdom.domain.vo.PlanToMtdVO;
import com.zg.project.wisdom.mapper.GysJhMapper;
import org.springframework.beans.factory.annotation.Autowired;
@@ -67,9 +69,37 @@ public class MtdServiceImpl implements IMtdService
* @return 结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int insertMtd(Mtd mtd)
{
return mtdMapper.insertMtd(mtd);
if (mtd == null || StringUtils.isBlank(mtd.getMid())) {
throw new ServiceException("物料号不能为空");
}
// 1⃣ 先新增物料字典
int rows = mtdMapper.insertMtd(mtd);
// 2⃣ 新增成功后,开始“反查供应计划”
if (rows > 0 && StringUtils.isNotBlank(mtd.getUnt())) {
String wlNo = mtd.getMid().trim();
String dw = mtd.getUnt().trim();
// 3⃣ 根据物料号查询供应计划(可能多条)
List<GysJh> jhList = gysJhMapper.selectByWlNo(wlNo);
if (jhList != null && !jhList.isEmpty()) {
for (GysJh jh : jhList) {
// 4⃣ 只处理「供应计划中单位为空」的记录
if (StringUtils.isBlank(jh.getDw())) {
gysJhMapper.updateDwById(jh.getId(), dw);
}
}
}
}
return rows;
}
/**

View File

@@ -1,182 +0,0 @@
package com.zg.project.wisdom.config;
import com.zg.project.wisdom.mqtt.dispatcher.MqttMessageDispatcher;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
/**
* MQTT 客户端初始化配置类
*
* 作用说明:
* 1. Spring Boot 启动时自动创建 MQTT 客户端
* 2. 连接 EMQX Broker
* 3. 订阅 application.yml 中配置的 Topic支持通配符
* 4. 接收传感器上报的数据
*
* 说明:
* - 同一个 MQTT Client 同时支持「订阅(接收)」和「发布(下发指令)」
* - 后端一般只需要一个 Client
*/
@Slf4j
@Configuration
public class MqttClientConfig {
/**
* MQTT 客户端实例
* 由本配置类统一维护
*/
private MqttClient mqttClient;
@Resource
private MqttMessageDispatcher mqttMessageDispatcher;
/**
* 创建 MQTT Client Bean
*
* @param props MQTT 配置(来自 application.yml
*/
@Bean
public MqttClient mqttClient(MqttProperties props) throws MqttException {
// 如果未开启 MQTT 功能,直接不初始化
// 注意:此时 mqttClient Bean 为 null
if (!props.isEnabled()) {
log.warn("[MQTT] mqtt.enabled=falseMQTT 功能未启用");
return null;
}
// 1. 创建 MQTT 客户端
mqttClient = new MqttClient(
props.getBroker(),
props.getClientId(),
new MemoryPersistence()
);
// 2. MQTT 连接参数
MqttConnectOptions options = new MqttConnectOptions();
// 是否清除会话:
// true - 断开后清除订阅(推荐后端使用)
// false - 保留会话(多用于设备端)
options.setCleanSession(props.isCleanSession());
// 心跳时间(秒)
options.setKeepAliveInterval(props.getKeepAlive());
// 连接超时时间(秒)
options.setConnectionTimeout(props.getTimeout());
// 自动重连(非常重要,防止网络抖动)
options.setAutomaticReconnect(true);
// 设置用户名密码(如果配置了)
if (props.getUsername() != null && !props.getUsername().trim().isEmpty()) {
options.setUserName(props.getUsername().trim());
}
if (props.getPassword() != null && !props.getPassword().trim().isEmpty()) {
options.setPassword(props.getPassword().toCharArray());
}
// 3. 设置 MQTT 回调
mqttClient.setCallback(new MqttCallbackExtended() {
/**
* 连接成功(或重连成功)回调
*/
@Override
public void connectComplete(boolean reconnect, String serverURI) {
log.info("[MQTT] 连接成功 reconnect={}, serverURI={}", reconnect, serverURI);
// 连接成功后订阅 Topic
subscribe(props);
}
/**
* 连接丢失回调
*/
@Override
public void connectionLost(Throwable cause) {
log.warn("[MQTT] 连接断开", cause);
}
/**
* 接收到消息回调
*
* @param topic 消息主题(用于区分设备、类型)
* @param message 消息内容
*/
@Override
public void messageArrived(String topic, MqttMessage message) {
String payload = new String(message.getPayload(), StandardCharsets.UTF_8);
mqttMessageDispatcher.dispatch(topic, payload);
}
/**
* 消息发送完成回调(发布消息后触发)
*/
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
// 后端下发指令时可用于确认发送完成
}
});
// 4. 连接 Broker
log.info("[MQTT] 正在连接 Broker{}", props.getBroker());
mqttClient.connect(options);
log.info("[MQTT] MQTT 已连接");
// 5. 启动时先订阅一次(防止 connectComplete 未触发)
subscribe(props);
return mqttClient;
}
/**
* 订阅 Topic支持通配符
*/
private void subscribe(MqttProperties props) {
if (mqttClient == null || !mqttClient.isConnected()) {
log.warn("[MQTT] 订阅失败:客户端未连接");
return;
}
if (props.getTopics() == null || props.getTopics().isEmpty()) {
log.warn("[MQTT] 未配置订阅 Topic");
return;
}
try {
for (String topic : props.getTopics()) {
if (topic == null || topic.trim().isEmpty()) {
continue;
}
mqttClient.subscribe(topic.trim(), props.getQos());
log.info("[MQTT] 已订阅 Topic{}QoS={}", topic.trim(), props.getQos());
}
} catch (Exception e) {
log.error("[MQTT] 订阅 Topic 失败", e);
}
}
/**
* Spring 容器关闭时,优雅断开 MQTT 连接
*/
@PreDestroy
public void destroy() {
try {
if (mqttClient != null) {
if (mqttClient.isConnected()) {
mqttClient.disconnect();
}
mqttClient.close();
}
} catch (Exception ignored) {
}
}
}

View File

@@ -1,46 +0,0 @@
package com.zg.project.wisdom.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* MQTT 配置属性(对应 application.yml 中 mqtt.*
*/
@Data
@Component
@ConfigurationProperties(prefix = "mqtt")
public class MqttProperties {
/** 是否启用 MQTT */
private boolean enabled;
/** Broker 地址tcp://192.168.1.29:1883 */
private String broker;
/** 客户端ID在 EMQX 中唯一) */
private String clientId;
/** 账号 */
private String username;
/** 密码 */
private String password;
/** 是否清除会话 */
private boolean cleanSession = true;
/** 心跳时间(秒) */
private int keepAlive = 30;
/** 连接超时时间(秒) */
private int timeout = 10;
/** 默认 QoS */
private int qos = 1;
/** 订阅 Topic 列表(支持通配符) */
private List<String> topics;
}

View File

@@ -1,74 +0,0 @@
package com.zg.project.wisdom.config;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
/**
* MQTT 消息发布客户端
*
* 作用说明:
* - 专门用于向指定设备下发控制指令
* - 例如:开启 / 关闭 / 重启 传感器
*
* 说明:
* - 依赖同一个 MqttClient
* - 发布消息时使用“精确 Topic”不能使用通配符
*/
@Slf4j
@Component
public class MqttPublishClient {
/**
* MQTT 客户端(由 Spring 注入)
*/
private final MqttClient mqttClient;
public MqttPublishClient(MqttClient mqttClient) {
this.mqttClient = mqttClient;
}
/**
* 发布 MQTT 消息(默认 QoS=1不保留消息
*
* @param topic 目标 Topic必须是具体设备的 Topic
* @param payload 消息内容JSON 字符串)
*/
public void publish(String topic, String payload) {
publish(topic, payload, 1, false);
}
/**
* 发布 MQTT 消息(自定义参数)
*/
public void publish(String topic, String payload, int qos, boolean retained) {
// 如果 MQTT 未启用或未初始化
if (mqttClient == null) {
throw new IllegalStateException("MQTT 客户端未初始化mqtt.enabled=false");
}
// 如果 MQTT 尚未连接
if (!mqttClient.isConnected()) {
throw new IllegalStateException("MQTT 客户端未连接到 Broker");
}
try {
MqttMessage msg = new MqttMessage(payload.getBytes(StandardCharsets.UTF_8));
msg.setQos(qos);
msg.setRetained(retained);
mqttClient.publish(topic, msg);
log.info("[MQTT] 指令已发送 topic={}, qos={}, retained={}, payload={}",
topic, qos, retained, payload);
} catch (Exception e) {
log.error("[MQTT] 指令发送失败 topic=" + topic, e);
throw new RuntimeException(e);
}
}
}

View File

@@ -80,11 +80,13 @@ public class AgvTaskResultController extends BaseController {
*/
@PostMapping("/upGoods")
public AjaxResult upGoods(@RequestBody OutGoodsDTO dto) {
if (StringUtils.isBlank(dto.getTaskNo())) {
throw new ServiceException("taskNo 不能为空");
}
// 返回 taskId
String taskId = agvTaskResultService.handleUpGoods(dto.getTaskNo(), dto.getMaterialStatus());
return AjaxResult.success("上架任务已提交至WCS").put("taskId", taskId);
}

View File

@@ -22,7 +22,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
* @author zg
*/
@RestController
@RequestMapping("/wisdom/task")
@RequestMapping("/wisdom/dispatch")
public class DdTaskController extends BaseController {
@Autowired

View File

@@ -33,7 +33,7 @@ public class GysJhController extends BaseController
@Autowired
private IGysJhService gysJhService;
/**A
/**
* 查询供应计划列表
*/
@PreAuthorize("@ss.hasPermi('plan:jh:list')")
@@ -52,10 +52,8 @@ public class GysJhController extends BaseController
*/
// @PreAuthorize("@ss.hasPermi('plan:jh:list')")
@GetMapping("/getBySapNo")
public AjaxResult getBySapNo(String sapNo)
{
List<GysJh> list = gysJhService.getBySapNo(sapNo);
return success(list);
public AjaxResult getBySapNo(@RequestParam String sapNo) {
return gysJhService.getBySapNo(sapNo);
}
/**

View File

@@ -6,11 +6,11 @@ import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import com.github.pagehelper.PageHelper;
import com.zg.common.utils.StringUtils;
import com.zg.project.wisdom.domain.dto.*;
import com.zg.project.wisdom.domain.vo.DeliveryBillVO;
import com.zg.project.wisdom.domain.vo.PcodeQtyVO;
import com.zg.project.wisdom.service.QwenOcrRemoteService;
import io.swagger.annotations.ApiOperation;
import com.zg.project.wisdom.service.RkStatisticsService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -67,17 +67,21 @@ public class RkInfoController extends BaseController
return getDataTable(list);
}
/**
* 库存查询
* @param dto
* @return
*/
@PostMapping("/pageStatistics")
public Map<String, Object> pageStatistics(@RequestBody RkInfoQueryDTO dto) {
// 使用 PageHelper 分页
PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
List<RkInfo> list = rkInfoService.selectAllRkInfo(dto);
Map<String,Object> dataInfo = new HashMap<>();
dataInfo.put("dataList",getDataTable(list));
return dataInfo;
List<RkInfo> list = rkInfoService.selectAllRkInfo(dto);
Map<String, Object> dataInfo = new HashMap<>();
dataInfo.put("dataList", getDataTable(list));
return dataInfo;
}
/**
@@ -97,14 +101,51 @@ public class RkInfoController extends BaseController
}
// @PreAuthorize("@ss.hasPermi('wisdom:stock:list')")
@PostMapping("/bill/groups")
public TableDataInfo billGroups(@RequestBody RkInfoQueryDTO query) {
// 分页
PageHelper.startPage(query.getPageNum(), query.getPageSize());
// 查询
List<RkInfo> rows = rkInfoService.selectGroupedByBill(query);
// @PostMapping("/bill/groups")
// public TableDataInfo billGroups(@RequestBody RkInfoQueryDTO query) {
// // 分页
// PageHelper.startPage(query.getPageNum(), query.getPageSize());
// // 查询
// List<RkInfo> rows = rkInfoService.selectGroupedByBill(query);
//
// return getDataTable(rows);
// }
return getDataTable(rows);
/**
* 获取单据列表(轻量级:仅返回单据头公共信息,不聚合明细字段)
*/
@PostMapping("/bill/groups")
public TableDataInfo billHeaderList(@RequestBody RkInfoQueryDTO query) {
// 1. 分页
PageHelper.startPage(query.getPageNum(), query.getPageSize());
// 2. 查询
List<RkInfo> list = rkInfoService.selectBillHeaderList(query);
return getDataTable(list);
}
/**
* 导出出入库记录
*/
@PreAuthorize("@ss.hasPermi('wisdom:stock:export')")
@Log(title = "出入库记录", businessType = BusinessType.EXPORT)
@PostMapping("/exportPageList")
public void exportPageList(HttpServletResponse response, RkInfoQueryDTO query)
{
List<RkInfo> list = rkInfoService.selectRkInfoPageList(query);
ExcelUtil<RkInfo> util = new ExcelUtil<>(RkInfo.class);
util.exportExcel(response, list, "出入库记录导出");
}
/**
* 出入库查询
*/
@PostMapping("/pageList")
public TableDataInfo pageList(@RequestBody RkInfoQueryDTO dto) {
PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
List<RkInfo> list = rkInfoService.selectRkInfoPageList(dto);
return getDataTable(list);
}
/**
@@ -117,7 +158,7 @@ public class RkInfoController extends BaseController
{
List<RkInfo> list = rkInfoService.selectRkInfoList(rkInfo);
ExcelUtil<RkInfo> util = new ExcelUtil<RkInfo>(RkInfo.class);
util.exportExcel(response, list, "库存单据主数据");
util.exportExcel(response, list, "数据导出");
}
/**
@@ -146,12 +187,25 @@ public class RkInfoController extends BaseController
*/
@PreAuthorize("@ss.hasPermi('wisdom:stock:edit')")
@Log(title = "库存单据主", businessType = BusinessType.UPDATE)
@PutMapping("/update")
@PostMapping("/update")
public AjaxResult edit(@RequestBody RkInfo rkInfo)
{
return toAjax(rkInfoService.updateRkInfo(rkInfo));
}
/**
* 根据出库单据号修改库存单据
*/
@PreAuthorize("@ss.hasPermi('wisdom:stock:edit')")
@Log(title = "库存单据-按出库单号修改", businessType = BusinessType.UPDATE)
@PostMapping("/updateByBillNoCk")
public AjaxResult updateByBillNoCk(@RequestBody RkInfo rkInfo) {
if (StringUtils.isBlank(rkInfo.getBillNoCk())) {
return AjaxResult.error("出库单据号 billNoCk 不能为空");
}
return toAjax(rkInfoService.updateRkInfoByBillNoCk(rkInfo));
}
/**
* 删除库存单据主
*/
@@ -353,16 +407,5 @@ public class RkInfoController extends BaseController
}
@PostMapping("/statistics")
public Map<String, Object> statistics(@RequestBody RkInfoQueryDTO dto) {
Long sumMoney = rkInfoService.selectStatistics(dto);
Long pcdeCount = rkInfoService.selectPcde(dto);
Map<String, Object> dataInfo = new HashMap<>();
dataInfo.put("sumMoney", sumMoney);
dataInfo.put("pcdeCount", pcdeCount);
return dataInfo;
}
}

View File

@@ -1,6 +1,7 @@
package com.zg.project.wisdom.controller;
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.service.RkStatisticsService;
import com.zg.project.wisdom.service.WarehouseStatService;
@@ -178,4 +179,10 @@ public class RkStatisticsController {
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

@@ -1,6 +1,9 @@
package com.zg.project.wisdom.domain;
import java.math.BigDecimal;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.zg.framework.aspectj.lang.annotation.Excel;
@@ -19,6 +22,10 @@ public class GysJh extends BaseEntity
/** 主键ID */
private Long id;
/** 行号 */
@Excel(name = "行号")
private String xh;
/** 序号 */
@Excel(name = "序号")
private Long indexNo;
@@ -59,25 +66,24 @@ public class GysJh extends BaseEntity
@Excel(name = "合同单价")
private BigDecimal htDj;
/** 合同数量 */
@Excel(name = "合同数量")
private BigDecimal htQty;
/** SAP订单编号 */
@Excel(name = "SAP订单编号")
private String sapNo;
/** 行号 */
@Excel(name = "行号")
private String xh;
/** 计划交货数量 */
@Excel(name = "计划交货数量")
private BigDecimal jhQty;
/** 合同数量 */
private BigDecimal htQty;
/** 计量单位 */
@Excel(name = "计量单位")
private String dw;
/** 0未到货1已入库2部分入库 */
@Excel(name = "0未到货1已入库2部分入库")
// @Excel(name = "0未到货,1已入库,2部分入库")
private String status;
/** 身份码 */
@@ -89,9 +95,17 @@ public class GysJh extends BaseEntity
private String remark;
/** 是否删除0正常 1删除 */
@Excel(name = "是否删除", readConverterExp = "0=正常,1=删除")
// @Excel(name = "是否删除", readConverterExp = "0=正常,1=删除")
private String isDelete;
/** 查询开始时间yyyy-MM-dd HH:mm:ss */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date beginTime;
/** 查询结束时间yyyy-MM-dd HH:mm:ss */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endTime;
public void setId(Long id)
{
this.id = id;
@@ -278,6 +292,23 @@ public class GysJh extends BaseEntity
return isDelete;
}
public Date getBeginTime() {
return beginTime;
}
public void setBeginTime(Date beginTime) {
this.beginTime = beginTime;
}
public Date getEndTime() {
return endTime;
}
public void setEndTime(Date endTime) {
this.endTime = endTime;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)

View File

@@ -29,8 +29,12 @@ public class RkInfo extends BaseEntity {
@TableField(exist = false)
private List<Long> ids;
/** 是否已出库0已入库1已出库2待审批3借料出库4入库撤销5出库撤销 */
@Excel(name = "库存状态", readConverterExp = "0=已入库,1=已出库,2=待审批,3=借料出库,4=入库撤销,5=出库撤销")
private String isChuku;
/** 供应计划ID对应供应计划表主键 */
@Excel(name = "供应计划ID")
// @Excel(name = "供应计划ID")
private Long gysJhId;
/** 审批人ID非数据库字段 */
@@ -70,7 +74,7 @@ public class RkInfo extends BaseEntity {
/** 所属大仓编码 */
@TableField(exist = false)
@Excel(name = "所属大仓编码")
// @Excel(name = "所属大仓编码")
private String parentWarehouseCode;
/** 所属大仓名称 */
@@ -80,7 +84,7 @@ public class RkInfo extends BaseEntity {
/** 所属小仓编码 */
@TableField(exist = false)
@Excel(name = "所属小仓编码")
// @Excel(name = "所属小仓编码")
private String warehouseCode;
/** 所属小仓名称 */
@@ -105,16 +109,13 @@ public class RkInfo extends BaseEntity {
private Date returnTime;
/** 理货员 */
@Excel(name = "理货员")
// @Excel(name = "理货员")
private String lihuoY;
/** 理货员名称(联查显示用,导出专用) */
@Excel(name = "理货员")
private String lihuoYName;
/** 是否已出库0已入库1已出库2待审批3借料出库4入库撤销5出库撤销 */
@Excel(name = "是否已出库", readConverterExp = "0已入库1已出库2待审批3借料出库4入库撤销5出库撤销")
private String isChuku;
/** 单据号 */
@Excel(name = "单据号")
private String billNo;
@@ -124,11 +125,11 @@ public class RkInfo extends BaseEntity {
private String billNoCk;
/** 是否需要配送(0否 1是 2配送中 3配送完成) */
@Excel(name = "是否需要配送", readConverterExp = "0=否,1=是,2=配送中,3=配送完成")
// @Excel(name = "是否需要配送", readConverterExp = "0=否,1=是,2=配送中,3=配送完成")
private String isDelivery;
/** 县局 */
@Excel(name = "县局")
// @Excel(name = "县局")
private String xj;
/** 项目号 */
@@ -139,10 +140,10 @@ public class RkInfo extends BaseEntity {
@Excel(name = "库存项目描述")
private String xmMs;
@Excel(name = "领取方项目号")
// @Excel(name = "领取方项目号")
private String xmNoCk;
@Excel(name = "领取方项目描述")
// @Excel(name = "领取方项目描述")
private String xmMsCk;
/** 物料号 */
@@ -174,7 +175,7 @@ public class RkInfo extends BaseEntity {
private String sapNo;
/** 行号 */
@Excel(name = "行号")
// @Excel(name = "行号")
private String xh;
/** 计划交货数量 */
@@ -193,12 +194,19 @@ public class RkInfo extends BaseEntity {
@Excel(name = "实际入库数量")
private BigDecimal realQty;
/** 总金额 = 合同单价 × 实际入库数量(导出专用) */
@Excel(name = "总金额")
private BigDecimal totalAmount;
@Excel(name = "备注")
private String remark;
/** 库位码(编码) */
@Excel(name = "库位码")
private String pcode;
/** 库位主键IDpcde_detail.id */
@Excel(name = "库位主键ID")
// @Excel(name = "库位主键ID")
private String pcodeId;
/** 托盘码 */
@@ -206,7 +214,7 @@ public class RkInfo extends BaseEntity {
private String trayCode;
/** 实物ID */
@Excel(name = "实物ID")
// @Excel(name = "实物ID")
private String entityId;
/** 一货一图 - 货物照片URL非表字段 */
@@ -227,21 +235,21 @@ public class RkInfo extends BaseEntity {
private String teamName;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "领用时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "出库时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date lyTime;
@Excel(name = "出库备注")
private String ckRemark;
@Excel(name = "出库备注")
// @Excel(name = "审核状态")
private String status;
/** 是否移库过0否 1是 */
@Excel(name = "是否移库过")
// @Excel(name = "是否移库过")
private String hasMoved;
/** 是否借料0否 1是 2已归还 */
@Excel(name = "是否借料", readConverterExp = "0=否,1=是,2=已归还")
// @Excel(name = "是否借料", readConverterExp = "0=否,1=是,2=已归还")
private String isBorrowed;
/** 签字图片URLimage_type = sign非表字段 */
@@ -452,6 +460,14 @@ public class RkInfo extends BaseEntity {
public String getCkRemark() { return ckRemark; }
public void setCkRemark(String ckRemark) { this.ckRemark = ckRemark; }
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
@@ -488,6 +504,14 @@ public class RkInfo extends BaseEntity {
public String getFycde2() { return fycde2; }
public void setFycde2(String fycde2) { this.fycde2 = fycde2; }
public BigDecimal getTotalAmount() {
return totalAmount;
}
public void setTotalAmount(BigDecimal totalAmount) {
this.totalAmount = totalAmount;
}
public String getIsDelete() { return isDelete; }
public void setIsDelete(String isDelete) { this.isDelete = isDelete; }

View File

@@ -1,9 +1,17 @@
package com.zg.project.wisdom.domain.dto;
import com.zg.framework.aspectj.lang.annotation.Excel;
import lombok.Data;
@Data
public class OutGoodsDTO {
private String taskNo;
private Integer materialStatus; // 0=空托1=有货(影响是否传物料信息)
/**
* 调度模式
* 1 = 仅立库WCS
* 2 = 立库 + AGV
*/
@Excel(name = "调度模式", readConverterExp = "1=仅立库,2=立库+AGV")
private Integer dispatchMode;
}

View File

@@ -14,6 +14,9 @@ public class RefundRequestDTO {
/** 新库位码 */
private String pcode;
/** 新仓库 */
private String warehouseCode;
/** 入库类型 */
private String rkType;
}

View File

@@ -20,10 +20,10 @@ public class RkInfoQueryDTO extends RkInfo {
private Integer pageSize;
/** 开始时间 */
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date statDate;
/** 结束时间 */
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date endDate;
}

View File

@@ -1,7 +1,10 @@
package com.zg.project.wisdom.domain.dto;
import com.zg.framework.aspectj.lang.annotation.Excel;
import lombok.Data;
import java.math.BigDecimal;
/**
* 出库明细 DTO
*/
@@ -20,6 +23,9 @@ public class StockOutItemDTO {
/** 托盘码 */
private String trayCode;
/** 实际数量 */
private BigDecimal realQty;
/** 现场照片(单张) */
private String photoUrl; // ✅ 一物一图绑定

View File

@@ -0,0 +1,79 @@
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

@@ -120,5 +120,16 @@ public interface GysJhMapper
*/
List<PlanToMtdVO> selectMissingMtdItems();
/**
* 根据物料号查询供应计划(用于回填单位)
*/
List<GysJh> selectByWlNo(@Param("wlNo") String wlNo);
/**
* 根据主键更新单位
*/
int updateDwById(@Param("id") Long id,
@Param("dw") String dw);
}

View File

@@ -1,6 +1,7 @@
package com.zg.project.wisdom.mapper;
import java.util.List;
import java.util.Map;
import com.zg.project.inventory.domain.vo.PcdeCntVO;
import com.zg.project.inventory.domain.vo.RkInfoMatchVO;
@@ -34,12 +35,14 @@ public interface RkInfoMapper
/**
* 使用 selectRkInfoVo 作为子查询,外层按 bill_no 分组聚合
* 不新增 resultMap / VO直接用 RkInfoResult 映射需要的字段
*/
List<RkInfo> selectGroupedByBill(@Param("q") RkInfoQueryDTO query,
@Param("needAudit") Integer needAudit);
List<RkInfo> selectBillHeaderList(@Param("q") RkInfoQueryDTO query,
@Param("needAudit") Integer needAudit);
/**
* 修改库存单据主
*
@@ -48,6 +51,14 @@ public interface RkInfoMapper
*/
public int updateRkInfo(RkInfo rkInfo);
/**
* 根据出库单据号更新库存单据
*
* @param rkInfo 库存单据
* @return 影响行数
*/
int updateByBillNoCk(RkInfo rkInfo);
/**
* 删除库存单据主
*
@@ -107,7 +118,7 @@ public interface RkInfoMapper
List<RkInfo> selectTopOverdueStock(@Param("limit") int limit);
/**
* 出库操作
*
* @param update
*/
void updateById(RkInfo update);
@@ -226,9 +237,10 @@ public interface RkInfoMapper
*/
List<RkInfo> selectAllRkInfo(RkInfo query);
Long selectStatistics(RkInfo query);
Long selectPcde(RkInfo query);
/**
* 出入库明细分页查询(统计页面)
*/
List<RkInfo> selectRkInfoPageList(RkInfo query);
int updateBillInfo(RkInfo query);
@@ -254,4 +266,7 @@ public interface RkInfoMapper
RkInfo selectHeaderByBillNo(String billNo);
String selectMaxBillNo(@Param("prefix") String prefix);
Map<String, Object> selectStockStatistics(RkInfoQueryDTO query);
}

View File

@@ -1,55 +0,0 @@
package com.zg.project.wisdom.mqtt.dispatcher;
import com.zg.project.wisdom.mqtt.handler.HumiSensorHandler;
import com.zg.project.wisdom.mqtt.handler.SmokeSensorHandler;
import com.zg.project.wisdom.mqtt.handler.TempSensorHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* MQTT消息分发器
* 负责根据MQTT主题(topic)将消息分发给相应的传感器处理器
*/
@Slf4j
@Component
public class MqttMessageDispatcher {
@Autowired
private TempSensorHandler tempSensorHandler;
@Autowired
private HumiSensorHandler humiSensorHandler;
@Autowired
private SmokeSensorHandler smokeSensorHandler;
/**
* 分发MQTT消息到对应的处理器
* 根据topic的前缀判断消息类型并调用相应的处理器进行处理
*
* @param topic MQTT主题用于判断消息类型
* @param payload 消息负载,包含具体的传感器数据
*/
public void dispatch(String topic, String payload) {
// 处理温度传感器消息
if (topic.startsWith("zg/wms/test/temp/")) {
tempSensorHandler.handle(topic, payload);
return;
}
// 处理湿度传感器消息
if (topic.startsWith("zg/wms/test/humi/")) {
humiSensorHandler.handle(topic, payload);
return;
}
// 处理烟雾传感器消息
if (topic.startsWith("zg/wms/test/smoke/")) {
smokeSensorHandler.handle(topic, payload);
return;
}
log.warn("未知 MQTT Topic: {}", topic);
}
}

View File

@@ -1,43 +0,0 @@
package com.zg.project.wisdom.mqtt.handler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 湿度传感器 Handler
* Topic 示例zg/wms/test/humi/{deviceId}
*/
@Slf4j
@Component
public class HumiSensorHandler {
public static final String TOPIC_PREFIX = "zg/wms/test/humi/";
/**
* 处理湿度传感器消息
* @param topic MQTT主题
* @param payload 消息负载
*/
public void handle(String topic, String payload) {
String deviceId = resolveDeviceId(topic, TOPIC_PREFIX);
log.info("[HUMI] deviceId={}, topic={}, payload={}", deviceId, topic, payload);
// TODO: 这里后续写湿度业务逻辑
}
/**
* 从MQTT主题中解析设备ID
* @param topic MQTT主题
* @param prefix 主题前缀
* @return 设备ID
*/
private String resolveDeviceId(String topic, String prefix) {
if (topic == null || !topic.startsWith(prefix)) {
return "";
}
// 提取设备ID部分
return topic.substring(prefix.length());
}
}

View File

@@ -1,47 +0,0 @@
package com.zg.project.wisdom.mqtt.handler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 烟雾传感器 Handler
* Topic 示例zg/wms/test/smoke/{deviceId}
*/
@Slf4j
@Component
public class SmokeSensorHandler {
/**
* MQTT主题前缀用于烟雾传感器消息
*/
public static final String TOPIC_PREFIX = "zg/wms/test/smoke/";
/**
* 处理烟雾传感器消息
*
* @param topic MQTT主题
* @param payload 消息负载内容
*/
public void handle(String topic, String payload) {
String deviceId = resolveDeviceId(topic, TOPIC_PREFIX);
log.info("[SMOKE] deviceId={}, topic={}, payload={}", deviceId, topic, payload);
// TODO: 这里后续写烟雾业务逻辑
}
/**
* 从MQTT主题中解析设备ID
*
* @param topic 完整的MQTT主题
* @param prefix 主题前缀
* @return 设备ID如果主题格式不正确则返回空字符串
*/
private String resolveDeviceId(String topic, String prefix) {
if (topic == null || !topic.startsWith(prefix)) {
return "";
}
// 提取前缀后的设备ID部分
return topic.substring(prefix.length());
}
}

View File

@@ -1,46 +0,0 @@
package com.zg.project.wisdom.mqtt.handler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 温度传感器 Handler
* Topic 示例zg/wms/test/temp/{deviceId}
*/
@Slf4j
@Component
public class TempSensorHandler {
/**
* MQTT主题前缀用于温度传感器数据
*/
public static final String TOPIC_PREFIX = "zg/wms/test/temp/";
/**
* 处理温度传感器数据
*
* @param topic MQTT主题格式为 zg/wms/test/temp/{deviceId}
* @param payload 传感器数据载荷
*/
public void handle(String topic, String payload) {
String deviceId = resolveDeviceId(topic, TOPIC_PREFIX);
log.info("[TEMP] deviceId={}, topic={}, payload={}", deviceId, topic, payload);
// TODO: 这里后续写温度业务逻辑解析JSON、落库、告警等
}
/**
* 从MQTT主题中解析设备ID
*
* @param topic MQTT主题
* @param prefix 主题前缀
* @return 设备ID如果主题格式不正确则返回空字符串
*/
private String resolveDeviceId(String topic, String prefix) {
if (topic == null || !topic.startsWith(prefix)) {
return "";
}
return topic.substring(prefix.length());
}
}

View File

@@ -1,6 +1,8 @@
package com.zg.project.wisdom.service;
import java.util.List;
import com.zg.framework.web.domain.AjaxResult;
import com.zg.project.wisdom.domain.GysJh;
import com.zg.project.wisdom.domain.dto.ExcelFieldMapping;
import org.springframework.web.multipart.MultipartFile;
@@ -51,6 +53,9 @@ public interface IGysJhService
* @param ids 需要删除的供应计划主键集合
* @return 结果
*/
public int deleteGysJhByIds(Long[] ids);
/**
@@ -74,7 +79,7 @@ public interface IGysJhService
* @param sapNo
* @return
*/
List<GysJh> getBySapNo(String sapNo);
AjaxResult getBySapNo(String sapNo);
/**
* 按字段映射导入供应计划

View File

@@ -1,6 +1,7 @@
package com.zg.project.wisdom.service;
import java.util.List;
import java.util.Map;
import com.zg.project.inventory.domain.dto.QueryDTO;
import com.zg.project.inventory.domain.vo.ChartDataVO;
@@ -38,6 +39,10 @@ public interface IRkInfoService
*/
List<RkInfo> selectGroupedByBill(RkInfoQueryDTO query);
// 接口
List<RkInfo> selectBillHeaderList(RkInfoQueryDTO query);
/**
* 修改库存单据主
*
@@ -46,6 +51,14 @@ public interface IRkInfoService
*/
public int updateRkInfo(RkInfo rkInfo);
/**
* 根据出库单据号修改库存单据
*
* @param rkInfo 库存单据
* @return 影响行数
*/
int updateRkInfoByBillNoCk(RkInfo rkInfo);
/**
* 批量删除库存单据主
*
@@ -160,9 +173,10 @@ public interface IRkInfoService
*/
List<RkInfo> selectAllRkInfo(RkInfo query);
Long selectStatistics(RkInfo query);
Long selectPcde(RkInfo query);
/**
* 出入库明细分页查询(统计页面)
*/
List<RkInfo> selectRkInfoPageList(RkInfo query);
public int updateBillInfo(RkInfo rkInfo);

View File

@@ -87,6 +87,7 @@ public class QwenOcrRemoteService {
// 远程服务返回:{ success: true, found: true/false, erpOrderNo: "0101398982" }
JsonNode erpNode = rootNode.get("erpOrderNo");
if (erpNode == null || erpNode.isNull()) {
return "";
}

View File

@@ -1,5 +1,6 @@
package com.zg.project.wisdom.service;
import com.zg.project.wisdom.domain.dto.RkInfoQueryDTO;
import com.zg.project.wisdom.domain.vo.*;
import javax.servlet.http.HttpServletResponse;
@@ -87,4 +88,11 @@ public interface RkStatisticsService {
* 规则:排除 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);
}

View File

@@ -135,8 +135,12 @@ public class AgvTaskResultServiceImpl implements IAgvTaskResultService {
* 货物上架
* @param
*/
/**
* 货物上架
*/
@Override
public String handleUpGoods(String taskNo, Integer materialStatus) {
Date now = DateUtils.getNowDate();
// 1. 查询调度任务
@@ -172,13 +176,11 @@ public class AgvTaskResultServiceImpl implements IAgvTaskResultService {
// 3. 发起调用
String wcsResp = OkHttpUtils.postJson(wcsJobCreateUrl, wcsParam.toJSONString());
if (StringUtils.isBlank(wcsResp)) {
throw new ServiceException("WCS 接口无响应");
}
JSONObject json = JSON.parseObject(wcsResp);
if (!json.containsKey("result")) {
throw new ServiceException("WCS 返回格式异常:" + wcsResp);
}
@@ -203,12 +205,19 @@ public class AgvTaskResultServiceImpl implements IAgvTaskResultService {
wcs.setUpdateTime(now);
wcsTaskResultMapper.insertWcsTaskResult(wcs);
log.info("[上架] 执行成功任务 {} 状态已更新为已完成", taskNo);
// ✅ 5. 根据 dispatchMode 判断:仅立库(WCS)模式WCS成功任务完成
if (ddTask.getDispatchMode() != null && ddTask.getDispatchMode() == 1) {
ddTaskMapper.updateTaskStatusByTaskNo(taskNo, 2); // 2=已完成
log.info("[上架] dispatchMode=1仅立库模式任务 {} 已更新为已完成(task_status=2)", taskNo);
} else {
log.info("[上架] dispatchMode!=1非仅立库模式任务 {} 不在此处置完成,后续流程继续", taskNo);
}
// ✅ 返回 taskId
return taskId;
}
/**
* 货物下架
* @param

View File

@@ -8,6 +8,7 @@ import com.zg.common.exception.ServiceException;
import com.zg.common.utils.DateUtils;
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 org.apache.poi.ss.usermodel.*;
import org.springframework.beans.factory.annotation.Autowired;
@@ -162,11 +163,40 @@ public class GysJhServiceImpl implements IGysJhService
* @return
*/
@Override
public List<GysJh> getBySapNo(String sapNo) {
return gysJhMapper.getBySapNo(sapNo);
public AjaxResult getBySapNo(String sapNo) {
List<GysJh> list = gysJhMapper.getBySapNo(sapNo);
AjaxResult result = AjaxResult.success(list);
if (list == null || list.isEmpty()) {
return result;
}
// 找出已入库(status=1)的物料号,去重、过滤空值
List<String> inStockWlNos = list.stream()
.filter(x -> x != null && "1".equals(String.valueOf(x.getStatus()).trim()))
.map(GysJh::getWlNo)
.filter(wlNo -> wlNo != null && !wlNo.trim().isEmpty())
.map(String::trim)
.distinct()
.collect(java.util.stream.Collectors.toList());
if (!inStockWlNos.isEmpty()) {
result.put("warn", true);
result.put("inStockWlNos", inStockWlNos);
// 组装提示文案(可根据前端展示需要调整长度)
String msg = "该 SAP 订单号下,以下物料号已入库过,请注意:"
+ String.join("", inStockWlNos);
result.put("msg", msg);
}
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public int importByMapping(MultipartFile file, List<ExcelFieldMapping> mapping) throws Exception {

View File

@@ -107,6 +107,11 @@ public class MoveRecordServiceImpl implements IMoveRecordService
return moveRecordMapper.deleteMoveRecordById(id);
}
/**
* 处理库存移库操作
*
* @param dto 移库请求参数包含原库存ID、移库目标列表等
*/
/**
* 处理库存移库操作
*
@@ -124,11 +129,6 @@ public class MoveRecordServiceImpl implements IMoveRecordService
throw new ServiceException("目标位置列表不能为空");
}
// 0.1 校验每个目标库位与仓库关系
for (MoveTargetItem target : dto.getTargets()) {
validatePcodeWarehouseRelation(target.getToPcode(), target.getToCangku());
}
// 1. 查询原始库存记录
RkInfo original = rkInfoMapper.selectRkInfoById(dto.getFromRkId());
if (original == null || "1".equals(original.getIsDelete())) {
@@ -154,69 +154,61 @@ public class MoveRecordServiceImpl implements IMoveRecordService
String username = dto.getMovedBy();
Date now = DateUtils.parseDate(dto.getMovedAt());
// ===== 情况一:整库移库(目标总数量 = 原库存,且仅一个目标=====
// ===== 情况一:整库移库(目标 + 数量相等=====
if (dto.getTargets().size() == 1 && totalQty.compareTo(realQty) == 0) {
MoveTargetItem target = dto.getTargets().get(0);
// 记录移库前快照
RkInfo info = new RkInfo();
BeanUtils.copyProperties(original, info);
// 移库前快照
RkInfo snapshot = new RkInfo();
BeanUtils.copyProperties(original, snapshot);
// 更新原库存到新位置(统一写入 cangku
// 更新原库存位置
original.setCangku(target.getToCangku());
original.setPcode(target.getToPcode());
original.setTrayCode(target.getToTrayCode());
// ✅ 发生移库就标记
original.setHasMoved("1");
original.setUpdateBy(username);
original.setUpdateTime(now);
rkInfoMapper.updateRkInfo(original);
// 记录移库日志(从 info → target
moveRecordMapper.insertMoveRecord(createMoveRecord(info, target, dto));
// 移库记录
moveRecordMapper.insertMoveRecord(
createMoveRecord(snapshot, target, dto)
);
return;
}
// ===== 情况二 & 三:需要新建多条库存记录 =====
// ===== 情况二 / 三:拆分移库 =====
List<RkInfo> insertList = new ArrayList<>();
// ✅ 注意:做移库的“来源记录快照”,用于日志一致性(不受后面数量更新影响
// 来源快照(用于日志 & 新库存模板
RkInfo fromSnapshot = new RkInfo();
BeanUtils.copyProperties(original, fromSnapshot);
// 情况三:部分移库(目标总量 < 原库存) → 原库存数量减少
// 情况三:部分移库(原库存减少
if (totalQty.compareTo(realQty) < 0) {
original.setRealQty(realQty.subtract(totalQty));
// ✅ 关键:发生移库就标记(你问的 has_moved 应该是 1
original.setHasMoved("1");
original.setUpdateBy(username);
original.setUpdateTime(now);
rkInfoMapper.updateRkInfo(original);
} else {
// 情况二:原库存刚好用完(但目标多个) → 删除原库存
// 情况二:原库存刚好用完(但目标多个)
rkInfoMapper.deleteRkInfoById(original.getId());
}
// 新增多条目标库存
// 新增目标库存
for (MoveTargetItem target : dto.getTargets()) {
RkInfo newInfo = new RkInfo();
// ✅ 以“移库前快照”作为模板,避免被上面修改过数量影响其它字段
// 移库前快照为模板
BeanUtils.copyProperties(fromSnapshot, newInfo, "id");
// ✅ 目标位置字段
newInfo.setCangku(target.getToCangku());
newInfo.setPcode(target.getToPcode());
newInfo.setTrayCode(target.getToTrayCode());
// ✅ 目标数量
newInfo.setRealQty(target.getRealQty());
// ✅ 发生移库就标记
newInfo.setHasMoved("1");
newInfo.setCreateBy(username);
@@ -226,8 +218,10 @@ public class MoveRecordServiceImpl implements IMoveRecordService
insertList.add(newInfo);
// 移库记录:从“移库前快照” → 每个 target更准确
moveRecordMapper.insertMoveRecord(createMoveRecord(fromSnapshot, target, dto));
// 移库记录
moveRecordMapper.insertMoveRecord(
createMoveRecord(fromSnapshot, target, dto)
);
}
if (!insertList.isEmpty()) {

View File

@@ -2,6 +2,8 @@ package com.zg.project.wisdom.service.impl;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
@@ -12,6 +14,8 @@ import com.zg.common.exception.ServiceException;
import com.zg.common.utils.DateUtils;
import com.zg.common.utils.SecurityUtils;
import com.zg.common.utils.StringUtils;
import com.zg.project.information.domain.Mtd;
import com.zg.project.information.mapper.MtdMapper;
import com.zg.project.inventory.AutoInventory.mapper.InventoryMatchScanMapper;
import com.zg.project.inventory.Task.mapper.InventoryTaskMapper;
import com.zg.project.inventory.domain.dto.QueryDTO;
@@ -30,12 +34,15 @@ import com.zg.project.wisdom.utils.BillNoUtil;
import com.zg.project.wisdom.utils.CodeConvertUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import com.zg.project.wisdom.mapper.RkInfoMapper;
import com.zg.project.wisdom.domain.RkInfo;
import com.zg.project.wisdom.service.IRkInfoService;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import static com.zg.common.utils.SecurityUtils.getUsername;
/**
@@ -69,6 +76,8 @@ public class RkInfoServiceImpl implements IRkInfoService
@Autowired
private PcdeDetailMapper pcdeDetailMapper;
@Autowired
private MtdMapper mtdMapper;
/**
* 查询库存单据主
@@ -89,43 +98,19 @@ public class RkInfoServiceImpl implements IRkInfoService
* @return 库存单据主
*/
@Override
public List<RkInfo> selectRkInfoList(RkInfo rkInfo) {
public List<RkInfo> selectRkInfoList(RkInfo rkInfo)
{
boolean needAudit = "1".equals(configService.selectConfigByKey("rk.audit.enabled"));
List<RkInfo> list = rkInfoMapper.selectRkInfoList(rkInfo);
LocalDate today = LocalDate.now();
for (RkInfo info : list) {
// 计算库龄
if (info.getRkTime() != null) {
LocalDate rkDate = info.getRkTime().toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate();
long days = ChronoUnit.DAYS.between(rkDate, today);
info.setStockAge(days);
}
// 查询现场图片 + 审核结果
AuditSignature signature = auditSignatureMapper.selectPhotoUrlByRkId(info.getId());
if (signature != null) {
info.setScenePhotoUrl(signature.getSignUrl());
info.setAuditResult(signature.getAuditResult());
info.setApproverId(signature.getApproverId());
} else {
info.setScenePhotoUrl(null);
info.setAuditResult(null);
info.setApproverId(null);
}
}
// 审核开启,过滤掉审核结果不是“通过” 且 审核人不为空(说明是审核失败)
if (needAudit) {
// 审核开启时,过滤掉“审核失败”的数据
if (needAudit)
{
list = list.stream()
.filter(info ->
// ① 未审核过approverId 为空)或
info.getApproverId() == null ||
// ② 审核通过
"1".equals(info.getAuditResult())
info.getApproverId() == null
|| "1".equals(info.getAuditResult())
)
.collect(Collectors.toList());
}
@@ -183,6 +168,13 @@ public class RkInfoServiceImpl implements IRkInfoService
return rkInfoMapper.selectGroupedByBill(query, needAudit ? 1 : 0);
}
@Override
public List<RkInfo> selectBillHeaderList(RkInfoQueryDTO query) {
// 同样传入审核开关逻辑(保留原逻辑一致性)
boolean needAudit = "1".equals(configService.selectConfigByKey("rk.audit.enabled"));
return rkInfoMapper.selectBillHeaderList(query, needAudit ? 1 : 0);
}
@Override
public RkInfo selectHeaderByBillNo(String billNo) {
if (StringUtils.isBlank(billNo)) {
@@ -213,26 +205,62 @@ public class RkInfoServiceImpl implements IRkInfoService
String pcode = rkInfo.getPcode();
// 只有当小仓 + 库位都有的时候才做这层校验
if (StringUtils.isNotBlank(warehouseCode) && StringUtils.isNotBlank(pcode)) {
// 临时组装一个 PcRkInfoBatchDTO专门给 validateWarehouseAndPcode 用
PcRkInfoBatchDTO tmpDto = new PcRkInfoBatchDTO();
tmpDto.setWarehouseCode(warehouseCode);
List<PcRkInfoItemDTO> rkList = new ArrayList<>();
PcRkInfoItemDTO item = new PcRkInfoItemDTO();
item.setPcode(pcode);
rkList.add(item);
tmpDto.setRkList(rkList);
validateWarehouseAndPcode(tmpDto);
}
// if (StringUtils.isNotBlank(warehouseCode) && StringUtils.isNotBlank(pcode)) {
//
// // 临时组装一个 PcRkInfoBatchDTO专门给 validateWarehouseAndPcode 用
// PcRkInfoBatchDTO tmpDto = new PcRkInfoBatchDTO();
// tmpDto.setWarehouseCode(warehouseCode);
//
// List<PcRkInfoItemDTO> rkList = new ArrayList<>();
// PcRkInfoItemDTO item = new PcRkInfoItemDTO();
// item.setPcode(pcode);
// rkList.add(item);
//
// tmpDto.setRkList(rkList);
//
// validateWarehouseAndPcode(tmpDto);
// }
// ====== 校验通过再更新 ======
return rkInfoMapper.updateRkInfo(rkInfo);
}
@Override
@Transactional(rollbackFor = Exception.class)
public int updateRkInfoByBillNoCk(RkInfo rkInfo) {
// ====== 基础校验 ======
if (StringUtils.isBlank(rkInfo.getBillNoCk())) {
throw new ServiceException("出库单据号 billNoCk 不能为空");
}
// 通用审计字段
rkInfo.setUpdateTime(DateUtils.getNowDate());
rkInfo.setUpdateBy(SecurityUtils.getUserId().toString());
// ====== 仓库 + 库位校验(保持与你 updateRkInfo 完全一致) ======
String warehouseCode = rkInfo.getWarehouseCode();
if (StringUtils.isBlank(warehouseCode)) {
warehouseCode = rkInfo.getCangku();
}
String pcode = rkInfo.getPcode();
if (StringUtils.isNotBlank(warehouseCode) && StringUtils.isNotBlank(pcode)) {
PcRkInfoBatchDTO tmpDto = new PcRkInfoBatchDTO();
tmpDto.setWarehouseCode(warehouseCode);
PcRkInfoItemDTO item = new PcRkInfoItemDTO();
item.setPcode(pcode);
tmpDto.setRkList(Collections.singletonList(item));
validateWarehouseAndPcode(tmpDto);
}
// ====== 按 bill_no_ck 批量更新 ======
return rkInfoMapper.updateByBillNoCk(rkInfo);
}
/**
* 批量处理撤销入库 / 撤销出库的逻辑(不是通用删除接口)
*
@@ -366,27 +394,31 @@ public class RkInfoServiceImpl implements IRkInfoService
String userId = SecurityUtils.getUserId().toString();
Date now = DateUtils.getNowDate();
// 🚩 0. 校验库位是否属于当前小仓
validateWarehouseAndPcode(dto);
// 0. 只有前端传了库位编码,才做“库位属于当前小仓”的校验(否则不传库位会被拦截)
/*暂时去掉校验*/
// boolean hasPcode = list != null && list.stream()
// .anyMatch(x -> StringUtils.isNotBlank(x.getPcode()));
// if (hasPcode) {
// validateWarehouseAndPcode(dto);
// }
// ✅ 1. 供应计划扣减映射
// ✅ 1. 供应计划扣减映射(同一个 gysJhId 多条明细时realQty 累加)
Map<Long, BigDecimal> realQtyMap = list.stream()
.filter(item -> item.getGysJhId() != null && item.getRealQty() != null)
.collect(Collectors.toMap(
PcRkInfoItemDTO::getGysJhId,
PcRkInfoItemDTO::getRealQty,
(a, b) -> b
(a, b) -> a.add(b)
));
// ✅ 2-4. 供应计划扣减&状态更新
// ✅ 2-4. 供应计划扣减&状态更新(保持你原逻辑)
if (!realQtyMap.isEmpty()) {
List<GysJh> jhList = gysJhMapper.selectByIds(new ArrayList<>(realQtyMap.keySet()));
Set<Long> idsToUpdateStatus = new HashSet<>();
for (GysJh jh : jhList) {
Long jhId = jh.getId();
BigDecimal planQty =
jh.getJhQty() == null ? BigDecimal.ZERO : jh.getJhQty();
BigDecimal planQty = jh.getJhQty() == null ? BigDecimal.ZERO : jh.getJhQty();
BigDecimal realQty = realQtyMap.get(jhId);
if (realQty == null) {
continue;
@@ -395,13 +427,11 @@ public class RkInfoServiceImpl implements IRkInfoService
boolean isEqual = realQty.compareTo(planQty) == 0;
if (!isEqual) {
// 部分入库:扣减计划数量
gysJhMapper.decreaseJhQtyById(jhId, realQty);
if (!needAudit) {
gysJhMapper.updateStatusById(jhId, "2"); // 2 = 部分入库
}
} else {
// 全部入库:状态=1
if (!needAudit) {
gysJhMapper.updateStatusById(jhId, "1"); // 1 = 全部入库
}
@@ -413,27 +443,18 @@ public class RkInfoServiceImpl implements IRkInfoService
}
}
// ✅ 5. 构建 RkInfo 入库记录(注意:小仓 = dto.getWarehouseCode()
// ✅ 5. 构建 RkInfo 入库记录(库位字段按“前端是否传 pcode”决定是否设置
for (PcRkInfoItemDTO item : list) {
RkInfo rk = new RkInfo();
// 物料、项目、供应商、xj县局、数量等等从 item 拷贝
BeanUtils.copyProperties(item, rk);
rk.setBillNo(billNo);
rk.setRkType(dto.getRkType());
rk.setWlType(dto.getWlType());
rk.setLihuoY(dto.getLihuoY());
// 🚩 所属小仓:页面选的小仓编码
rk.setCangku(dto.getWarehouseCode());
// 🚩 入库时间:页面传,若为空则使用当前时间
rk.setRkTime(dto.getRkTime() != null ? dto.getRkTime() : DateUtils.getNowDate());
// 库位 encodedId
String encodedId = pcdeDetailMapper.selectEncodedIdByPcode(item.getPcode());
rk.setPcodeId(encodedId);
rk.setCreateBy(userId);
rk.setCreateTime(now);
rk.setIsDelete("0");
@@ -442,6 +463,45 @@ public class RkInfoServiceImpl implements IRkInfoService
rk.setFycde1(item.getFycde1());
rk.setFycde2(item.getFycde2());
if (StringUtils.isBlank(rk.getDw()) && StringUtils.isNotBlank(item.getWlNo())) {
Mtd mtd = mtdMapper.selectMtdByMid(item.getWlNo().trim());
if (mtd != null && StringUtils.isNotBlank(mtd.getUnt())) {
rk.setDw(mtd.getUnt());
}
}
// 库位编码前端不传,就不设置库位相关字段(并且不查 encodedId
if (StringUtils.isNotBlank(item.getPcode())) {
// pcode 有值:允许设置/绑定库位
rk.setPcode(item.getPcode());
// 只有有 pcode 才查 pcodeId
String encodedId = pcdeDetailMapper.selectEncodedIdByPcodeAndWarehouse(
item.getPcode(),
dto.getWarehouseCode()
);
rk.setPcodeId(encodedId);
// 这些字段如果前端没传,保持 null如果传了就写入
if (StringUtils.isNotBlank(item.getTrayCode())) {
rk.setTrayCode(item.getTrayCode());
} else {
rk.setTrayCode(null);
}
if (StringUtils.isNotBlank(item.getEntityId())) {
rk.setEntityId(item.getEntityId());
} else {
rk.setEntityId(null);
}
} else {
// pcode 不传:明确不设置库位信息
rk.setPcode(null);
rk.setPcodeId(null);
rk.setTrayCode(null);
rk.setEntityId(null);
}
if (needAudit) {
rk.setStatus("0"); // 待审核
rk.setIsChuku("2"); // 待入库(审核中)
@@ -449,6 +509,7 @@ public class RkInfoServiceImpl implements IRkInfoService
rk.setStatus("1"); // 审核通过
rk.setIsChuku("0"); // 已入库
}
rkInfos.add(rk);
}
@@ -465,15 +526,18 @@ public class RkInfoServiceImpl implements IRkInfoService
AuditSignature photo = new AuditSignature();
photo.setRkId(rk.getId());
photo.setBillNo(billNo);
photo.setBillType("0"); // 入库
photo.setAuditResult("2"); // 待审核
photo.setBillType("0");
photo.setAuditResult("2");
photo.setSignerId(userId);
photo.setSignerRole("2"); // 现场照片
photo.setImageType("1"); // photo
photo.setSignerRole("2");
photo.setImageType("1");
photo.setSignUrl(item.getPhotoUrl());
photo.setSignTime(now);
photo.setPcode(item.getPcode());
photo.setTrayCode(item.getTrayCode());
// 这里照旧:有库位就写库位/托盘;没库位就是 null
photo.setPcode(rk.getPcode());
photo.setTrayCode(rk.getTrayCode());
photo.setApproverId(dto.getApproverId());
photo.setIsCurrent("1");
photo.setIsDelete("0");
@@ -493,15 +557,15 @@ public class RkInfoServiceImpl implements IRkInfoService
AuditSignature mainSign = new AuditSignature();
mainSign.setBillNo(billNo);
mainSign.setBillType("0"); // 入库
mainSign.setBillType("0");
mainSign.setSignerId(userId);
mainSign.setSignerRole("0"); // 发起人
mainSign.setSignerRole("0");
mainSign.setApproverId(dto.getApproverId());
mainSign.setSignUrl(dto.getSignatureUrl());
mainSign.setImageType("0"); // sign
mainSign.setImageType("0");
mainSign.setSignTime(now);
mainSign.setIsCurrent("1");
mainSign.setAuditResult("2"); // 待审核
mainSign.setAuditResult("2");
mainSign.setIsDelete("0");
mainSign.setCreateBy(userId);
mainSign.setCreateTime(now);
@@ -616,8 +680,8 @@ public class RkInfoServiceImpl implements IRkInfoService
@Transactional(rollbackFor = Exception.class)
public int batchInsertApp(RkInfoBatchDTO dto) {
// ✅ 0. 小仓 + 库位一致性校验(统一用 warehouseCode
validateWarehouseAndPcode(dto);
// // ✅ 0. 小仓 + 库位一致性校验(统一用 warehouseCode
// validateWarehouseAndPcode(dto);
List<RkInfo> saveList = new ArrayList<>();
String userId = SecurityUtils.getUserId().toString();
@@ -871,42 +935,133 @@ public class RkInfoServiceImpl implements IRkInfoService
boolean needAudit = "1".equals(configService.selectConfigByKey("rk.audit.enabled"));
// Step 2: 生成出库单据号
String billNo = BillNoUtil.generateTodayBillNo("RK", rkInfoMapper);
String billNo = BillNoUtil.generateTodayBillNo("CK", rkInfoMapper);
// Step 3: 批量更新 rk_info 出库字段
// 记录原记录id -> 出库记录id部分出库时照片要绑定到新插入的出库记录
Map<Long, Long> outRkIdMap = new HashMap<>();
// Step 3: 出库处理(支持部分出库)
for (StockOutItemDTO item : dto.getCkList()) {
RkInfo update = new RkInfo();
update.setId(item.getId());
update.setBillNoCk(billNo);
update.setIsDelivery(dto.getIsDelivery());
update.setCkType(dto.getCkType());
update.setTeamCode(dto.getTeamCode());
update.setLyTime(now);
update.setCkLihuoY(dto.getCkLihuoY());
update.setCkRemark(item.getCkRemark());
update.setXmNoCk(dto.getXmNoCk());
update.setXmMsCk(dto.getXmMsCk());
update.setUpdateBy(userId);
update.setUpdateTime(now);
if ("JLCK".equals(dto.getCkType())) {
update.setIsBorrowed("1");
update.setBorrowTime(dto.getBorrowTime());
update.setReturnTime(dto.getReturnTime());
if (item.getId() == null) {
throw new ServiceException("出库记录ID不能为空");
}
if (item.getRealQty() == null || item.getRealQty().compareTo(BigDecimal.ZERO) <= 0) {
throw new ServiceException("出库数量必须大于0id=" + item.getId());
}
if (needAudit) {
update.setStatus("3"); // 出库待审核
update.setIsChuku("JLCK".equals(dto.getCkType()) ? "3" : "2");
// 3.1 查询原库存记录
RkInfo db = rkInfoMapper.selectRkInfoById(item.getId());
if (db == null) {
throw new ServiceException("库存记录不存在id=" + item.getId());
}
BigDecimal oldQty = db.getRealQty() == null ? BigDecimal.ZERO : db.getRealQty();
BigDecimal outQty = item.getRealQty();
// 3.2 校验不能超出库存
BigDecimal newQty = oldQty.subtract(outQty);
if (newQty.compareTo(BigDecimal.ZERO) < 0) {
throw new ServiceException("出库数量不能大于库存数量id=" + item.getId()
+ ",库存=" + oldQty + ",出库=" + outQty);
}
boolean partialOut = newQty.compareTo(BigDecimal.ZERO) > 0;
if (partialOut) {
// ✅ 部分出库:原记录只扣减 real_qty
RkInfo reduce = new RkInfo();
reduce.setId(db.getId());
reduce.setRealQty(newQty);
reduce.setUpdateBy(userId);
reduce.setUpdateTime(now);
rkInfoMapper.updateById(reduce);
// ✅ 插入一条新的“出库记录”:复制原记录 + 覆盖本次出库字段
RkInfo outRow = new RkInfo();
BeanUtils.copyProperties(db, outRow);
outRow.setId(null);
// —— 出库这次操作字段
outRow.setBillNoCk(billNo);
outRow.setIsDelivery(dto.getIsDelivery());
outRow.setCkType(dto.getCkType());
outRow.setTeamCode(dto.getTeamCode());
outRow.setLyTime(now);
outRow.setCkLihuoY(dto.getCkLihuoY());
outRow.setCkRemark(item.getCkRemark());
outRow.setXmNoCk(dto.getXmNoCk());
outRow.setXmMsCk(dto.getXmMsCk());
// 新出库记录数量=本次出库数量
outRow.setRealQty(outQty);
// 借料字段(仅 JLCK 才写)
if ("JLCK".equals(dto.getCkType())) {
outRow.setIsBorrowed("1");
outRow.setBorrowTime(dto.getBorrowTime());
outRow.setReturnTime(dto.getReturnTime());
}
// 审核状态 + is_chuku部分出库新出库记录按正常出库逻辑走
if (needAudit) {
outRow.setStatus("3"); // 出库待审核
outRow.setIsChuku("JLCK".equals(dto.getCkType()) ? "3" : "2");
} else {
outRow.setStatus("1"); // 审核通过
outRow.setIsChuku("JLCK".equals(dto.getCkType()) ? "3" : "1");
}
// 通用字段
outRow.setIsDelete("0");
outRow.setCreateBy(userId);
outRow.setCreateTime(now);
outRow.setUpdateBy(userId);
outRow.setUpdateTime(now);
rkInfoMapper.insertRkInfo(outRow);
// 记录映射:照片绑定新出库记录
outRkIdMap.put(db.getId(), outRow.getId());
} else {
update.setStatus("1"); // 审核通过
update.setIsChuku("JLCK".equals(dto.getCkType()) ? "3" : "1");
}
// ✅ 全部出库:不扣减 real_qty直接改状态
RkInfo update = new RkInfo();
update.setId(item.getId());
update.setBillNoCk(billNo);
update.setIsDelivery(dto.getIsDelivery());
update.setCkType(dto.getCkType());
update.setTeamCode(dto.getTeamCode());
update.setLyTime(now);
update.setCkLihuoY(dto.getCkLihuoY());
update.setCkRemark(item.getCkRemark());
update.setXmNoCk(dto.getXmNoCk());
update.setXmMsCk(dto.getXmMsCk());
update.setUpdateBy(userId);
update.setUpdateTime(now);
rkInfoMapper.updateById(update);
if ("JLCK".equals(dto.getCkType())) {
update.setIsBorrowed("1");
update.setBorrowTime(dto.getBorrowTime());
update.setReturnTime(dto.getReturnTime());
}
if (needAudit) {
update.setStatus("3"); // 出库待审核
update.setIsChuku("JLCK".equals(dto.getCkType()) ? "3" : "2");
} else {
update.setStatus("1"); // 审核通过
update.setIsChuku("JLCK".equals(dto.getCkType()) ? "3" : "1");
}
rkInfoMapper.updateById(update);
// 全出库:照片绑定原记录
outRkIdMap.put(item.getId(), item.getId());
}
}
// Step 4: 如果启用审核,写入签字记录
// Step 4: 如果启用审核,写入签字记录部分出库照片绑定新出库记录ID
if (needAudit) {
List<AuditSignature> recordList = new ArrayList<>();
@@ -934,6 +1089,8 @@ public class RkInfoServiceImpl implements IRkInfoService
for (StockOutItemDTO item : dto.getCkList()) {
if (StringUtils.isBlank(item.getPhotoUrl())) continue;
Long rkIdForPhoto = outRkIdMap.getOrDefault(item.getId(), item.getId());
AuditSignature photo = new AuditSignature();
photo.setBillNo(billNo);
photo.setBillType("1");
@@ -948,9 +1105,12 @@ public class RkInfoServiceImpl implements IRkInfoService
photo.setIsDelete("0");
photo.setCreateBy(userId);
photo.setCreateTime(now);
photo.setRkId(item.getId());
// ✅ 关键:部分出库绑定“新出库记录”的 rk_id
photo.setRkId(rkIdForPhoto);
photo.setPcode(item.getPcode());
photo.setTrayCode(item.getTrayCode());
recordList.add(photo);
}
@@ -962,6 +1122,7 @@ public class RkInfoServiceImpl implements IRkInfoService
return dto.getCkList().size();
}
@Override
@Transactional(rollbackFor = Exception.class) // 开启事务,异常时回滚
public void matchWithStatus(QueryDTO dto) {
@@ -1110,8 +1271,6 @@ public class RkInfoServiceImpl implements IRkInfoService
@Override
public int refundMaterial(RefundRequestDTO dto) {
Long originalId = dto.getOriginalId();
String newPcode = dto.getPcode();
// 1. 查原出库记录
RkInfo original = rkInfoMapper.selectRkInfoById(originalId);
if (original == null || "1".equals(original.getIsDelete())) {
@@ -1123,9 +1282,11 @@ public class RkInfoServiceImpl implements IRkInfoService
BeanUtils.copyProperties(original, newEntry);
newEntry.setId(null);
newEntry.setIsChuku("0");
newEntry.setPcode(newPcode);
newEntry.setPcode(dto.getPcode());
newEntry.setCangku(dto.getWarehouseCode());
newEntry.setRkType(dto.getRkType());
newEntry.setRkTime(DateUtils.getNowDate());
newEntry.setBorrowTime(original.getBorrowTime());
newEntry.setBillNo(BillNoUtil.generateTodayBillNo("RK", rkInfoMapper));
newEntry.setCreateBy(SecurityUtils.getUserId().toString());
newEntry.setCreateTime(DateUtils.getNowDate());
@@ -1143,6 +1304,8 @@ public class RkInfoServiceImpl implements IRkInfoService
update.setReturnTime(DateUtils.getNowDate());
update.setUpdateBy(getUsername());
update.setUpdateTime(DateUtils.getNowDate());
update.setPcode(dto.getPcode());
update.setCangku(dto.getWarehouseCode());
rkInfoMapper.updateById(update);
return rows;
@@ -1204,21 +1367,16 @@ public class RkInfoServiceImpl implements IRkInfoService
return rkInfoMapper.listRkInfoByPcode(pcode);
}
@Override
public List<RkInfo> selectAllRkInfo(RkInfo query) {
return rkInfoMapper.selectAllRkInfo(query);
}
@Override
public Long selectStatistics(RkInfo query) {
return rkInfoMapper.selectStatistics(query);
public List<RkInfo> selectRkInfoPageList(RkInfo query) {
return rkInfoMapper.selectRkInfoPageList(query);
}
@Override
public Long selectPcde(RkInfo query) {
return rkInfoMapper.selectPcde(query);
}
@Override
public int updateBillInfo(RkInfo query) {
return rkInfoMapper.updateBillInfo(query);
@@ -1256,7 +1414,7 @@ public class RkInfoServiceImpl implements IRkInfoService
e.setBillNo(dto.getBillNo());
e.setRkType(dto.getRkType());
e.setWlType(dto.getWlType());
e.setWarehouseCode(dto.getWarehouseCode());
e.setCangku(dto.getWarehouseCode());
e.setLihuoY(dto.getLihuoY());
e.setRkTime(rkTime);

View File

@@ -5,10 +5,13 @@ 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.vo.*;
import com.zg.project.wisdom.mapper.GysJhMapper;
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.annotation.Resource;
@@ -23,9 +26,12 @@ import java.util.*;
@Service
public class RkStatisticsServiceImpl implements RkStatisticsService {
@Resource
@Autowired
private RkStatisticsMapper rkStatisticsMapper;
@Autowired
private RkInfoMapper rkInfoMapper;
@Override
public List<Map<String, Object>> getAgeStatsAsList() {
@@ -426,4 +432,24 @@ public class RkStatisticsServiceImpl implements RkStatisticsService {
}
return list;
}
@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;
}
}

View File

@@ -4,45 +4,63 @@ import com.zg.project.wisdom.mapper.RkInfoMapper;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.ThreadLocalRandom;
/**
* 单据号生成工具类
*
* 目标:
* 1) 每次业务操作都生成全新单据号不依赖历史不查DB
* 2) 单号短、可读
* 3) 避免串单:不同出库操作绝不复用同一个 billNo
*
* 推荐格式:
* CK24011315384227
* 前缀 + yyMMdd + HHmmss + 2位随机数
*/
public class BillNoUtil {
// 内存缓存key = prefix + yyyyMMdd
private static final Map<String, AtomicInteger> DAILY_SEQUENCE_MAP = new ConcurrentHashMap<>();
private static final String DEFAULT_PREFIX = "RK";
/**
* 生成当天单据号(格式RK20251222_1
* 生成单据号(短号,不查数据库
* 格式PREFIX + yyMMdd + HHmmss + 2位随机数
* 示例CK24011315384227
*
* @param prefix 单据前缀RK/CK/...
* @param rkInfoMapper 兼容旧签名(不再使用,可传 null
*/
public static synchronized String generateTodayBillNo(String prefix, RkInfoMapper rkInfoMapper) {
// ✅ 先得到最终前缀(不要让 lambda 捕获一个会被重新赋值的变量)
final String finalPrefix = (prefix == null || prefix.trim().isEmpty()) ? "RK" : prefix.trim();
// 前缀处理
final String p = (prefix == null || prefix.trim().isEmpty())
? DEFAULT_PREFIX
: prefix.trim().toUpperCase();
// ✅ today / key 也用 final确保 lambda 可用
final String today = new SimpleDateFormat("yyyyMMdd").format(new Date());
final String key = finalPrefix + today;
final String likePrefix = finalPrefix + today + "_";
// 时间到秒yyMMddHHmmss12位
String time = new SimpleDateFormat("yyMMddHHmmss").format(new Date());
// 如果当天还没初始化,从数据库取最大号
AtomicInteger counter = DAILY_SEQUENCE_MAP.computeIfAbsent(key, k -> {
// 2位随机数10~99避免同一秒内多次点击撞号
int rnd = ThreadLocalRandom.current().nextInt(10, 100);
String maxBillNo = rkInfoMapper.selectMaxBillNo(likePrefix);
return p + time + rnd;
}
int start = 1;
if (maxBillNo != null) {
try {
String seqStr = maxBillNo.substring(maxBillNo.lastIndexOf("_") + 1);
start = Integer.parseInt(seqStr) + 1;
} catch (Exception ignored) {
}
}
return new AtomicInteger(start);
});
/**
* 需要更短的版本(可选)
* 格式PREFIX + yyMMdd + 4位随机数
* 示例CK2401134837
*
* 注意:比带秒的版本更短,但理论碰撞概率略高(一般够用)
*/
public static synchronized String generateShortBillNo(String prefix) {
final String p = (prefix == null || prefix.trim().isEmpty())
? DEFAULT_PREFIX
: prefix.trim().toUpperCase();
int seq = counter.getAndIncrement();
return finalPrefix + today + "_" + seq;
String day = new SimpleDateFormat("yyMMdd").format(new Date());
int rnd4 = ThreadLocalRandom.current().nextInt(1000, 10000);
return p + day + rnd4;
}
}

View File

@@ -6,10 +6,10 @@ spring:
druid:
# 主库数据源
master:
# url: jdbc:mysql://101.132.133.142:3306/wisdom?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://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/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
# url: jdbc:mysql://192.168.1.251:3306/wisdom?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# url: jdbc:mysql://localhost:3306/wisdom?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: shzg

View File

@@ -170,51 +170,3 @@ mock:
#配送系统中调用大模型进行图片识别
qwen-ocr:
base-url: http://192.168.1.253:8087/ocr/extractErpByBase64
# =========================
# MQTT 配置EMQX 接入)
# =========================
mqtt:
# 是否启用 MQTT 功能
# true系统启动时自动连接 EMQX 并订阅 Topic
# false不初始化 MQTT 客户端
enabled: true
# MQTT Broker 地址
# tcp://IP:1883 —— MQTT TCP
# ws://IP:8083 —— MQTT WebSocket
# ssl://IP:8883 —— MQTT SSL
broker: tcp://192.168.1.29:1883
# MQTT 客户端 ID在 EMQX 中唯一)
# 建议:系统名 + 模块名,避免重复
clientId: zg-wms-backend
# MQTT 账号EMQX Dashboard 中配置)
username: demo02
# MQTT 密码
password: admin123
# 是否清除会话
# true :断开后清除订阅和未接收消息(推荐后端使用)
# false :保留会话(适合设备端)
cleanSession: true
# 心跳时间(秒)
# 客户端与 Broker 保持连接的心跳间隔
keepAlive: 30
# 连接超时时间(秒)
timeout: 10
# 默认消息 QoS
# 0最多一次不保证到达性能最好
# 1至少一次推荐业务数据常用
# 2仅一次最严格性能最差
qos: 1
# 订阅的 Topic 列表(支持通配符)
# 建议先使用测试 Topic确认链路正常后再接入真实业务 Topic
topics:
- zg/wms/test/#

View File

@@ -31,12 +31,28 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="isDelete != null and isDelete != ''"> and is_delete = #{isDelete}</if>
</where>
</select>
<select id="selectConstructionTeamById" parameterType="Long" resultMap="ConstructionTeamResult">
<include refid="selectConstructionTeamVo"/>
where id = #{id}
</select>
<select id="selectByTeamCode" resultMap="ConstructionTeamResult">
SELECT *
FROM construction_team
WHERE team_code = #{teamCode}
AND is_delete = '0'
LIMIT 1
</select>
<select id="selectByTeamName" resultMap="ConstructionTeamResult">
SELECT *
FROM construction_team
WHERE team_name = #{teamName}
AND is_delete = '0'
LIMIT 1
</select>
<insert id="insertConstructionTeam" parameterType="ConstructionTeam" useGeneratedKeys="true" keyProperty="id">
insert into construction_team
<trim prefix="(" suffix=")" suffixOverrides=",">
@@ -59,6 +75,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</trim>
</insert>
<select id="selectMaxTeamCode" resultType="java.lang.String">
SELECT MAX(team_code)
FROM construction_team
WHERE team_code LIKE 'CT%'
</select>
<update id="updateConstructionTeam" parameterType="ConstructionTeam">
update construction_team
<trim prefix="SET" suffixOverrides=",">

View File

@@ -182,6 +182,15 @@
LIMIT 1
</select>
<select id="selectEncodedIdByPcodeAndWarehouse" resultType="java.lang.String">
SELECT encoded_id
FROM pcde_detail
WHERE pcode = #{pcode}
AND warehouse_code = #{warehouseCode}
AND is_delete = '0'
LIMIT 1
</select>
<!-- 单条插入 -->
<insert id="insertPcdeDetail"
parameterType="PcdeDetail"

View File

@@ -59,11 +59,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="status != null and status != ''">
and status = #{status}
</if>
and (status is null or trim(status) != '1')
<!-- and (status is null or trim(status) != '1') -->
<if test="isDelete != null and isDelete != ''">
and is_delete = #{isDelete}
</if>
<!-- 按创建时间筛选create_time 在 [beginTime, endTime] -->
<if test="beginTime != null">
and create_time <![CDATA[>=]]> #{beginTime}
</if>
<if test="endTime != null">
and create_time <![CDATA[<]]> #{endTime}
</if>
</where>
</select>
@@ -75,7 +81,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="getBySapNo" parameterType="java.lang.String" resultMap="GysJhResult">
<include refid="selectGysJhVo"/>
WHERE sap_no = #{sapNo}
AND status != '1'
</select>
<!-- 【已注释】唯一性校验查询基于SAP订单号、项目号、物料号判断是否已存在 -->
@@ -234,5 +239,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</foreach>
</delete>
<select id="selectByWlNo" resultMap="GysJhResult">
SELECT *
FROM gys_jh
WHERE wl_no = #{wlNo}
AND is_delete = '0'
</select>
<update id="updateDwById">
UPDATE gys_jh
SET dw = #{dw}
WHERE id = #{id}
</update>
</mapper>

File diff suppressed because it is too large Load Diff