移库记录去掉库位和仓库的对应关系校验

施工队新增时去掉编号,由代码生成
新增出入库记录查询接口
This commit is contained in:
2026-01-20 10:20:39 +08:00
parent f8600107ec
commit a82797c475
12 changed files with 320 additions and 51 deletions

View File

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

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

@@ -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

@@ -67,11 +67,14 @@ public class RkInfoController extends BaseController
return getDataTable(list);
}
/**
* 库存查询
* @param dto
* @return
*/
@PostMapping("/pageStatistics")
public Map<String, Object> pageStatistics(@RequestBody RkInfoQueryDTO dto) {
// 分页(如果这里不需要总数,后面你可以改成 startPage(..., false)
PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
List<RkInfo> list = rkInfoService.selectAllRkInfo(dto);
@@ -81,6 +84,16 @@ public class RkInfoController extends BaseController
return dataInfo;
}
/**
* 出入库查询
*/
@PostMapping("/pageList")
public TableDataInfo pageList(@RequestBody RkInfoQueryDTO dto) {
PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
List<RkInfo> list = rkInfoService.selectRkInfoPageList(dto);
return getDataTable(list);
}
/**
* 单据头信息:根据入库单号查询公共字段
*/

View File

@@ -198,6 +198,9 @@ public class RkInfo extends BaseEntity {
@Excel(name = "总金额")
private BigDecimal totalAmount;
@Excel(name = "备注")
private String remark;
/** 库位码(编码) */
@Excel(name = "库位码")
private String pcode;
@@ -238,7 +241,7 @@ public class RkInfo extends BaseEntity {
@Excel(name = "出库备注")
private String ckRemark;
// @Excel(name = "审核状态")
// @Excel(name = "审核状态")
private String status;
/** 是否移库过0否 1是 */
@@ -457,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; }

View File

@@ -237,6 +237,10 @@ public interface RkInfoMapper
*/
List<RkInfo> selectAllRkInfo(RkInfo query);
/**
* 出入库明细分页查询(统计页面)
*/
List<RkInfo> selectRkInfoPageList(RkInfo query);
int updateBillInfo(RkInfo query);

View File

@@ -173,6 +173,11 @@ public interface IRkInfoService
*/
List<RkInfo> selectAllRkInfo(RkInfo query);
/**
* 出入库明细分页查询(统计页面)
*/
List<RkInfo> selectRkInfoPageList(RkInfo query);
public int updateBillInfo(RkInfo rkInfo);
/**

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

@@ -1372,6 +1372,11 @@ public class RkInfoServiceImpl implements IRkInfoService
return rkInfoMapper.selectAllRkInfo(query);
}
@Override
public List<RkInfo> selectRkInfoPageList(RkInfo query) {
return rkInfoMapper.selectRkInfoPageList(query);
}
@Override
public int updateBillInfo(RkInfo query) {
return rkInfoMapper.updateBillInfo(query);

View File

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

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

@@ -1338,6 +1338,127 @@
</select>
<select id="selectRkInfoPageList"
parameterType="com.zg.project.wisdom.domain.RkInfo"
resultMap="RkInfoResult">
<include refid="selectRkInfoVo"/>
<where>
(ri.is_delete = '0' OR ri.is_delete = 0 OR ri.is_delete IS NULL)
<if test="isChukuList != null and isChukuList.size() > 0">
AND ri.is_chuku IN
<foreach collection="isChukuList"
item="item"
open="("
separator=","
close=")">
#{item}
</foreach>
</if>
<if test="(isChukuList == null or isChukuList.size() == 0)
and isChuku != null">
AND ri.is_chuku = #{isChuku}
</if>
<if test="warehouseCode != null and warehouseCode != ''">
AND ri.cangku = #{warehouseCode}
</if>
<if test="parentWarehouseCode != null and parentWarehouseCode != ''">
AND wh.parent_warehouse_code = #{parentWarehouseCode}
</if>
<if test="rkType != null and rkType != ''">
AND ri.rk_type = #{rkType}
</if>
<if test="wlType != null and wlType != ''">
AND ri.wl_type = #{wlType}
</if>
<!-- 时间范围查询:入库时间 OR 出库时间 -->
<if test="statDate != null and endDate != null">
AND (
(
ri.rk_time <![CDATA[ >= ]]> #{statDate}
AND ri.rk_time <![CDATA[ < ]]> DATE_ADD(#{endDate}, INTERVAL 1 SECOND)
)
OR
(
ri.ly_time <![CDATA[ >= ]]> #{statDate}
AND ri.ly_time <![CDATA[ < ]]> DATE_ADD(#{endDate}, INTERVAL 1 SECOND)
)
)
</if>
<!-- &lt;!&ndash; 入库时间 &ndash;&gt;-->
<!-- <if test="startTime != null">-->
<!-- AND ri.rk_time <![CDATA[ >= ]]> #{startTime}-->
<!-- </if>-->
<!-- <if test="endTime != null">-->
<!-- AND ri.rk_time <![CDATA[ < ]]> DATE_ADD(#{endTime}, INTERVAL 1 SECOND)-->
<!-- </if>-->
<!-- &lt;!&ndash; 出库时间 &ndash;&gt;-->
<!-- <if test="lyStartTime != null">-->
<!-- AND ri.ly_time <![CDATA[ >= ]]> #{lyStartTime}-->
<!-- </if>-->
<!-- <if test="lyEndTime != null">-->
<!-- AND ri.ly_time <![CDATA[ < ]]> DATE_ADD(#{lyEndTime}, INTERVAL 1 SECOND)-->
<!-- </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="gysNo != null and gysNo != ''">
AND ri.gys_no LIKE CONCAT('%', #{gysNo}, '%')
</if>
<if test="gysMc != null and gysMc != ''">
AND ri.gys_mc LIKE CONCAT('%', #{gysMc}, '%')
</if>
<if test="sapNo != null and sapNo != ''">
AND ri.sap_no LIKE CONCAT('%', #{sapNo}, '%')
</if>
<if test="billNo != null and billNo != ''">
AND ri.bill_no LIKE CONCAT('%', #{billNo}, '%')
</if>
<if test="billNoCk != null and billNoCk != ''">
AND ri.bill_no_ck LIKE CONCAT('%', #{billNoCk}, '%')
</if>
<if test="ckType != null and ckType != ''">
AND ri.ck_type LIKE CONCAT('%', #{ckType}, '%')
</if>
<if test="pcode != null and pcode != ''">
AND ri.pcode LIKE CONCAT('%', #{pcode}, '%')
</if>
<if test="fycde1 != null and fycde1 != ''">
AND ri.fycde_1 LIKE CONCAT('%', #{fycde1}, '%')
</if>
<if test="fycde2 != null and fycde2 != ''">
AND ri.fycde_2 LIKE CONCAT('%', #{fycde2}, '%')
</if>
</where>
<!-- ✅ 核心排序规则 -->
ORDER BY
COALESCE(ri.ly_time, ri.rk_time) DESC,
ri.id DESC
</select>
<select id="selectDeliveryCkList"
parameterType="com.zg.project.wisdom.domain.RkInfo"