功能重构开发

This commit is contained in:
2026-01-23 17:15:20 +08:00
parent 9b2b142347
commit 268e5f21fa
19 changed files with 577 additions and 301 deletions

View File

@@ -3,6 +3,7 @@ package com.zg.project.wisdom.controller;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import com.zg.project.wisdom.domain.dto.BorrowReturnDTO;
import com.zg.project.wisdom.domain.dto.RkBillCreateDTO;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
@@ -125,10 +126,10 @@ public class RkBillController extends BaseController
* 还料入库
*/
@PreAuthorize("@ss.hasPermi('wisdom:bill:return:add')")
@Log(title = "还料入库", businessType = BusinessType.INSERT)
@PostMapping("/return/add")
public AjaxResult returnIn(@RequestBody RkBillCreateDTO dto) {
return toAjax(rkBillService.insertReturnBillAndDetail(dto));
@Log(title = "还料", businessType = BusinessType.UPDATE)
@PostMapping("/borrow/return")
public AjaxResult returnBorrow(@RequestBody BorrowReturnDTO dto) {
return toAjax(rkBillService.returnBorrow(dto));
}
}

View File

@@ -2,6 +2,8 @@ package com.zg.project.wisdom.controller;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
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;
@@ -101,4 +103,13 @@ public class RkInfoController extends BaseController
{
return toAjax(rkInfoService.deleteRkInfoByIds(ids));
}
/**
* 库存统计(随查询条件动态变化)
*/
@PostMapping("/statistic")
public AjaxResult statistic(@RequestBody RkInfo query) {
return AjaxResult.success(rkInfoService.getStockStatistic(query));
}
}

View File

@@ -162,4 +162,12 @@ public class RkRecordController extends BaseController
return toAjax(rkRecordService.deletePreOutRecords(ids));
}
/**
* 出入库统计(同时返回)
*/
@PostMapping("/statistic")
public AjaxResult statistic(@RequestBody RkRecord query) {
return AjaxResult.success(rkRecordService.getRecordStatistic(query));
}
}

View File

@@ -43,6 +43,10 @@ public class RkRecord extends BaseEntity
@Excel(name = "施工队")
private String teamCode;
/** 施工队名称(联表) */
@Excel(name = "施工队名称")
private String teamName;
/** 执行状态0预入/预出1已完成 */
@Excel(name = "执行状态", readConverterExp = "0=预操作,1=已完成")
private String execStatus;
@@ -222,7 +226,7 @@ public class RkRecord extends BaseEntity
private Long sid;
/** 是否需要配送(0否,1是,2配送中,3配送完成) */
// @Excel(name = "是否需要配送(0否,1是,2配送中,3配送完成)")
@Excel(name = "是否需要配送(0否,1是,2配送中,3配送完成)")
private String isDelivery;
/** 封样编号1 */
@@ -645,6 +649,14 @@ public class RkRecord extends BaseEntity
return teamCode;
}
public String getTeamName() {
return teamName;
}
public void setTeamName(String teamName) {
this.teamName = teamName;
}
public void setBorrowTime(Date borrowTime)
{
this.borrowTime = borrowTime;
@@ -850,6 +862,7 @@ public class RkRecord extends BaseEntity
.append("trayCode", getTrayCode())
.append("entityId", getEntityId())
.append("teamCode", getTeamCode())
.append("teamName", teamName)
.append("borrowTime", getBorrowTime())
.append("returnTime", getReturnTime())
.append("hasMoved", getHasMoved())

View File

@@ -0,0 +1,31 @@
package com.zg.project.wisdom.domain.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
@Data
public class BorrowReturnDTO {
/** 原借料出库单据号 */
private String borrowBillNo;
/** 还料单据号(可不传,后端生成) */
private String returnBillNo;
/** 操作人 */
private Long operator;
/** 还料时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date returnTime;
/** 备注 */
private String remark;
/** 明细列表 */
private List<BorrowReturnItemDTO> items;
}

View File

@@ -0,0 +1,40 @@
package com.zg.project.wisdom.domain.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
@Data
public class BorrowReturnItemDTO {
/** 🔑 原借料出库记录 rk_record.id必须 */
private Long recordId;
/** 原 rk_info.id */
private Long rkInfoId;
/** 还料数量 */
private BigDecimal returnQty;
/** 库位 */
private String pcode;
/** 托盘 */
private String trayCode;
/** 实物ID */
private String entityId;
/** 借料时间(前端如果传) */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date borrowTime;
/** 还料时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date returnTime;
/** 备注 */
private String remark;
}

View File

@@ -1,39 +0,0 @@
package com.zg.project.wisdom.domain.vo;// package com.zg.project.wisdom.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
@Data
public class BillGroupVO {
/** 分组主键:统一用入库单号 bill_no若为空会用 bill_no_ck 兜底) */
private String billNo;
/** 出库单号(出库单据时有值,前端可展示/操作) */
private String billNoCk;
/** 单据类型IN / OUT */
private String billType;
/** 单据时间IN 取最早 rk_timeOUT 取最早 ly_time */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date billTime;
// ——主数据常用抬头字段(从首条提取即可)——
private String xj;
private String xmNo;
private String xmMs;
private String rkType; private String rkTypeName;
private String ckType; private String ckTypeName;
private String teamCode; private String teamName;
private String cangkuName;
private String lihuoYName;
private String remark;
// ——聚合指标——
private Integer itemCount; // 明细条数
private BigDecimal sumQty; // 数量合计sum real_qty
private BigDecimal sumAmount; // 金额合计sum ht_dj * real_qty
}

View File

@@ -0,0 +1,13 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
@Data
public class RecordStatisticVO {
/** 入库统计 */
private StockStatisticVO inStatistic;
/** 出库统计 */
private StockStatisticVO outStatistic;
}

View File

@@ -0,0 +1,18 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class StockStatisticVO {
/** 库存总金额 */
private BigDecimal totalAmount;
/** 使用库位数量 */
private Integer locationCount;
/** 货物总数量 */
private BigDecimal totalQuantity;
}

View File

@@ -4,6 +4,7 @@ import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import com.zg.project.wisdom.domain.RkInfo;
import com.zg.project.wisdom.domain.vo.StockStatisticVO;
import io.lettuce.core.dynamic.annotation.Param;
/**
@@ -102,4 +103,9 @@ public interface RkInfoMapper
@Param("returnTime") Date returnTime);
List<RkInfo> selectRkInfoByIds(@Param("rkInfoIds") List<Long> rkInfoIds);
/**
* 库存统计(金额 / 库位数 / 数量)
*/
StockStatisticVO selectStockStatisticByCondition(RkInfo query);
}

