diff --git a/src/main/java/com/zg/project/wisdom/controller/RkBillController.java b/src/main/java/com/zg/project/wisdom/controller/RkBillController.java index e42361b..a04f310 100644 --- a/src/main/java/com/zg/project/wisdom/controller/RkBillController.java +++ b/src/main/java/com/zg/project/wisdom/controller/RkBillController.java @@ -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)); } } diff --git a/src/main/java/com/zg/project/wisdom/controller/RkInfoController.java b/src/main/java/com/zg/project/wisdom/controller/RkInfoController.java index bb1d6e7..4ae554a 100644 --- a/src/main/java/com/zg/project/wisdom/controller/RkInfoController.java +++ b/src/main/java/com/zg/project/wisdom/controller/RkInfoController.java @@ -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)); + } + } diff --git a/src/main/java/com/zg/project/wisdom/controller/RkRecordController.java b/src/main/java/com/zg/project/wisdom/controller/RkRecordController.java index fda5837..75025b8 100644 --- a/src/main/java/com/zg/project/wisdom/controller/RkRecordController.java +++ b/src/main/java/com/zg/project/wisdom/controller/RkRecordController.java @@ -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)); + } + } diff --git a/src/main/java/com/zg/project/wisdom/domain/RkRecord.java b/src/main/java/com/zg/project/wisdom/domain/RkRecord.java index a0b54b9..717de35 100644 --- a/src/main/java/com/zg/project/wisdom/domain/RkRecord.java +++ b/src/main/java/com/zg/project/wisdom/domain/RkRecord.java @@ -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()) diff --git a/src/main/java/com/zg/project/wisdom/domain/dto/BorrowReturnDTO.java b/src/main/java/com/zg/project/wisdom/domain/dto/BorrowReturnDTO.java new file mode 100644 index 0000000..0442bbe --- /dev/null +++ b/src/main/java/com/zg/project/wisdom/domain/dto/BorrowReturnDTO.java @@ -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 items; +} diff --git a/src/main/java/com/zg/project/wisdom/domain/dto/BorrowReturnItemDTO.java b/src/main/java/com/zg/project/wisdom/domain/dto/BorrowReturnItemDTO.java new file mode 100644 index 0000000..4e6e8ed --- /dev/null +++ b/src/main/java/com/zg/project/wisdom/domain/dto/BorrowReturnItemDTO.java @@ -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; +} diff --git a/src/main/java/com/zg/project/wisdom/domain/vo/BillGroupVO.java b/src/main/java/com/zg/project/wisdom/domain/vo/BillGroupVO.java deleted file mode 100644 index e1807d3..0000000 --- a/src/main/java/com/zg/project/wisdom/domain/vo/BillGroupVO.java +++ /dev/null @@ -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_time;OUT 取最早 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) -} diff --git a/src/main/java/com/zg/project/wisdom/domain/vo/RecordStatisticVO.java b/src/main/java/com/zg/project/wisdom/domain/vo/RecordStatisticVO.java new file mode 100644 index 0000000..6124af4 --- /dev/null +++ b/src/main/java/com/zg/project/wisdom/domain/vo/RecordStatisticVO.java @@ -0,0 +1,13 @@ +package com.zg.project.wisdom.domain.vo; + +import lombok.Data; + +@Data +public class RecordStatisticVO { + + /** 入库统计 */ + private StockStatisticVO inStatistic; + + /** 出库统计 */ + private StockStatisticVO outStatistic; +} \ No newline at end of file diff --git a/src/main/java/com/zg/project/wisdom/domain/vo/StockStatisticVO.java b/src/main/java/com/zg/project/wisdom/domain/vo/StockStatisticVO.java new file mode 100644 index 0000000..1400be4 --- /dev/null +++ b/src/main/java/com/zg/project/wisdom/domain/vo/StockStatisticVO.java @@ -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; +} diff --git a/src/main/java/com/zg/project/wisdom/mapper/RkInfoMapper.java b/src/main/java/com/zg/project/wisdom/mapper/RkInfoMapper.java index 3144536..d0f4d59 100644 --- a/src/main/java/com/zg/project/wisdom/mapper/RkInfoMapper.java +++ b/src/main/java/com/zg/project/wisdom/mapper/RkInfoMapper.java @@ -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 selectRkInfoByIds(@Param("rkInfoIds") List rkInfoIds); + /** + * 库存统计(金额 / 库位数 / 数量) + */ + StockStatisticVO selectStockStatisticByCondition(RkInfo query); + } diff --git a/src/main/java/com/zg/project/wisdom/mapper/RkRecordMapper.java b/src/main/java/com/zg/project/wisdom/mapper/RkRecordMapper.java index 7d371d9..bb0b37d 100644 --- a/src/main/java/com/zg/project/wisdom/mapper/RkRecordMapper.java +++ b/src/main/java/com/zg/project/wisdom/mapper/RkRecordMapper.java @@ -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); } diff --git a/src/main/java/com/zg/project/wisdom/service/IRkBillService.java b/src/main/java/com/zg/project/wisdom/service/IRkBillService.java index 44c920f..5f810f6 100644 --- a/src/main/java/com/zg/project/wisdom/service/IRkBillService.java +++ b/src/main/java/com/zg/project/wisdom/service/IRkBillService.java @@ -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); } diff --git a/src/main/java/com/zg/project/wisdom/service/IRkInfoService.java b/src/main/java/com/zg/project/wisdom/service/IRkInfoService.java index 4d3c41b..589cfe3 100644 --- a/src/main/java/com/zg/project/wisdom/service/IRkInfoService.java +++ b/src/main/java/com/zg/project/wisdom/service/IRkInfoService.java @@ -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); + + } diff --git a/src/main/java/com/zg/project/wisdom/service/IRkRecordService.java b/src/main/java/com/zg/project/wisdom/service/IRkRecordService.java index 2decfbc..be15ebb 100644 --- a/src/main/java/com/zg/project/wisdom/service/IRkRecordService.java +++ b/src/main/java/com/zg/project/wisdom/service/IRkRecordService.java @@ -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); } diff --git a/src/main/java/com/zg/project/wisdom/service/impl/RkBillServiceImpl.java b/src/main/java/com/zg/project/wisdom/service/impl/RkBillServiceImpl.java index a95c8aa..2acc835 100644 --- a/src/main/java/com/zg/project/wisdom/service/impl/RkBillServiceImpl.java +++ b/src/main/java/com/zg/project/wisdom/service/impl/RkBillServiceImpl.java @@ -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 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 rkInfoList) { - if (info.getGysJhId() == null || info.getRealQty() == null) { - return; - } + Map 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 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 rkInfoList) { + + // 1️⃣ 按 gysJhId 分组并汇总 realQty + Map 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 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 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(); } } diff --git a/src/main/java/com/zg/project/wisdom/service/impl/RkInfoServiceImpl.java b/src/main/java/com/zg/project/wisdom/service/impl/RkInfoServiceImpl.java index b88f47a..13dc81a 100644 --- a/src/main/java/com/zg/project/wisdom/service/impl/RkInfoServiceImpl.java +++ b/src/main/java/com/zg/project/wisdom/service/impl/RkInfoServiceImpl.java @@ -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); + } } diff --git a/src/main/java/com/zg/project/wisdom/service/impl/RkRecordServiceImpl.java b/src/main/java/com/zg/project/wisdom/service/impl/RkRecordServiceImpl.java index 8d74015..ed9a882 100644 --- a/src/main/java/com/zg/project/wisdom/service/impl/RkRecordServiceImpl.java +++ b/src/main/java/com/zg/project/wisdom/service/impl/RkRecordServiceImpl.java @@ -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 recordList = - rkRecordMapper.selectRkRecordByIds(recordIds); - + List recordList = rkRecordMapper.selectRkRecordByIds(recordIds); if (recordList == null || recordList.isEmpty()) { throw new ServiceException("入库记录不存在"); } - /* ================== 2. 只处理「预入库」记录 ================== */ + /* ================== 2. 仅处理「预入库」记录 ================== */ List 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 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 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 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 billNos = preRecordList.stream() + // ③ rk_bill.exec_status = 1(✅ 只有当 bill 下所有 record 均完成) + Set 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 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 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; + } } diff --git a/src/main/resources/mybatis/wisdom/RkInfoMapper.xml b/src/main/resources/mybatis/wisdom/RkInfoMapper.xml index f8f7c83..7b18370 100644 --- a/src/main/resources/mybatis/wisdom/RkInfoMapper.xml +++ b/src/main/resources/mybatis/wisdom/RkInfoMapper.xml @@ -189,6 +189,65 @@ + + DELETE FROM rk_info WHERE id = #{id} @@ -327,6 +386,7 @@ return_time = #{returnTime}, update_time = NOW() WHERE id = #{id} + AND is_delete = '0' diff --git a/src/main/resources/mybatis/wisdom/RkRecordMapper.xml b/src/main/resources/mybatis/wisdom/RkRecordMapper.xml index affb879..e3a9cb2 100644 --- a/src/main/resources/mybatis/wisdom/RkRecordMapper.xml +++ b/src/main/resources/mybatis/wisdom/RkRecordMapper.xml @@ -61,6 +61,7 @@ + @@ -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' @@ -193,7 +198,10 @@ AND rr.wl_ms LIKE concat('%', #{wlMs}, '%') - + + + AND rr.is_borrowed = #{isBorrowed} + AND (rr.is_delete = '0' OR rr.is_delete = 0 OR rr.is_delete IS NULL) @@ -528,4 +536,102 @@ + + + + + + + + + + + UPDATE rk_record + SET + is_borrowed = #{isBorrowed}, + return_time = #{returnTime}, + update_by = #{updateBy}, + update_time = #{updateTime} + WHERE id = #{id} + AND is_delete = '0' + +