View File

@@ -5,6 +5,7 @@ import java.util.List;
import com.zg.project.wisdom.domain.RkInfo;
import com.zg.project.wisdom.domain.RkRecord;
import com.zg.project.wisdom.domain.vo.StockStatisticVO;
import io.lettuce.core.dynamic.annotation.Param;
/**
@@ -121,4 +122,28 @@ public interface RkRecordMapper
* @return 记录
*/
RkRecord selectRkRecordByRkInfoId(@Param("rkInfoId") Long rkInfoId);
/**
* 入库统计
*/
StockStatisticVO selectInRecordStatistic(RkRecord query);
/**
* 出库统计
*/
StockStatisticVO selectOutRecordStatistic(RkRecord query);
/**
* 借出入库
*/
int updateBorrowReturnRecordById(@Param("id") Long id,
@Param("isBorrowed") String isBorrowed,
@Param("returnTime") Date returnTime,
@Param("updateBy") String updateBy,
@Param("updateTime") Date updateTime);
/**
* 查询某个单据下仍为预入库状态的记录数量
*/
int countPreInRecordByBillNo(@Param("billNo") String billNo);
}

View File

@@ -2,6 +2,7 @@ package com.zg.project.wisdom.service;
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;
/**
@@ -75,5 +76,5 @@ public interface IRkBillService
/**
* 还料入库
*/
int insertReturnBillAndDetail(RkBillCreateDTO dto);
int returnBorrow(BorrowReturnDTO dto);
}

View File

@@ -2,6 +2,7 @@ package com.zg.project.wisdom.service;
import java.util.List;
import com.zg.project.wisdom.domain.RkInfo;
import com.zg.project.wisdom.domain.vo.StockStatisticVO;
/**
* 库存单据明细Service接口
@@ -58,4 +59,12 @@ public interface IRkInfoService
* @return 结果
*/
public int deleteRkInfoById(Long id);
/**
* 查询库存统计信息
*/
StockStatisticVO getStockStatistic(RkInfo query);
}

View File

@@ -2,6 +2,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;
/**
* 出入库记录Service接口
@@ -90,4 +91,7 @@ public interface IRkRecordService
* 删除预出库记录(仅限 exec_status = 0
*/
int deletePreOutRecords(Long[] ids);
RecordStatisticVO getRecordStatistic(RkRecord query);
}

View File

@@ -4,6 +4,7 @@ import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import com.zg.common.exception.ServiceException;
import com.zg.common.utils.DateUtils;
@@ -14,6 +15,8 @@ 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.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.mapper.GysJhMapper;
import com.zg.project.wisdom.mapper.RkInfoMapper;
@@ -101,7 +104,12 @@ public class RkBillServiceImpl implements IRkBillService
Date now = DateUtils.getNowDate();
String userId = String.valueOf(SecurityUtils.getUserId());
/* ================== 1. 主单 rk_bill ================== */
List<RkInfo> rkInfoList = dto.getRkInfoList();
/* ================== 0⃣ 入库前:供应计划【批量】校验 ================== */
checkGysJhQtyBeforeInStockBatch(rkInfoList);
/* ================== 1⃣ 主单 rk_bill ================== */
RkBill bill = new RkBill();
if (dto.getRkBill() != null) {
BeanUtils.copyProperties(dto.getRkBill(), bill);
@@ -109,41 +117,33 @@ public class RkBillServiceImpl implements IRkBillService
bill.setBillNo(billNo);
bill.setBizType("0"); // 入库
bill.setExecStatus(bill.getExecStatus());
bill.setOperationTime(dto.getRkBill().getOperationTime());
bill.setCreateTime(now);
bill.setCreateBy(userId);
bill.setIsDelete("0");
// operator默认当前登录人
if (bill.getOperator() == null) {
bill.setOperator(Integer.valueOf(userId));
}
// execStatus默认已完成预入库 / 直接入库统一处理)
if (StringUtils.isBlank(bill.getExecStatus())) {
bill.setExecStatus("1");
}
rkBillMapper.insertRkBill(bill);
/* ================== 2. 明细 + 事件 + 供应计划 ================== */
for (RkInfo info : dto.getRkInfoList()) {
/* ================== 2️⃣ 明细 & 事件 ================== */
for (RkInfo info : rkInfoList) {
/* ====== ★ 0. 入库前:供应计划数量校验(核心新增) ====== */
checkGysJhQtyBeforeInStock(info);
info.setExecStatus(bill.getExecStatus());
/* ====== ① 根据 pcode 查询 pcde_detail获取 encoded_id ====== */
// 库位编码
if (StringUtils.isNotBlank(info.getPcode())) {
PcdeDetail pcde = pcdeDetailMapper.selectByPcode(info.getPcode());
if (pcde == null) {
throw new RuntimeException("库位不存在:" + info.getPcode());
}
info.setPcodeId(pcde.getEncodedId());
}
/* ---------- rk_info ---------- */
// rk_info
info.setBillNo(billNo);
info.setBizType(bill.getBizType());
info.setOperationType(bill.getOperationType());
@@ -155,104 +155,75 @@ public class RkBillServiceImpl implements IRkBillService
info.setIsChuku("0");
info.setHasMoved("0");
info.setIsBorrowed("0");
info.setExecStatus(bill.getExecStatus());
info.setIsDelete("0");
info.setCreateTime(now);
info.setCreateBy(userId);
info.setIsDelete("0");
rkInfoMapper.insertRkInfo(info);
/* ---------- rk_record ---------- */
// rk_record
RkRecord record = buildInRkRecord(bill, info, now);
record.setExecStatus(bill.getExecStatus());
record.setPcodeId(info.getPcodeId());
rkRecordMapper.insertRkRecord(record);
/* ---------- 供应计划 ---------- */
handleGysJhAfterInStock(info);
}
/* ================== 3⃣ 入库后:供应计划【批量】更新 ================== */
handleGysJhAfterInStockBatch(rkInfoList);
return 1;
}
/**
* 入库前校验:入库数量不能超过供应计划数量
* 入库前校验按供应计划ID聚合
* 规则:已入库数量 + 本次单据入库合计 ≤ 计划数量
*/
private void checkGysJhQtyBeforeInStock(RkInfo info) {
private void checkGysJhQtyBeforeInStockBatch(List<RkInfo> rkInfoList) {
if (info.getGysJhId() == null || info.getRealQty() == null) {
return;
}
Map<Long, BigDecimal> qtyMap = rkInfoList.stream()
.filter(i -> i.getGysJhId() != null && i.getRealQty() != null)
.collect(Collectors.groupingBy(
RkInfo::getGysJhId,
Collectors.mapping(
RkInfo::getRealQty,
Collectors.reducing(BigDecimal.ZERO, BigDecimal::add)
)
));
GysJh gysJh = gysJhMapper.selectGysJhById(info.getGysJhId());
if (gysJh == null) {
throw new RuntimeException("供应计划不存在ID" + info.getGysJhId());
}
for (Map.Entry<Long, BigDecimal> entry : qtyMap.entrySet()) {
BigDecimal planQty = gysJh.getJhQty();
if (planQty == null) {
return;
}
Long gysJhId = entry.getKey();
BigDecimal currentInQty = entry.getValue();
BigDecimal alreadyInQty = gysJh.getRealQty();
if (alreadyInQty == null) {
alreadyInQty = BigDecimal.ZERO;
}
GysJh gysJh = gysJhMapper.selectGysJhById(gysJhId);
if (gysJh == null) {
throw new RuntimeException("供应计划不存在ID" + gysJhId);
}
BigDecimal afterQty = alreadyInQty.add(info.getRealQty());
BigDecimal planQty = gysJh.getJhQty();
if (planQty == null) {
continue;
}
if (afterQty.compareTo(planQty) > 0) {
throw new RuntimeException(
"入库数量超出供应计划数量,物料号:" + info.getWlNo()
+ ",计划数量:" + planQty
+ ",已入库:" + alreadyInQty
+ ",本次入库:" + info.getRealQty()
);
BigDecimal alreadyInQty = gysJh.getRealQty();
if (alreadyInQty == null) {
alreadyInQty = BigDecimal.ZERO;
}
BigDecimal afterQty = alreadyInQty.add(currentInQty);
if (afterQty.compareTo(planQty) > 0) {
throw new RuntimeException(
"入库数量超出供应计划数量,物料号:" + gysJh.getWlNo()
+ ",计划数量:" + planQty
+ ",已入库:" + alreadyInQty
+ ",本次入库:" + currentInQty
);
}
}
}
private void handleGysJhAfterInStock(RkInfo info) {
// 1. 未关联供应计划或无实际入库数量,直接跳过
if (info.getGysJhId() == null || info.getRealQty() == null) {
return;
}
// 2. 累加 实际入库数量real_qty = real_qty + 本次入库数量)
gysJhMapper.increaseRealQtyById(
info.getRealQty(),
info.getGysJhId()
);
// 3. 查询最新供应计划数据
GysJh gysJh = gysJhMapper.selectGysJhById(info.getGysJhId());
if (gysJh == null) {
throw new RuntimeException("供应计划不存在ID" + info.getGysJhId());
}
BigDecimal planQty = gysJh.getJhQty(); // 计划交货数量
BigDecimal realQty = gysJh.getRealQty(); // 实际入库数量
if (realQty == null) {
realQty = BigDecimal.ZERO;
}
// 4. 状态判定
String status;
if (realQty.compareTo(BigDecimal.ZERO) == 0) {
status = "0"; // 未到货
} else if (planQty != null && realQty.compareTo(planQty) >= 0) {
status = "1"; // 已入库
} else {
status = "2"; // 部分入库
}
// 5. 更新供应计划状态
gysJhMapper.updateStatusById(gysJh.getId(), status);
}
private RkRecord buildInRkRecord(RkBill bill, RkInfo info, Date now) {
RkRecord record = new RkRecord();
@@ -286,6 +257,55 @@ public class RkBillServiceImpl implements IRkBillService
return record;
}
/**
* 入库完成后按供应计划ID汇总本次入库数量统一更新 real_qty 与 status
*/
private void handleGysJhAfterInStockBatch(List<RkInfo> rkInfoList) {
// 1⃣ 按 gysJhId 分组并汇总 realQty
Map<Long, BigDecimal> qtyMap = rkInfoList.stream()
.filter(i -> i.getGysJhId() != null && i.getRealQty() != null)
.collect(Collectors.groupingBy(
RkInfo::getGysJhId,
Collectors.mapping(
RkInfo::getRealQty,
Collectors.reducing(BigDecimal.ZERO, BigDecimal::add)
)
));
// 2⃣ 逐个供应计划更新
for (Map.Entry<Long, BigDecimal> entry : qtyMap.entrySet()) {
Long gysJhId = entry.getKey();
BigDecimal totalInQty = entry.getValue();
// 累加入库数量
gysJhMapper.increaseRealQtyById(totalInQty, gysJhId);
// 查询最新状态
GysJh gysJh = gysJhMapper.selectGysJhById(gysJhId);
if (gysJh == null) {
throw new RuntimeException("供应计划不存在ID" + gysJhId);
}
BigDecimal planQty = gysJh.getJhQty();
BigDecimal realQty = gysJh.getRealQty();
if (realQty == null) {
realQty = BigDecimal.ZERO;
}
String status;
if (realQty.compareTo(BigDecimal.ZERO) == 0) {
status = "0"; // 未到货
} else if (planQty != null && realQty.compareTo(planQty) >= 0) {
status = "1"; // 已入库
} else {
status = "2"; // 部分入库
}
gysJhMapper.updateStatusById(gysJhId, status);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
@@ -293,95 +313,97 @@ public class RkBillServiceImpl implements IRkBillService
if (dto == null || dto.getRkBill() == null
|| StringUtils.isBlank(dto.getRkBill().getBillNo())) {
throw new RuntimeException("单据号不能为空");
throw new ServiceException("单据号不能为空");
}
if (dto.getRkInfoList() == null || dto.getRkInfoList().isEmpty()) {
throw new RuntimeException("入库明细不能为空");
throw new ServiceException("入库明细不能为空");
}
String billNo = dto.getRkBill().getBillNo();
Date now = DateUtils.getNowDate();
// ================== 1. 查主单 ==================
/* ================== 1️⃣主单 ================== */
RkBill bill = rkBillMapper.selectByBillNo(billNo);
if (bill == null) {
throw new RuntimeException("单据不存在:" + billNo);
throw new ServiceException("单据不存在:" + billNo);
}
// ================== 2. execStatus 规则 ==================
/* ================== 2️⃣ 执行状态规则 ================== */
String execStatus = dto.getRkBill().getExecStatus();
if (StringUtils.isBlank(execStatus)) {
execStatus = bill.getExecStatus();
}
// ================== 3. 追加明细 ==================
for (RkInfo info : dto.getRkInfoList()) {
// 🚩 标记:本次是否包含预入库
boolean hasPreIn = false;
// ===== ① 根据 pcode 查询 pcde_detail → encoded_id关键新增 =====
List<RkInfo> rkInfoList = dto.getRkInfoList();
/* ================== 3⃣ 追加前:供应计划【批量】校验 ================== */
checkGysJhQtyBeforeInStockBatch(rkInfoList);
/* ================== 4⃣ 插入明细 & 事件 ================== */
for (RkInfo info : rkInfoList) {
/* ===== 4.1 库位校验 ===== */
if (StringUtils.isNotBlank(info.getPcode())) {
PcdeDetail pcde = pcdeDetailMapper.selectByPcode(info.getPcode());
if (pcde == null) {
throw new RuntimeException("库位不存在:" + info.getPcode());
throw new ServiceException("库位不存在:" + info.getPcode());
}
info.setPcodeId(pcde.getEncodedId());
}
// ---------- 基础关联 ----------
/* ===== 4.2 继承主单字段 ===== */
info.setBillNo(bill.getBillNo());
// ---------- 类型信息(来源于主单,不信前端) ----------
info.setOperationType(bill.getOperationType());
info.setBizType(bill.getBizType());
info.setWlType(bill.getWlType());
// ---------- 仓库 ----------
info.setCangku(bill.getCangku());
// ---------- 操作信息 ----------
info.setOperationTime(info.getOperationTime());
info.setOperator(info.getOperator());
// ---------- 执行状态 ----------
info.setOperationTime(now);
info.setOperator(bill.getOperator());
info.setExecStatus(execStatus);
// ---------- 库存状态 ----------
if ("0".equals(execStatus)) {
hasPreIn = true;
}
info.setIsChuku("0");
info.setHasMoved("0");
info.setIsBorrowed("0");
// ---------- 备注 ----------
String finalRemark = info.getRemark();
if (StringUtils.isBlank(finalRemark)) {
finalRemark = bill.getRemark();
}
/* ===== 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");
// ---------- 插入 rk_info ----------
/* ===== 4.5 插入 rk_info ===== */
rkInfoMapper.insertRkInfo(info);
// ---------- rk_record ----------
/* ===== 4.6 插入 rk_record ===== */
RkRecord record = buildInRkRecord(bill, info, now);
record.setExecStatus(execStatus);
record.setRkInfoId(info.getId());
// ⭐ encoded_id 同步
record.setPcodeId(info.getPcodeId());
// ⭐ 备注同步
record.setRemark(finalRemark);
rkRecordMapper.insertRkRecord(record);
}
// ---------- 供应计划 ----------
handleGysJhAfterInStock(info);
/* ================== 5⃣ 追加后:供应计划【批量】更新 ================== */
handleGysJhAfterInStockBatch(rkInfoList);
/* ================== 6⃣ 同步回退主单状态 ================== */
if (hasPreIn && !"0".equals(bill.getExecStatus())) {
rkBillMapper.updateExecStatusByBillNo(billNo, "0");
}
return 1;
@@ -575,8 +597,8 @@ public class RkBillServiceImpl implements IRkBillService
record.setOperationType(bill.getOperationType());
record.setOperationTime(bill.getOperationTime());
record.setOperator(bill.getOperator());
record.setCangku(dbInfo.getCangku());
record.setTeamCode(dbInfo.getTeamCode());
record.setCangku(bill.getCangku());
record.setTeamCode(bill.getTeamCode());
// ===== ★ 关键:备注来自【出库明细 outInfo】=====
record.setRemark(outInfo.getRemark());
@@ -611,136 +633,64 @@ public class RkBillServiceImpl implements IRkBillService
@Override
@Transactional(rollbackFor = Exception.class)
public int insertReturnBillAndDetail(RkBillCreateDTO dto) {
public int returnBorrow(BorrowReturnDTO dto) {
if (dto == null || dto.getRkInfoList() == null || dto.getRkInfoList().isEmpty()) {
if (dto == null || dto.getItems() == null || dto.getItems().isEmpty()) {
throw new RuntimeException("还料明细不能为空");
}
String billNo = BillNoUtil.generateTodayBillNo("HL", null);
Date now = DateUtils.getNowDate();
String userId = String.valueOf(SecurityUtils.getUserId());
/* ================== 1. 主单 rk_bill ================== */
RkBill bill = new RkBill();
if (dto.getRkBill() != null) {
BeanUtils.copyProperties(dto.getRkBill(), bill);
}
for (BorrowReturnItemDTO item : dto.getItems()) {
bill.setBillNo(billNo);
bill.setBizType("3"); // 3 = 还料入库
bill.setOperationTime(now);
bill.setCreateTime(now);
bill.setCreateBy(userId);
bill.setIsDelete("0");
if (bill.getOperator() == null) {
bill.setOperator(Integer.valueOf(userId));
}
// 还料入库默认已完成
if (StringUtils.isBlank(bill.getExecStatus())) {
bill.setExecStatus("1");
}
rkBillMapper.insertRkBill(bill);
/* ================== 2. 明细 + 事件 ================== */
for (RkInfo info : dto.getRkInfoList()) {
// ===== ① 基础校验 =====
if (info.getRealQty() == null || info.getRealQty().compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("还料数量必须大于 0");
/* ================== 1. 参数校验 ================== */
if (item.getRecordId() == null) {
throw new RuntimeException("借料记录ID(recordId)不能为空");
}
if (item.getReturnTime() == null) {
throw new RuntimeException("归还时间不能为空");
}
// 必须关联原借料出库库存
if (info.getRdid() == null) {
throw new RuntimeException("还料必须指定原借料库存ID(rdid)");
/* ================== 2. 查询借料 record主驱动 ================== */
RkRecord record = rkRecordMapper.selectRkRecordById(item.getRecordId());
if (record == null) {
throw new RuntimeException("借料记录不存在recordId=" + item.getRecordId());
}
RkInfo borrowInfo = rkInfoMapper.selectRkInfoById(info.getRdid());
if (borrowInfo == null) {
throw new RuntimeException("原借料库存不存在ID" + info.getRdid());
// 必须是借料出库记录
if (!"2".equals(record.getBizType())) {
throw new RuntimeException("该记录不是借料出库记录,禁止还料");
}
/* ---------- rk_info新增库存 ---------- */
info.setId(null);
info.setBillNo(billNo);
info.setBizType("3"); // 还料入库
info.setOperationType(bill.getOperationType());
info.setOperationTime(bill.getOperationTime());
info.setOperator(bill.getOperator());
// 防止重复还料
if ("2".equals(record.getIsBorrowed())) {
throw new RuntimeException("该借料记录已归还,禁止重复操作");
}
// 关键:库存是“新增”,不是回补原库存
info.setIsChuku("0");
info.setIsBorrowed("0");
info.setHasMoved("0");
info.setExecStatus(bill.getExecStatus());
Long rkInfoId = record.getRkInfoId();
if (rkInfoId == null) {
throw new RuntimeException("借料记录未关联库存(rkInfoId)");
}
// 仓库 / 物料 / 项目信息,默认继承原借料库存
info.setCangku(borrowInfo.getCangku());
info.setWlType(borrowInfo.getWlType());
info.setXmNo(borrowInfo.getXmNo());
info.setXmMs(borrowInfo.getXmMs());
info.setWlNo(borrowInfo.getWlNo());
info.setWlMs(borrowInfo.getWlMs());
info.setGysNo(borrowInfo.getGysNo());
info.setGysMc(borrowInfo.getGysMc());
info.setDw(borrowInfo.getDw());
info.setCreateTime(now);
info.setCreateBy(userId);
info.setIsDelete("0");
rkInfoMapper.insertRkInfo(info);
/* ---------- rk_record还料入库事件 ---------- */
RkRecord record = buildReturnRkRecord(bill, info, now);
rkRecordMapper.insertRkRecord(record);
/* ---------- 原借料记录标记“已归还” ---------- */
/* ================== 3. 更新 rk_info库存闭环 ================== */
rkInfoMapper.updateBorrowReturn(
borrowInfo.getId(),
"2", // is_borrowed = 2 已归还
rkInfoId,
"2", // is_borrowed = 已归还
item.getReturnTime()
);
/* ================== 4. 更新 rk_record状态闭环按主键 ================== */
rkRecordMapper.updateBorrowReturnRecordById(
record.getId(),
"2", // is_borrowed = 已归还
item.getReturnTime(),
userId,
now
);
}
return 1;
}
private RkRecord buildReturnRkRecord(RkBill bill, RkInfo info, Date now) {
RkRecord record = new RkRecord();
// ① 明细快照
BeanUtils.copyProperties(info, record);
record.setId(null);
// ② 主单上下文
record.setBillNo(bill.getBillNo());
record.setBizType("3"); // 还料入库
record.setOperationType(bill.getOperationType());
record.setOperationTime(bill.getOperationTime());
record.setOperator(bill.getOperator());
record.setCangku(info.getCangku());
record.setTeamCode(info.getTeamCode());
// ③ 状态
record.setIsChuku("0");
record.setIsBorrowed("2"); // 已归还
record.setHasMoved("0");
record.setExecStatus(bill.getExecStatus());
record.setIsDelete("0");
// ④ 审计
record.setCreateTime(now);
record.setCreateBy(bill.getCreateBy());
// ⑤ 关联
record.setRkInfoId(info.getId());
record.setRdid(info.getRdid()); // 指向原借料库存
return record;
return dto.getItems().size();
}
}

View File

@@ -2,6 +2,7 @@ package com.zg.project.wisdom.service.impl;
import java.util.List;
import com.zg.common.utils.DateUtils;
import com.zg.project.wisdom.domain.vo.StockStatisticVO;
import com.zg.project.wisdom.mapper.RkRecordMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -105,4 +106,10 @@ public class RkInfoServiceImpl implements IRkInfoService
{
return rkInfoMapper.deleteRkInfoById(id);
}
@Override
public StockStatisticVO getStockStatistic(RkInfo query) {
return rkInfoMapper.selectStockStatisticByCondition(query);
}
}

View File

@@ -9,6 +9,7 @@ import com.zg.common.utils.DateUtils;
import com.zg.common.utils.StringUtils;
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.mapper.GysJhMapper;
import com.zg.project.wisdom.mapper.RkBillMapper;
import com.zg.project.wisdom.mapper.RkInfoMapper;
@@ -206,6 +207,8 @@ public class RkRecordServiceImpl implements IRkRecordService
info.setGysMc(rkRecord.getGysMc());
info.setDw(rkRecord.getDw());
info.setHtDj(rkRecord.getHtDj());
info.setCangku(rkRecord.getCangku());
info.setPcode(rkRecord.getPcode());
info.setIsChuku("0");
}
@@ -240,6 +243,8 @@ public class RkRecordServiceImpl implements IRkRecordService
info.setGysMc(rkRecord.getGysMc());
info.setDw(rkRecord.getDw());
info.setHtDj(rkRecord.getHtDj());
info.setCangku(rkRecord.getCangku());
info.setPcode(rkRecord.getPcode());
}
/* ====================== 4. 更新库存表 ====================== */
@@ -431,14 +436,12 @@ public class RkRecordServiceImpl implements IRkRecordService
}
/* ================== 1. 查询 rk_record ================== */
List<RkRecord> recordList =
rkRecordMapper.selectRkRecordByIds(recordIds);
List<RkRecord> recordList = rkRecordMapper.selectRkRecordByIds(recordIds);
if (recordList == null || recordList.isEmpty()) {
throw new ServiceException("入库记录不存在");
}
/* ================== 2. 处理「预入库」记录 ================== */
/* ================== 2. 处理「预入库」记录 ================== */
List<RkRecord> preRecordList = recordList.stream()
.filter(r -> "0".equals(r.getExecStatus())) // 预入库
.collect(Collectors.toList());
@@ -447,16 +450,13 @@ public class RkRecordServiceImpl implements IRkRecordService
throw new ServiceException("所选入库记录均已完成,无需重复入库");
}
/* ================== 3. 校验:一键入库数量 ≤ 计划交货数量 ================== */
// 按供应计划ID聚合「本次完成入库数量」
/* ================== 3. 校验:完成入库数量 ≤ 计划交货数量 ================== */
Map<Long, BigDecimal> finishQtyMap = new HashMap<>();
for (RkRecord record : preRecordList) {
if (record.getGysJhId() == null || record.getRealQty() == null) {
continue;
}
finishQtyMap.merge(
record.getGysJhId(),
record.getRealQty(),
@@ -464,7 +464,6 @@ public class RkRecordServiceImpl implements IRkRecordService
);
}
// 只和「计划交货数量」做校验
for (Map.Entry<Long, BigDecimal> entry : finishQtyMap.entrySet()) {
Long gysJhId = entry.getKey();
@@ -494,7 +493,7 @@ public class RkRecordServiceImpl implements IRkRecordService
rkRecordMapper.updateExecStatusByIds(preRecordIds, "1");
// ② rk_info.exec_status = 1
// ② rk_info.exec_status = 1(只推进本次涉及的库存)
List<Long> rkInfoIds = preRecordList.stream()
.map(RkRecord::getRkInfoId)
.filter(Objects::nonNull)
@@ -505,27 +504,33 @@ public class RkRecordServiceImpl implements IRkRecordService
rkInfoMapper.updateExecStatusByIds(rkInfoIds, "1");
}
// ③ rk_bill.exec_status = 1
List<String> billNos = preRecordList.stream()
// ③ rk_bill.exec_status = 1(✅ 只有当 bill 下所有 record 均完成)
Set<String> billNoSet = preRecordList.stream()
.map(RkRecord::getBillNo)
.filter(StringUtils::isNotBlank)
.distinct()
.collect(Collectors.toList());
.collect(Collectors.toSet());
for (String billNo : billNos) {
rkBillMapper.updateExecStatusByBillNo(billNo, "1");
for (String billNo : billNoSet) {
// 查询该 bill 下是否仍存在 预入库 record
int unFinishedCount = rkRecordMapper.countPreInRecordByBillNo(billNo);
// 只有当不存在任何预入库 record 时,才推进 bill 状态
if (unFinishedCount == 0) {
rkBillMapper.updateExecStatusByBillNo(billNo, "1");
}
}
/* ================== 5. 同步修正供应计划(以完成结果为准) ================== */
/* ================== 5. 同步修正供应计划 ================== */
for (Map.Entry<Long, BigDecimal> entry : finishQtyMap.entrySet()) {
Long gysJhId = entry.getKey();
BigDecimal finishQty = entry.getValue();
// ① 直接修正 real_qty以一键入库结果为准
// ① 修正实收数量
gysJhMapper.updateRealQtyById(gysJhId, finishQty);
// ② 重新判定状态
// ② 修正状态
GysJh gysJh = gysJhMapper.selectGysJhById(gysJhId);
if (gysJh == null) {
continue;
@@ -546,6 +551,7 @@ public class RkRecordServiceImpl implements IRkRecordService
return preRecordIds.size();
}
@Override
@Transactional(rollbackFor = Exception.class)
public int rollbackOut(List<Long> recordIds) {
@@ -752,6 +758,12 @@ 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;
}
}

View File

@@ -189,6 +189,65 @@
</foreach>
</select>
<select id="selectStockStatisticByCondition"
parameterType="com.zg.project.wisdom.domain.RkInfo"
resultType="com.zg.project.wisdom.domain.vo.StockStatisticVO">
SELECT
IFNULL(SUM(ri.real_qty * ri.ht_dj), 0) AS total_amount,
COUNT(DISTINCT ri.pcode) AS location_count,
IFNULL(SUM(ri.real_qty), 0) AS total_quantity
FROM rk_info ri
<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="startDate != null">
AND ri.operation_time &gt;= #{startDate}
</if>
<if test="endDate != null">
AND ri.operation_time &lt;= #{endDate}
</if>
</where>
</select>
<!-- ========================= 删除 ========================= -->
<delete id="deleteRkInfoById" parameterType="Long">
DELETE FROM rk_info WHERE id = #{id}
@@ -327,6 +386,7 @@
return_time = #{returnTime},
update_time = NOW()
WHERE id = #{id}
AND is_delete = '0'
</update>

View File

@@ -61,6 +61,7 @@
<result property="trayCode" column="tray_code"/>
<result property="entityId" column="entity_id"/>
<result property="teamCode" column="team_code"/>
<result property="teamName" column="team_name"/>
<result property="borrowTime" column="borrow_time"/>
<result property="returnTime" column="return_time"/>
@@ -101,7 +102,8 @@
/* 仓库信息:小仓/大仓 */
wh.warehouse_name,
wh.parent_warehouse_code,
wh.parent_warehouse_name
wh.parent_warehouse_name,
ct.team_name AS team_name
FROM rk_record rr
LEFT JOIN sys_user su
ON rr.operator = su.user_id
@@ -113,6 +115,9 @@
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'
</sql>
<!-- ===================== 查询列表 ===================== -->
@@ -193,7 +198,10 @@
<if test="wlMs != null and wlMs != ''">
AND rr.wl_ms LIKE concat('%', #{wlMs}, '%')
</if>
<!-- 是否借料 -->
<if test="isBorrowed != null and isBorrowed != ''">
AND rr.is_borrowed = #{isBorrowed}
</if>
<!-- 默认不查删除 -->
<if test="isDelete == null">
AND (rr.is_delete = '0' OR rr.is_delete = 0 OR rr.is_delete IS NULL)
@@ -528,4 +536,102 @@
</foreach>
</delete>
<!-- 入库统计 -->
<select id="selectInRecordStatistic"
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 = 0 <!-- 入库 -->
<if test="operationType != null and operationType != ''">
AND rr.operation_type LIKE CONCAT('%', #{operationType}, '%')
</if>
<if test="xmNo != null and xmNo != ''">
AND rr.xm_no LIKE CONCAT('%', #{xmNo}, '%')
</if>
<if test="wlNo != null and wlNo != ''">
AND rr.wl_no LIKE CONCAT('%', #{wlNo}, '%')
</if>
<if test="pcode != null and pcode != ''">
AND rr.pcode LIKE CONCAT('%', #{pcode}, '%')
</if>
<if test="cangku != null and cangku != ''">
AND rr.cangku LIKE CONCAT('%', #{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>
<if test="xmNo != null and xmNo != ''">
AND rr.xm_no LIKE CONCAT('%', #{xmNo}, '%')
</if>
<if test="wlNo != null and wlNo != ''">
AND rr.wl_no LIKE CONCAT('%', #{wlNo}, '%')
</if>
<if test="pcode != null and pcode != ''">
AND rr.pcode LIKE CONCAT('%', #{pcode}, '%')
</if>
<if test="cangku != null and cangku != ''">
AND rr.cangku LIKE CONCAT('%', #{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="countPreInRecordByBillNo" resultType="int">
SELECT COUNT(1)
FROM rk_record
WHERE bill_no = #{billNo}
AND exec_status = '0'
AND is_delete = '0'
</select>
<update id="updateBorrowReturnRecordById">
UPDATE rk_record
SET
is_borrowed = #{isBorrowed},
return_time = #{returnTime},
update_by = #{updateBy},
update_time = #{updateTime}
WHERE id = #{id}
AND is_delete = '0'
</update>
</mapper>