diff --git a/src/main/java/com/zg/project/wisdom/controller/MoveRecordController.java b/src/main/java/com/zg/project/wisdom/controller/MoveRecordController.java index 4aed02b..7c3d378 100644 --- a/src/main/java/com/zg/project/wisdom/controller/MoveRecordController.java +++ b/src/main/java/com/zg/project/wisdom/controller/MoveRecordController.java @@ -42,6 +42,17 @@ public class MoveRecordController extends BaseController return AjaxResult.success("移库成功"); } + /** + * 撤销移库 + */ + @PreAuthorize("@ss.hasPermi('wisdom:stock:move:rollback')") + @Log(title = "撤销移库", businessType = BusinessType.UPDATE) + @PostMapping("/rollback/{id}") + public AjaxResult rollbackMove(@PathVariable("id") Long moveRecordId) { + moveRecordService.rollbackMove(moveRecordId); + return AjaxResult.success("撤销移库成功"); + } + /** * 查询移库记录列表 */ 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 a549f9d..e42361b 100644 --- a/src/main/java/com/zg/project/wisdom/controller/RkBillController.java +++ b/src/main/java/com/zg/project/wisdom/controller/RkBillController.java @@ -8,7 +8,6 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -90,7 +89,6 @@ public class RkBillController extends BaseController return toAjax(rkBillService.appendRkBillDetail(dto)); } - /** * 修改库存单据 */ @@ -107,9 +105,30 @@ public class RkBillController extends BaseController */ @PreAuthorize("@ss.hasPermi('wisdom:bill:remove')") @Log(title = "库存单据", businessType = BusinessType.DELETE) - @DeleteMapping("/{ids}") - public AjaxResult remove(@PathVariable Long[] ids) + @DeleteMapping("/delete") + public AjaxResult remove(@RequestBody Long[] ids) { return toAjax(rkBillService.deleteRkBillByIds(ids)); } + + /** + * 新增出库单据 + */ + @PreAuthorize("@ss.hasPermi('wisdom:bill:out:add')") + @Log(title = "出库单据", businessType = BusinessType.INSERT) + @PostMapping("/out/add") + public AjaxResult addOut(@RequestBody RkBillCreateDTO dto) { + return toAjax(rkBillService.insertOutBillAndDetail(dto)); + } + + /** + * 还料入库 + */ + @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)); + } + } 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 8098f7b..fda5837 100644 --- a/src/main/java/com/zg/project/wisdom/controller/RkRecordController.java +++ b/src/main/java/com/zg/project/wisdom/controller/RkRecordController.java @@ -102,12 +102,12 @@ public class RkRecordController extends BaseController } /** - * 删除出入库记录 + * 删除入库记录 */ @PreAuthorize("@ss.hasPermi('wisdom:record:remove')") - @Log(title = "出入库记录", businessType = BusinessType.DELETE) - @DeleteMapping("/{ids}") - public AjaxResult remove(@PathVariable Long[] ids) + @Log(title = "入库记录", businessType = BusinessType.DELETE) + @DeleteMapping("/delete") + public AjaxResult remove(@RequestBody Long[] ids) { return toAjax(rkRecordService.deleteRkRecordByIds(ids)); } @@ -132,4 +132,34 @@ public class RkRecordController extends BaseController return toAjax(rkRecordService.finishIn(record.getIds())); } + /** + * 撤销出库 + */ + @PreAuthorize("@ss.hasPermi('wisdom:bill:out:rollback')") + @Log(title = "撤销出库", businessType = BusinessType.UPDATE) + @PostMapping("/out/rollback") + public AjaxResult rollbackOut(@RequestBody RkRecord record) { + return toAjax(rkRecordService.rollbackOut(record.getIds())); + } + + /** + * 一键出库(批量完成出库) + */ + @PreAuthorize("@ss.hasPermi('wisdom:record:out:finish')") + @Log(title = "一键出库", businessType = BusinessType.UPDATE) + @PostMapping("/out/finish") + public AjaxResult finishOut(@RequestBody RkRecord record) { + return toAjax(rkRecordService.finishOut(record.getIds())); + } + + /** + * 删除预出库记录 + */ + @PreAuthorize("@ss.hasPermi('wisdom:record:out:remove')") + @Log(title = "预出库记录删除", businessType = BusinessType.DELETE) + @DeleteMapping("/out/delete") + public AjaxResult deletePreOut(@RequestBody Long[] ids) { + return toAjax(rkRecordService.deletePreOutRecords(ids)); + } + } diff --git a/src/main/java/com/zg/project/wisdom/domain/GysJh.java b/src/main/java/com/zg/project/wisdom/domain/GysJh.java index b8ff98d..a14b55a 100644 --- a/src/main/java/com/zg/project/wisdom/domain/GysJh.java +++ b/src/main/java/com/zg/project/wisdom/domain/GysJh.java @@ -11,7 +11,12 @@ import com.zg.framework.web.domain.BaseEntity; /** * 供应计划对象 gys_jh - * + * + * 说明: + * - jhQty :计划交货数量 + * - realQty :累计已入库数量(落库) + * - waitQty :待入库数量 = jhQty - realQty(仅用于反显,不落库) + * * @author zg * @date 2025-05-28 */ @@ -78,12 +83,28 @@ public class GysJh extends BaseEntity @Excel(name = "计划交货数量") private BigDecimal jhQty; + /** 已入库数量(累计) */ + @Excel(name = "已入库数量") + private BigDecimal realQty; + + /** + * 待入库数量(反显字段,不落库) + * + * 计算规则: + * waitQty = jhQty - realQty + */ + private BigDecimal waitQty; + /** 计量单位 */ @Excel(name = "计量单位") private String dw; - /** 0:未到货,1:已入库,2部分入库 */ -// @Excel(name = "0:未到货,1:已入库,2部分入库") + /** + * 状态 + * 0:未到货 + * 1:部分入库 + * 2:已入库 + */ private String status; /** 身份码 */ @@ -95,7 +116,6 @@ public class GysJh extends BaseEntity private String remark; /** 是否删除(0正常 1删除) */ -// @Excel(name = "是否删除", readConverterExp = "0=正常,1=删除") private String isDelete; /** 查询开始时间(yyyy-MM-dd HH:mm:ss) */ @@ -106,236 +126,120 @@ public class GysJh extends BaseEntity @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date endTime; - public void setId(Long id) - { - this.id = id; - } + // ======================= getter / setter ======================= - public Long getId() - { - return id; - } + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } - public void setIndexNo(Long indexNo) - { - this.indexNo = indexNo; - } + public String getXh() { return xh; } + public void setXh(String xh) { this.xh = xh; } - public Long getIndexNo() - { - return indexNo; - } + public Long getIndexNo() { return indexNo; } + public void setIndexNo(Long indexNo) { this.indexNo = indexNo; } - public void setXj(String xj) - { - this.xj = xj; - } + public String getXj() { return xj; } + public void setXj(String xj) { this.xj = xj; } - public String getXj() - { - return xj; - } + public String getXmNo() { return xmNo; } + public void setXmNo(String xmNo) { this.xmNo = xmNo; } - public void setXmNo(String xmNo) - { - this.xmNo = xmNo; - } + public String getXmMs() { return xmMs; } + public void setXmMs(String xmMs) { this.xmMs = xmMs; } - public String getXmNo() - { - return xmNo; - } + public String getWlNo() { return wlNo; } + public void setWlNo(String wlNo) { this.wlNo = wlNo; } - public void setXmMs(String xmMs) - { - this.xmMs = xmMs; - } + public String getWlMs() { return wlMs; } + public void setWlMs(String wlMs) { this.wlMs = wlMs; } - public String getXmMs() - { - return xmMs; - } + public String getGysNo() { return gysNo; } + public void setGysNo(String gysNo) { this.gysNo = gysNo; } - public void setWlNo(String wlNo) - { - this.wlNo = wlNo; - } + public String getGysMc() { return gysMc; } + public void setGysMc(String gysMc) { this.gysMc = gysMc; } - public String getWlNo() - { - return wlNo; - } + public BigDecimal getJhAmt() { return jhAmt; } + public void setJhAmt(BigDecimal jhAmt) { this.jhAmt = jhAmt; } - public void setWlMs(String wlMs) - { - this.wlMs = wlMs; - } + public BigDecimal getHtDj() { return htDj; } + public void setHtDj(BigDecimal htDj) { this.htDj = htDj; } - public String getWlMs() - { - return wlMs; - } - - public void setGysNo(String gysNo) - { - this.gysNo = gysNo; - } - - public String getGysNo() - { - return gysNo; - } - - public void setGysMc(String gysMc) - { - this.gysMc = gysMc; - } - - public String getGysMc() - { - return gysMc; - } - - public void setJhAmt(BigDecimal jhAmt) - { - this.jhAmt = jhAmt; - } - - public BigDecimal getJhAmt() - { - return jhAmt; - } - - public void setHtDj(BigDecimal htDj) - { - this.htDj = htDj; - } - - public BigDecimal getHtDj() - { - return htDj; - } - - public void setSapNo(String sapNo) - { - this.sapNo = sapNo; - } - - public String getSapNo() - { - return sapNo; - } - - public void setXh(String xh) - { - this.xh = xh; - } - - public String getXh() - { - return xh; - } - - public void setJhQty(BigDecimal jhQty) { this.jhQty = jhQty; } - public BigDecimal getJhQty() { return jhQty; } - - public void setHtQty(BigDecimal htQty) { this.htQty = htQty; } public BigDecimal getHtQty() { return htQty; } + public void setHtQty(BigDecimal htQty) { this.htQty = htQty; } - public void setDw(String dw) - { - this.dw = dw; + public String getSapNo() { return sapNo; } + public void setSapNo(String sapNo) { this.sapNo = sapNo; } + + public BigDecimal getJhQty() { return jhQty; } + public void setJhQty(BigDecimal jhQty) { this.jhQty = jhQty; } + + public BigDecimal getRealQty() { return realQty; } + public void setRealQty(BigDecimal realQty) { this.realQty = realQty; } + + /** + * 待入库数量(不落库,动态计算) + */ + public BigDecimal getWaitQty() { + if (jhQty == null) { + return BigDecimal.ZERO; + } + BigDecimal inQty = realQty == null ? BigDecimal.ZERO : realQty; + return jhQty.subtract(inQty); + } + public void setWaitQty(BigDecimal waitQty) { + this.waitQty = waitQty; } - public String getDw() - { - return dw; - } + public String getDw() { return dw; } + public void setDw(String dw) { this.dw = dw; } - public void setStatus(String status) - { - this.status = status; - } + public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } - public String getStatus() - { - return status; - } + public String getEntityId() { return entityId; } + public void setEntityId(String entityId) { this.entityId = entityId; } - public void setEntityId(String entityId) - { - this.entityId = entityId; - } + public String getRemark() { return remark; } + public void setRemark(String remark) { this.remark = remark; } - public String getEntityId() - { - return entityId; - } + public String getIsDelete() { return isDelete; } + public void setIsDelete(String isDelete) { this.isDelete = isDelete; } - public void setRemark(String remark) - { - this.remark = remark; - } + public Date getBeginTime() { return beginTime; } + public void setBeginTime(Date beginTime) { this.beginTime = beginTime; } - public String getRemark() - { - return remark; - } - - public void setIsDelete(String isDelete) - { - this.isDelete = isDelete; - } - - public String getIsDelete() - { - 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; - } + 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) - .append("id", getId()) - .append("indexNo", getIndexNo()) - .append("xj", getXj()) - .append("xmNo", getXmNo()) - .append("xmMs", getXmMs()) - .append("wlNo", getWlNo()) - .append("wlMs", getWlMs()) - .append("gysNo", getGysNo()) - .append("gysMc", getGysMc()) - .append("jhAmt", getJhAmt()) - .append("htDj", getHtDj()) - .append("sapNo", getSapNo()) - .append("xh", getXh()) - .append("jhQty", getJhQty()) - .append("htQty", getHtQty()) - .append("dw", getDw()) - .append("status", getStatus()) - .append("entityId", getEntityId()) - .append("remark", getRemark()) - .append("createBy", getCreateBy()) - .append("createTime", getCreateTime()) - .append("updateBy", getUpdateBy()) - .append("updateTime", getUpdateTime()) - .append("isDelete", getIsDelete()) - .toString(); + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("indexNo", getIndexNo()) + .append("xj", getXj()) + .append("xmNo", getXmNo()) + .append("xmMs", getXmMs()) + .append("wlNo", getWlNo()) + .append("wlMs", getWlMs()) + .append("gysNo", getGysNo()) + .append("gysMc", getGysMc()) + .append("jhAmt", getJhAmt()) + .append("htDj", getHtDj()) + .append("sapNo", getSapNo()) + .append("xh", getXh()) + .append("jhQty", getJhQty()) + .append("realQty", getRealQty()) + .append("waitQty", getWaitQty()) + .append("htQty", getHtQty()) + .append("dw", getDw()) + .append("status", getStatus()) + .append("entityId", getEntityId()) + .append("remark", getRemark()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("isDelete", getIsDelete()) + .toString(); } } diff --git a/src/main/java/com/zg/project/wisdom/domain/MoveRecord.java b/src/main/java/com/zg/project/wisdom/domain/MoveRecord.java index 45d7537..7816323 100644 --- a/src/main/java/com/zg/project/wisdom/domain/MoveRecord.java +++ b/src/main/java/com/zg/project/wisdom/domain/MoveRecord.java @@ -5,6 +5,7 @@ import com.zg.framework.aspectj.lang.annotation.Excel; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import java.math.BigDecimal; import java.util.Date; /** @@ -20,13 +21,20 @@ public class MoveRecord extends BaseEntity { /** 主键ID */ private Long id; - /** 关联库存表ID(rk_info.id) */ + /** 原库存ID(rk_info.id) */ private Long rkInfoId; + /** 移库生成的新库存ID(rk_info.id,用于撤销移库) */ + private Long newRkInfoId; + /** 实物ID */ @Excel(name = "实物ID") private String entityId; + /** 实际移库数量 */ + @Excel(name = "移库数量") + private BigDecimal realQty; + /** 原仓库 */ @Excel(name = "原仓库") private String fromCangku; @@ -112,6 +120,14 @@ public class MoveRecord extends BaseEntity { this.rkInfoId = rkInfoId; } + public Long getNewRkInfoId() { + return newRkInfoId; + } + + public void setNewRkInfoId(Long newRkInfoId) { + this.newRkInfoId = newRkInfoId; + } + public String getEntityId() { return entityId; } @@ -120,6 +136,14 @@ public class MoveRecord extends BaseEntity { this.entityId = entityId; } + public BigDecimal getRealQty() { + return realQty; + } + + public void setRealQty(BigDecimal realQty) { + this.realQty = realQty; + } + public String getFromCangku() { return fromCangku; } @@ -261,7 +285,9 @@ public class MoveRecord extends BaseEntity { return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) .append("id", getId()) .append("rkInfoId", getRkInfoId()) + .append("newRkInfoId", getNewRkInfoId()) .append("entityId", getEntityId()) + .append("realQty", getRealQty()) .append("fromCangku", getFromCangku()) .append("fromPcode", getFromPcode()) .append("fromTrayCode", getFromTrayCode()) diff --git a/src/main/java/com/zg/project/wisdom/domain/RkBill.java b/src/main/java/com/zg/project/wisdom/domain/RkBill.java index b23b4bc..f9b0ffe 100644 --- a/src/main/java/com/zg/project/wisdom/domain/RkBill.java +++ b/src/main/java/com/zg/project/wisdom/domain/RkBill.java @@ -77,7 +77,6 @@ public class RkBill extends BaseEntity { private String execStatus; /** 理货员(用户ID) */ -// @Excel(name = "理货员") private Integer operator; /** 理货员名称(联表) */ @@ -88,10 +87,31 @@ public class RkBill extends BaseEntity { @Excel(name = "施工队编码") private String teamCode; + /** 施工队名称(联表) */ + @Excel(name = "施工队名称") + private String teamName; + /** 是否需要配送(0否,1是,2配送中,3配送完成) */ @Excel(name = "是否需要配送", readConverterExp = "0=否,1=是,2=配送中,3=配送完成") private String isDelivery; + /** 借用时间 */ + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + private Date borrowTime; + + /** 归还时间 */ + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + private Date returnTime; + + /** 借用方项目号(借用方项目) */ + @Excel(name = "借用方项目号") + private String xmNoCk; + + /** 借用方项目描述(借用方项目描述) */ + @Excel(name = "借用方项目描述") + private String xmMsCk; + + /** 创建人 */ private String createBy; @@ -251,6 +271,14 @@ public class RkBill extends BaseEntity { this.teamCode = teamCode; } + public String getTeamName() { + return teamName; + } + + public void setTeamName(String teamName) { + this.teamName = teamName; + } + public String getIsDelivery() { return isDelivery; } @@ -259,6 +287,42 @@ public class RkBill extends BaseEntity { this.isDelivery = isDelivery; } + public Date getBorrowTime() { + return borrowTime; + } + + public void setBorrowTime(Date borrowTime) { + this.borrowTime = borrowTime; + } + + public Date getReturnTime() { + return returnTime; + } + + public void setReturnTime(Date returnTime) { + this.returnTime = returnTime; + } + + public void setXmNoCk(String xmNoCk) + { + this.xmNoCk = xmNoCk; + } + + public String getXmNoCk() + { + return xmNoCk; + } + + public void setXmMsCk(String xmMsCk) + { + this.xmMsCk = xmMsCk; + } + + public String getXmMsCk() + { + return xmMsCk; + } + public String getCreateBy() { return createBy; } @@ -343,13 +407,18 @@ public class RkBill extends BaseEntity { .append("operator", operator) .append("operatorName", operatorName) .append("teamCode", teamCode) + .append("teamName", teamName) .append("isDelivery", isDelivery) + .append("borrowTime", borrowTime) + .append("returnTime", returnTime) + .append("xmNoCk", getXmNoCk()) + .append("xmMsCk", getXmMsCk()) .append("createBy", createBy) .append("createTime", createTime) .append("updateBy", updateBy) .append("updateTime", updateTime) .append("isDelete", isDelete) - .append("remark", getRemark()) + .append("remark", remark) .append("startDate", startDate) .append("endDate", endDate) .toString(); diff --git a/src/main/java/com/zg/project/wisdom/domain/RkInfo.java b/src/main/java/com/zg/project/wisdom/domain/RkInfo.java index cd500c2..dad6016 100644 --- a/src/main/java/com/zg/project/wisdom/domain/RkInfo.java +++ b/src/main/java/com/zg/project/wisdom/domain/RkInfo.java @@ -102,11 +102,11 @@ public class RkInfo extends BaseEntity private String xmMs; /** 出库项目号(借用方项目) */ - @Excel(name = "出库项目号") + @Excel(name = "借用方项目号") private String xmNoCk; /** 出库项目描述(借用方项目描述) */ - @Excel(name = "出库项目描述") + @Excel(name = "借用方项目描述") private String xmMsCk; /** 物料号 */ @@ -178,13 +178,13 @@ public class RkInfo extends BaseEntity private String teamCode; /** 借用时间 */ - @JsonFormat(pattern = "yyyy-MM-dd") - @Excel(name = "借用时间", width = 20, dateFormat = "yyyy-MM-dd") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") +// @Excel(name = "借用时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") private Date borrowTime; /** 归还时间 */ - @JsonFormat(pattern = "yyyy-MM-dd") - @Excel(name = "归还时间", width = 20, dateFormat = "yyyy-MM-dd") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") +// @Excel(name = "归还时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") private Date returnTime; /** 是否移库过(0否 1是) */ 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 a6fd686..a0b54b9 100644 --- a/src/main/java/com/zg/project/wisdom/domain/RkRecord.java +++ b/src/main/java/com/zg/project/wisdom/domain/RkRecord.java @@ -29,6 +29,8 @@ public class RkRecord extends BaseEntity /** 业务类型 */ @Excel(name = "业务类型", readConverterExp = "0=入库,1=出库,2=借料出库,3=还料入库") private String bizType; + /** 业务类型列表(查询用) */ + private List bizTypeList; /** 出入库类型 */ // @Excel(name = "出入库类型") private String operationType; @@ -275,7 +277,13 @@ public class RkRecord extends BaseEntity public String getBizType() { return bizType; } + public List getBizTypeList() { + return bizTypeList; + } + public void setBizTypeList(List bizTypeList) { + this.bizTypeList = bizTypeList; + } public void setOperationType(String operationType) { this.operationType = operationType; diff --git a/src/main/java/com/zg/project/wisdom/mapper/GysJhMapper.java b/src/main/java/com/zg/project/wisdom/mapper/GysJhMapper.java index 9a00315..eb549ba 100644 --- a/src/main/java/com/zg/project/wisdom/mapper/GysJhMapper.java +++ b/src/main/java/com/zg/project/wisdom/mapper/GysJhMapper.java @@ -132,4 +132,41 @@ public interface GysJhMapper @Param("dw") String dw); + /** + * 根据供应计划ID,累加实际入库数量 + */ + void increaseRealQtyById( + @Param("realQty") BigDecimal realQty, + @Param("gysJhId") Long gysJhId + ); + + + void rollbackInQty( + @Param("jhId") Long jhId, + @Param("rollbackQty") BigDecimal rollbackQty + ); + + /** + * 根据id查询实收数量 + */ + BigDecimal selectRealQtyById(@Param("jhId") Long jhId); + + /** + * 根据id修改状态 + */ + + void updateStatus( + @Param("jhId") Long jhId, + @Param("status") String status + ); + + /** + * 根据供应计划ID,直接更新已入库数量(real_qty) + * + * @param gysJhId 供应计划ID + * @param realQty 已入库数量(以一键入库结果为准) + * @return 影响行数 + */ + int updateRealQtyById(@Param("gysJhId") Long gysJhId, + @Param("realQty") BigDecimal realQty); } diff --git a/src/main/java/com/zg/project/wisdom/mapper/RkBillMapper.java b/src/main/java/com/zg/project/wisdom/mapper/RkBillMapper.java index 72535d6..26a2b59 100644 --- a/src/main/java/com/zg/project/wisdom/mapper/RkBillMapper.java +++ b/src/main/java/com/zg/project/wisdom/mapper/RkBillMapper.java @@ -78,4 +78,12 @@ public interface RkBillMapper @Param("execStatus") String execStatus ); + /** + * 修改执行状态 + * @param billNo + * @param execStatus + * @return + */ + int updateExecStatus(@Param("billNo") String billNo, + @Param("execStatus") String execStatus); } 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 5187d3d..3144536 100644 --- a/src/main/java/com/zg/project/wisdom/mapper/RkInfoMapper.java +++ b/src/main/java/com/zg/project/wisdom/mapper/RkInfoMapper.java @@ -1,5 +1,7 @@ package com.zg.project.wisdom.mapper; +import java.math.BigDecimal; +import java.util.Date; import java.util.List; import com.zg.project.wisdom.domain.RkInfo; import io.lettuce.core.dynamic.annotation.Param; @@ -58,11 +60,46 @@ public interface RkInfoMapper * @param ids 需要删除的数据主键集合 * @return 结果 */ - public int deleteRkInfoByIds(Long[] ids); + int deleteRkInfoByIds(@Param("ids") Long[] ids); /** * 修改库存明细执行状态 */ int updateExecStatusByIds(@Param("ids") List ids, @Param("execStatus") String execStatus); + + /** + * 根据单据编号查询库存明细数量 + */ + int countByBillNo(String billNo); + + /** + * 根据入库明细ID查询非预入库数量 + */ + int countNotPreByIds(@Param("rkInfoIds") List rkInfoIds); + + /** + * 根据入库明细ID更新实收数量 + */ + void updateRealQtyById( + @Param("id") Long id, + @Param("realQty") BigDecimal realQty + ); + + /** + * 根据入库明细ID更新出库状态 + */ + void updateIsChukuById( + @Param("id") Long id, + @Param("isChuku") String isChuku + ); + + /** + * 标记借料已归还 + */ + int updateBorrowReturn(@Param("id") Long id, + @Param("isBorrowed") String isBorrowed, + @Param("returnTime") Date returnTime); + + List selectRkInfoByIds(@Param("rkInfoIds") List rkInfoIds); } 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 71b990c..7d371d9 100644 --- a/src/main/java/com/zg/project/wisdom/mapper/RkRecordMapper.java +++ b/src/main/java/com/zg/project/wisdom/mapper/RkRecordMapper.java @@ -69,7 +69,7 @@ public interface RkRecordMapper * @param ids 需要删除的数据主键集合 * @return 结果 */ - public int deleteRkRecordByIds(Long[] ids); + int deleteRkRecordByIds(@Param("ids") Long[] ids); /** * 修改执行状态 @@ -95,4 +95,30 @@ public interface RkRecordMapper * @param rkInfo 单据信息 */ void updateByRkInfo(RkInfo rkInfo); + + /** + * 根据单据号查询记录数量 + * + * @param billNo 单据号 + * @return 记录数量 + */ + int countByBillNo(String billNo); + + /** + * 根据单据号查询预入库记录数量 + * + * @param billNo 单据号 + * @return 记录数量 + */ + int countPreByBillNo(@Param("billNo") String billNo); + + int countPreOutByBillNo(@Param("billNo") String billNo); + + /** + * 根据单据ID查询记录 + * + * @param rkInfoId 单据ID + * @return 记录 + */ + RkRecord selectRkRecordByRkInfoId(@Param("rkInfoId") Long rkInfoId); } diff --git a/src/main/java/com/zg/project/wisdom/service/IMoveRecordService.java b/src/main/java/com/zg/project/wisdom/service/IMoveRecordService.java index cd5212d..337e040 100644 --- a/src/main/java/com/zg/project/wisdom/service/IMoveRecordService.java +++ b/src/main/java/com/zg/project/wisdom/service/IMoveRecordService.java @@ -60,4 +60,11 @@ public interface IMoveRecordService * @param dto */ void processMove(MoveRequestDTO dto); + + /** + * 撤销移库 + * + * @param moveRecordId 移库记录ID + */ + void rollbackMove(Long moveRecordId); } 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 0da4023..44c920f 100644 --- a/src/main/java/com/zg/project/wisdom/service/IRkBillService.java +++ b/src/main/java/com/zg/project/wisdom/service/IRkBillService.java @@ -66,4 +66,14 @@ public interface IRkBillService * @return 结果 */ public int deleteRkBillById(Long id); + + /** + * 新增出库 + */ + int insertOutBillAndDetail(RkBillCreateDTO dto); + + /** + * 还料入库 + */ + int insertReturnBillAndDetail(RkBillCreateDTO dto); } 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 a4c3eaa..2decfbc 100644 --- a/src/main/java/com/zg/project/wisdom/service/IRkRecordService.java +++ b/src/main/java/com/zg/project/wisdom/service/IRkRecordService.java @@ -76,4 +76,18 @@ public interface IRkRecordService * 批量将指定入库记录完成入库 */ int finishIn(List ids); + /** + * 撤销出库(已出库 → 预出库) + */ + int rollbackOut(List recordIds); + + /** + * 批量完成预出库 → 已出库 + */ + int finishOut(List recordIds); + + /** + * 删除预出库记录(仅限 exec_status = 0) + */ + int deletePreOutRecords(Long[] ids); } diff --git a/src/main/java/com/zg/project/wisdom/service/impl/GysJhServiceImpl.java b/src/main/java/com/zg/project/wisdom/service/impl/GysJhServiceImpl.java index 09873dd..5fc3167 100644 --- a/src/main/java/com/zg/project/wisdom/service/impl/GysJhServiceImpl.java +++ b/src/main/java/com/zg/project/wisdom/service/impl/GysJhServiceImpl.java @@ -167,13 +167,12 @@ public class GysJhServiceImpl implements IGysJhService List list = gysJhMapper.getBySapNo(sapNo); - AjaxResult result = AjaxResult.success(list); - if (list == null || list.isEmpty()) { - return result; + return AjaxResult.success(list); } - // 找出已入库(status=1)的物料号,去重、过滤空值 + AjaxResult result = AjaxResult.success(list); + List inStockWlNos = list.stream() .filter(x -> x != null && "1".equals(String.valueOf(x.getStatus()).trim())) .map(GysJh::getWlNo) @@ -185,18 +184,14 @@ public class GysJhServiceImpl implements IGysJhService if (!inStockWlNos.isEmpty()) { result.put("warn", true); result.put("inStockWlNos", inStockWlNos); - - // 组装提示文案(可根据前端展示需要调整长度) - String msg = "该 SAP 订单号下,以下物料号已入库过,请注意:" - + String.join("、", inStockWlNos); - result.put("msg", msg); + result.put("msg", "该 SAP 订单号下,以下物料号已全部入库,请注意:" + + String.join("、", inStockWlNos)); } return result; } - @Override @Transactional(rollbackFor = Exception.class) public int importByMapping(MultipartFile file, List mapping) throws Exception { diff --git a/src/main/java/com/zg/project/wisdom/service/impl/MoveRecordServiceImpl.java b/src/main/java/com/zg/project/wisdom/service/impl/MoveRecordServiceImpl.java index ad9faac..2f69a85 100644 --- a/src/main/java/com/zg/project/wisdom/service/impl/MoveRecordServiceImpl.java +++ b/src/main/java/com/zg/project/wisdom/service/impl/MoveRecordServiceImpl.java @@ -114,18 +114,13 @@ public class MoveRecordServiceImpl implements IMoveRecordService /** * 处理库存移库操作 * - * @param dto 移库请求参数(包含原库存ID、移库目标列表等) - */ - /** - * 处理库存移库操作 - * - * @param dto 移库请求参数(包含原库存ID、移库目标列表等) + * @param dto 移库请求参数 */ @Override @Transactional(rollbackFor = Exception.class) public void processMove(MoveRequestDTO dto) { - // ========= 0. 基本校验 ========= + /* ====================== 0. 基础校验 ====================== */ if (dto == null || dto.getFromRkId() == null) { throw new ServiceException("原库存ID不能为空"); } @@ -133,18 +128,18 @@ public class MoveRecordServiceImpl implements IMoveRecordService throw new ServiceException("目标位置列表不能为空"); } - // ========= 1. 查询原库存 ========= + /* ====================== 1. 查询原库存 ====================== */ RkInfo original = rkInfoMapper.selectRkInfoById(dto.getFromRkId()); if (original == null || "1".equals(original.getIsDelete())) { throw new ServiceException("原库存不存在或已删除"); } BigDecimal originQty = original.getRealQty(); - if (originQty == null) { - throw new ServiceException("原库存数量为空"); + if (originQty == null || originQty.compareTo(BigDecimal.ZERO) <= 0) { + throw new ServiceException("原库存数量异常"); } - // ========= 2. 计算移库总量 ========= + /* ====================== 2. 计算移库总量 ====================== */ BigDecimal moveTotalQty = dto.getTargets().stream() .map(MoveTargetItem::getRealQty) .reduce(BigDecimal.ZERO, BigDecimal::add); @@ -153,146 +148,189 @@ public class MoveRecordServiceImpl implements IMoveRecordService throw new ServiceException("移库数量不能大于原库存数量"); } - Long userId = SecurityUtils.getUserId(); - String username = userId.toString(); + String userId = String.valueOf(SecurityUtils.getUserId()); Date now = DateUtils.getNowDate(); - // ========= 3. 快照(只用于拷贝字段) ========= + /* ====================== 3. 原库存快照 ====================== */ RkInfo snapshot = new RkInfo(); BeanUtils.copyProperties(original, snapshot); - // ========= 4. 查询原 rk_record(用于同步更新) ========= - RkRecord originRecord = rkRecordMapper.selectRkRecordById(original.getId()); - if (originRecord == null) { - throw new ServiceException("未找到对应的出入库记录"); - } - - // ================== 一、全量移库 ================== + /* ====================== 4. 全量移库(不拆分) ====================== */ if (dto.getTargets().size() == 1 && moveTotalQty.compareTo(originQty) == 0) { MoveTargetItem target = dto.getTargets().get(0); - // 1️⃣ rk_info:更新库位 + // 4.1 更新原库存(仅换库位) original.setCangku(target.getToCangku()); original.setPcode(target.getToPcode()); original.setTrayCode(target.getToTrayCode()); original.setHasMoved("1"); - original.setUpdateBy(username); + original.setUpdateBy(userId); original.setUpdateTime(now); rkInfoMapper.updateRkInfo(original); - // 2️⃣ rk_record:同步更新库位 - originRecord.setCangku(target.getToCangku()); - originRecord.setPcode(target.getToPcode()); - originRecord.setTrayCode(target.getToTrayCode()); - originRecord.setHasMoved("1"); - originRecord.setUpdateBy(username); - originRecord.setUpdateTime(now); - rkRecordMapper.updateRkRecord(originRecord); + // 4.2 移库流水(全量移库,无新库存) + MoveRecord record = new MoveRecord(); + record.setRkInfoId(snapshot.getId()); + record.setNewRkInfoId(null); // ✅ 关键:全量移库没有新库存 + record.setEntityId(snapshot.getEntityId()); + record.setRealQty(originQty); - // 3️⃣ move_record:记录移库流水(绑定原 rk_info.id) - moveRecordMapper.insertMoveRecord( - createMoveRecord(original, target, dto) - ); + record.setFromCangku(snapshot.getCangku()); + record.setFromPcode(snapshot.getPcode()); + record.setFromTrayCode(snapshot.getTrayCode()); + record.setToCangku(target.getToCangku()); + record.setToPcode(target.getToPcode()); + record.setToTrayCode(target.getToTrayCode()); + + record.setMoveReason(dto.getMoveReason()); + record.setMovedBy(userId); + record.setMovedAt(now); + + record.setCreateBy(userId); + record.setCreateTime(now); + record.setUpdateBy(userId); + record.setUpdateTime(now); + record.setIsDelete("0"); + + moveRecordMapper.insertMoveRecord(record); return; } - // ================== 二、部分移库 ================== + /* ====================== 5. 部分移库(拆分库存) ====================== */ - // 1️⃣ 原 rk_info 扣减数量 + // 5.1 原库存扣减数量 original.setRealQty(originQty.subtract(moveTotalQty)); original.setHasMoved("1"); - original.setUpdateBy(username); + original.setUpdateBy(userId); original.setUpdateTime(now); rkInfoMapper.updateRkInfo(original); - // 2️⃣ 原 rk_record 扣减数量 - originRecord.setRealQty( - originRecord.getRealQty().subtract(moveTotalQty) - ); - originRecord.setHasMoved("1"); - originRecord.setUpdateBy(username); - originRecord.setUpdateTime(now); - rkRecordMapper.updateRkRecord(originRecord); - - // 3️⃣ 新增目标库存 + 新增目标记录 + // 5.2 生成新库存 + 移库流水 for (MoveTargetItem target : dto.getTargets()) { - // —— 新 rk_info + /* ---------- 新库存 ---------- */ RkInfo newInfo = new RkInfo(); BeanUtils.copyProperties(snapshot, newInfo, "id"); + newInfo.setCangku(target.getToCangku()); newInfo.setPcode(target.getToPcode()); newInfo.setTrayCode(target.getToTrayCode()); newInfo.setRealQty(target.getRealQty()); newInfo.setHasMoved("1"); - newInfo.setCreateBy(username); + + newInfo.setCreateBy(userId); newInfo.setCreateTime(now); - newInfo.setUpdateBy(username); + newInfo.setUpdateBy(userId); newInfo.setUpdateTime(now); + rkInfoMapper.insertRkInfo(newInfo); - // —— 新 rk_record - RkRecord newRecord = new RkRecord(); - BeanUtils.copyProperties(originRecord, newRecord, "id"); - newRecord.setCangku(target.getToCangku()); - newRecord.setPcode(target.getToPcode()); - newRecord.setTrayCode(target.getToTrayCode()); - newRecord.setRealQty(target.getRealQty()); - newRecord.setHasMoved("1"); - newRecord.setCreateBy(username); - newRecord.setCreateTime(now); - newRecord.setUpdateBy(username); - newRecord.setUpdateTime(now); - rkRecordMapper.insertRkRecord(newRecord); + /* ---------- 移库流水(重点) ---------- */ + MoveRecord record = new MoveRecord(); + record.setRkInfoId(snapshot.getId()); // 原库存 + record.setNewRkInfoId(newInfo.getId()); // ✅ 关键:新库存ID + record.setEntityId(snapshot.getEntityId()); + record.setRealQty(target.getRealQty()); - // —— move_record:绑定新生成的 rk_info.id - moveRecordMapper.insertMoveRecord( - createMoveRecord(newInfo, target, dto) - ); + record.setFromCangku(snapshot.getCangku()); + record.setFromPcode(snapshot.getPcode()); + record.setFromTrayCode(snapshot.getTrayCode()); + + record.setToCangku(target.getToCangku()); + record.setToPcode(target.getToPcode()); + record.setToTrayCode(target.getToTrayCode()); + + record.setMoveReason(dto.getMoveReason()); + record.setMovedBy(userId); + record.setMovedAt(now); + + record.setCreateBy(userId); + record.setCreateTime(now); + record.setUpdateBy(userId); + record.setUpdateTime(now); + record.setIsDelete("0"); + + moveRecordMapper.insertMoveRecord(record); } } /** - * 构建移库记录对象 - * - * @param info 原库存记录 - * @param target 目标位置数据 - * @param dto 请求参数 - * @return 构建后的移库记录 + * 撤销移库 */ - private MoveRecord createMoveRecord(RkInfo info, - MoveTargetItem target, - MoveRequestDTO dto) { + @Override + @Transactional(rollbackFor = Exception.class) + public void rollbackMove(Long moveRecordId) { - Long userId = SecurityUtils.getUserId(); + if (moveRecordId == null) { + throw new ServiceException("移库记录ID不能为空"); + } + + /* ====================== 1. 查询移库记录 ====================== */ + MoveRecord record = moveRecordMapper.selectMoveRecordById(moveRecordId); + if (record == null || "1".equals(record.getIsDelete())) { + throw new ServiceException("移库记录不存在或已撤销"); + } + + Long originRkInfoId = record.getRkInfoId(); + Long newRkInfoId = record.getNewRkInfoId(); + + /* ====================== 2. 查询原库存 ====================== */ + RkInfo origin = rkInfoMapper.selectRkInfoById(originRkInfoId); + if (origin == null || "1".equals(origin.getIsDelete())) { + throw new ServiceException("原库存不存在或已删除,无法撤销"); + } + + String userId = String.valueOf(SecurityUtils.getUserId()); Date now = DateUtils.getNowDate(); - MoveRecord record = new MoveRecord(); + /* ====================== 3. 分情况处理 ====================== */ - // ★ 关键:绑定 rk_info.id - record.setRkInfoId(info.getId()); + // ---------- 情况一:全量移库 ---------- + if (newRkInfoId == null) { - record.setEntityId(info.getEntityId()); - record.setFromCangku(info.getCangku()); - record.setFromPcode(info.getPcode()); - record.setFromTrayCode(info.getTrayCode()); - record.setToCangku(target.getToCangku()); - record.setToPcode(target.getToPcode()); - record.setToTrayCode(target.getToTrayCode()); - record.setMoveReason(dto.getMoveReason()); - record.setMovedBy(userId.toString()); - record.setMovedAt(now); + // 回滚库位 + origin.setCangku(record.getFromCangku()); + origin.setPcode(record.getFromPcode()); + origin.setTrayCode(record.getFromTrayCode()); + origin.setHasMoved("0"); + origin.setUpdateBy(userId); + origin.setUpdateTime(now); - record.setIsDelete("0"); - record.setCreateBy(userId.toString()); - record.setCreateTime(now); - record.setUpdateBy(userId.toString()); + rkInfoMapper.updateRkInfo(origin); + } + // ---------- 情况二:部分移库 ---------- + else { + + /* 3.1 原库存回补数量 */ + BigDecimal qty = record.getRealQty(); + if (qty == null || qty.compareTo(BigDecimal.ZERO) <= 0) { + throw new ServiceException("移库数量异常,无法撤销"); + } + + origin.setRealQty(origin.getRealQty().add(qty)); + origin.setHasMoved("0"); + origin.setUpdateBy(userId); + origin.setUpdateTime(now); + origin.setHasMoved("0"); + + rkInfoMapper.updateRkInfo(origin); + + /* 3.2 删除移库生成的新库存 */ + RkInfo newInfo = rkInfoMapper.selectRkInfoById(newRkInfoId); + if (newInfo != null && !"1".equals(newInfo.getIsDelete())) { + newInfo.setIsDelete("1"); + newInfo.setUpdateBy(userId); + newInfo.setUpdateTime(now); + rkInfoMapper.updateRkInfo(newInfo); + } + } + + /* ====================== 4. 标记移库记录已撤销 ====================== */ + record.setIsDelete("1"); + record.setUpdateBy(userId); record.setUpdateTime(now); - - return record; + moveRecordMapper.updateMoveRecord(record); } - - } 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 5559947..a95c8aa 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 @@ -5,6 +5,7 @@ import java.util.Date; import java.util.List; import java.util.Map; +import com.zg.common.exception.ServiceException; import com.zg.common.utils.DateUtils; import com.zg.common.utils.SecurityUtils; import com.zg.common.utils.StringUtils; @@ -100,7 +101,7 @@ public class RkBillServiceImpl implements IRkBillService Date now = DateUtils.getNowDate(); String userId = String.valueOf(SecurityUtils.getUserId()); - // ================== 1. 主单 rk_bill ================== + /* ================== 1. 主单 rk_bill ================== */ RkBill bill = new RkBill(); if (dto.getRkBill() != null) { BeanUtils.copyProperties(dto.getRkBill(), bill); @@ -108,7 +109,7 @@ public class RkBillServiceImpl implements IRkBillService bill.setBillNo(billNo); bill.setBizType("0"); // 入库 - bill.setOperationTime(now); + bill.setOperationTime(dto.getRkBill().getOperationTime()); bill.setCreateTime(now); bill.setCreateBy(userId); bill.setIsDelete("0"); @@ -118,17 +119,20 @@ public class RkBillServiceImpl implements IRkBillService bill.setOperator(Integer.valueOf(userId)); } - // execStatus:默认已完成 + // execStatus:默认已完成(预入库 / 直接入库统一处理) if (StringUtils.isBlank(bill.getExecStatus())) { bill.setExecStatus("1"); } rkBillMapper.insertRkBill(bill); - // ================== 2. 明细 + 事件 + 供应计划 ================== + /* ================== 2. 明细 + 事件 + 供应计划 ================== */ for (RkInfo info : dto.getRkInfoList()) { - // ===== ① 根据 pcode 查询 pcde_detail,获取 encoded_id ===== + /* ====== ★ 0. 入库前:供应计划数量校验(核心新增) ====== */ + checkGysJhQtyBeforeInStock(info); + + /* ====== ① 根据 pcode 查询 pcde_detail,获取 encoded_id ====== */ if (StringUtils.isNotBlank(info.getPcode())) { PcdeDetail pcde = pcdeDetailMapper.selectByPcode(info.getPcode()); @@ -139,7 +143,7 @@ public class RkBillServiceImpl implements IRkBillService info.setPcodeId(pcde.getEncodedId()); } - // ---------- rk_info ---------- + /* ---------- rk_info ---------- */ info.setBillNo(billNo); info.setBizType(bill.getBizType()); info.setOperationType(bill.getOperationType()); @@ -159,20 +163,56 @@ public class RkBillServiceImpl implements IRkBillService 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); } return 1; } + /** + * 入库前校验:入库数量不能超过供应计划数量 + */ + private void checkGysJhQtyBeforeInStock(RkInfo info) { + + if (info.getGysJhId() == null || info.getRealQty() == null) { + return; + } + + GysJh gysJh = gysJhMapper.selectGysJhById(info.getGysJhId()); + if (gysJh == null) { + throw new RuntimeException("供应计划不存在,ID:" + info.getGysJhId()); + } + + BigDecimal planQty = gysJh.getJhQty(); + if (planQty == null) { + return; + } + + BigDecimal alreadyInQty = gysJh.getRealQty(); + if (alreadyInQty == null) { + alreadyInQty = BigDecimal.ZERO; + } + + BigDecimal afterQty = alreadyInQty.add(info.getRealQty()); + + if (afterQty.compareTo(planQty) > 0) { + throw new RuntimeException( + "入库数量超出供应计划数量,物料号:" + info.getWlNo() + + ",计划数量:" + planQty + + ",已入库:" + alreadyInQty + + ",本次入库:" + info.getRealQty() + ); + } + } + private void handleGysJhAfterInStock(RkInfo info) { // 1. 未关联供应计划或无实际入库数量,直接跳过 @@ -180,29 +220,36 @@ public class RkBillServiceImpl implements IRkBillService return; } - // 2. 扣减供应计划数量(jh_qty = jh_qty - realQty) - gysJhMapper.decreaseJhQtyById( - info.getGysJhId(), - info.getRealQty() + // 2. 累加 实际入库数量(real_qty = real_qty + 本次入库数量) + gysJhMapper.increaseRealQtyById( + info.getRealQty(), + info.getGysJhId() ); - // 3. 查询最新供应计划数据(用于判断剩余数量) + // 3. 查询最新供应计划数据 GysJh gysJh = gysJhMapper.selectGysJhById(info.getGysJhId()); if (gysJh == null) { throw new RuntimeException("供应计划不存在,ID:" + info.getGysJhId()); } - // 4. 根据剩余数量判断状态 - BigDecimal remainQty = gysJh.getJhQty(); + BigDecimal planQty = gysJh.getJhQty(); // 计划交货数量 + BigDecimal realQty = gysJh.getRealQty(); // 实际入库数量 + if (realQty == null) { + realQty = BigDecimal.ZERO; + } + + // 4. 状态判定 String status; - if (remainQty == null || remainQty.compareTo(BigDecimal.ZERO) <= 0) { - status = "1"; // 全部入库 + if (realQty.compareTo(BigDecimal.ZERO) == 0) { + status = "0"; // 未到货 + } else if (planQty != null && realQty.compareTo(planQty) >= 0) { + status = "1"; // 已入库 } else { status = "2"; // 部分入库 } - // 5. 更新供应计划状态(⚠ 不使用 Map,直接参数) + // 5. 更新供应计划状态 gysJhMapper.updateStatusById(gysJh.getId(), status); } @@ -294,8 +341,8 @@ public class RkBillServiceImpl implements IRkBillService info.setCangku(bill.getCangku()); // ---------- 操作信息 ---------- - info.setOperationTime(now); - info.setOperator(bill.getOperator()); + info.setOperationTime(info.getOperationTime()); + info.setOperator(info.getOperator()); // ---------- 执行状态 ---------- info.setExecStatus(execStatus); @@ -359,11 +406,50 @@ public class RkBillServiceImpl implements IRkBillService * @return 结果 */ @Override - public int deleteRkBillByIds(Long[] ids) - { - return rkBillMapper.deleteRkBillByIds(ids); - } + @Transactional(rollbackFor = Exception.class) + public int deleteRkBillByIds(Long[] ids) { + if (ids == null || ids.length == 0) { + return 0; + } + + int rows = 0; + + for (Long id : ids) { + + // 1. 查询单据 + RkBill bill = rkBillMapper.selectRkBillById(id); + if (bill == null) { + continue; + } + + String billNo = bill.getBillNo(); + if (billNo == null || billNo.isEmpty()) { + continue; + } + + // 2. 校验 rk_record 是否存在 + int recordCount = rkRecordMapper.countByBillNo(billNo); + if (recordCount > 0) { + throw new ServiceException( + "单据号【" + billNo + "】仍存在出入库记录,不允许删除" + ); + } + + // 3. 校验 rk_info 是否存在 + int infoCount = rkInfoMapper.countByBillNo(billNo); + if (infoCount > 0) { + throw new ServiceException( + "单据号【" + billNo + "】仍存在库存明细,不允许删除" + ); + } + + // 4. 真正删除 rk_bill + rows += rkBillMapper.deleteRkBillById(id); + } + + return rows; + } /** * 删除库存单据信息 * @@ -375,4 +461,286 @@ public class RkBillServiceImpl implements IRkBillService { return rkBillMapper.deleteRkBillById(id); } + + @Override + @Transactional(rollbackFor = Exception.class) + public int insertOutBillAndDetail(RkBillCreateDTO dto) { + + if (dto == null || dto.getRkInfoList() == null || dto.getRkInfoList().isEmpty()) { + throw new RuntimeException("出库明细不能为空"); + } + + String billNo = BillNoUtil.generateTodayBillNo("CK", 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); + } + + if (StringUtils.isBlank(bill.getBizType())) { + throw new RuntimeException("bizType 不能为空"); + } + + bill.setBillNo(billNo); + bill.setOperationTime(now); + bill.setCreateTime(now); + bill.setCreateBy(userId); + bill.setIsDelete("0"); + + if (bill.getOperator() == null) { + bill.setOperator(Integer.valueOf(userId)); + } + + // exec_status:0=预出库,1=已出库 + if (StringUtils.isBlank(bill.getExecStatus())) { + bill.setExecStatus("1"); + } + + boolean isPreOut = "0".equals(bill.getExecStatus()); + + // ===== 借料出库字段 ===== + if ("2".equals(bill.getBizType())) { + bill.setBorrowTime(bill.getBorrowTime() != null ? bill.getBorrowTime() : now); + bill.setXmNoCk(bill.getXmNoCk()); + bill.setXmMsCk(bill.getXmMsCk()); + } + + rkBillMapper.insertRkBill(bill); + + // ================== 2. 出库处理 ================== + for (RkInfo outInfo : dto.getRkInfoList()) { + + if (outInfo.getId() == null) { + throw new RuntimeException("出库必须指定库存明细ID"); + } + if (outInfo.getRealQty() == null) { + throw new RuntimeException("出库数量不能为空"); + } + + RkInfo dbInfo = rkInfoMapper.selectRkInfoById(outInfo.getId()); + if (dbInfo == null) { + throw new RuntimeException("库存不存在,ID:" + outInfo.getId()); + } + + BigDecimal remainQty = dbInfo.getRealQty(); + BigDecimal outQty = outInfo.getRealQty(); + + if (!isPreOut && remainQty.compareTo(outQty) < 0) { + throw new RuntimeException("出库数量不能大于库存数量,库存ID:" + dbInfo.getId()); + } + + // ===== ① 非预出库才扣库存 ===== + if (!isPreOut) { + BigDecimal newQty = remainQty.subtract(outQty); + rkInfoMapper.updateRealQtyById(dbInfo.getId(), newQty); + + rkInfoMapper.updateIsChukuById( + dbInfo.getId(), + newQty.compareTo(BigDecimal.ZERO) == 0 ? "1" : "0" + ); + } + + // ===== ② 出库记录(重点:传 outInfo)===== + RkRecord record = buildOutRkRecord(bill, dbInfo, outInfo, outQty, now); + record.setExecStatus(isPreOut ? "0" : "1"); + + rkRecordMapper.insertRkRecord(record); + } + + return 1; + } + + private RkRecord buildOutRkRecord( + RkBill bill, + RkInfo dbInfo, + RkInfo outInfo, + BigDecimal outQty, + Date now) { + + RkRecord record = new RkRecord(); + + // ① 库存快照 + BeanUtils.copyProperties(dbInfo, record); + record.setId(null); + + // ② 本次出库数量 + record.setRealQty(outQty); + + // ③ 主单上下文 + record.setBillNo(bill.getBillNo()); + record.setBizType(bill.getBizType()); + record.setOperationType(bill.getOperationType()); + record.setOperationTime(bill.getOperationTime()); + record.setOperator(bill.getOperator()); + record.setCangku(dbInfo.getCangku()); + record.setTeamCode(dbInfo.getTeamCode()); + + // ===== ★ 关键:备注来自【出库明细 outInfo】===== + record.setRemark(outInfo.getRemark()); + + // ④ 借料专属字段 + if ("2".equals(bill.getBizType())) { + record.setIsBorrowed("1"); + record.setBorrowTime(bill.getBorrowTime()); + record.setReturnTime(bill.getReturnTime()); + record.setXmNoCk(bill.getXmNoCk()); + record.setXmMsCk(bill.getXmMsCk()); + } else { + record.setIsBorrowed("0"); + } + + // ⑤ 状态字段 + record.setIsChuku("1"); + record.setHasMoved("0"); + record.setIsDelete("0"); + record.setExecStatus(bill.getExecStatus()); + + // ⑥ 审计字段 + record.setCreateTime(now); + record.setCreateBy(bill.getCreateBy()); + + // ⑦ 关联库存 + record.setRkInfoId(dbInfo.getId()); + record.setRdid(dbInfo.getId()); + + return record; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int insertReturnBillAndDetail(RkBillCreateDTO dto) { + + if (dto == null || dto.getRkInfoList() == null || dto.getRkInfoList().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); + } + + 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"); + } + + // 必须关联原借料出库库存 + if (info.getRdid() == null) { + throw new RuntimeException("还料必须指定原借料库存ID(rdid)"); + } + + RkInfo borrowInfo = rkInfoMapper.selectRkInfoById(info.getRdid()); + if (borrowInfo == null) { + throw new RuntimeException("原借料库存不存在,ID:" + info.getRdid()); + } + + /* ---------- rk_info(新增库存) ---------- */ + info.setId(null); + info.setBillNo(billNo); + info.setBizType("3"); // 还料入库 + info.setOperationType(bill.getOperationType()); + info.setOperationTime(bill.getOperationTime()); + info.setOperator(bill.getOperator()); + + // 关键:库存是“新增”,不是回补原库存 + info.setIsChuku("0"); + info.setIsBorrowed("0"); + info.setHasMoved("0"); + info.setExecStatus(bill.getExecStatus()); + + // 仓库 / 物料 / 项目信息,默认继承原借料库存 + 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); + + /* ---------- 原借料记录标记“已归还” ---------- */ + rkInfoMapper.updateBorrowReturn( + borrowInfo.getId(), + "2", // is_borrowed = 2 已归还 + 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; + } + } 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 ba353aa..8d74015 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 @@ -1,13 +1,15 @@ package com.zg.project.wisdom.service.impl; -import java.util.List; -import java.util.Objects; +import java.math.BigDecimal; +import java.util.*; import java.util.stream.Collectors; import com.zg.common.exception.ServiceException; import com.zg.common.utils.DateUtils; import com.zg.common.utils.StringUtils; +import com.zg.project.wisdom.domain.GysJh; import com.zg.project.wisdom.domain.RkInfo; +import com.zg.project.wisdom.mapper.GysJhMapper; import com.zg.project.wisdom.mapper.RkBillMapper; import com.zg.project.wisdom.mapper.RkInfoMapper; import org.springframework.beans.BeanUtils; @@ -36,6 +38,9 @@ public class RkRecordServiceImpl implements IRkRecordService @Autowired private RkBillMapper rkBillMapper; + @Autowired + private GysJhMapper gysJhMapper; + /** * 查询出入库记录 * @@ -81,44 +86,179 @@ public class RkRecordServiceImpl implements IRkRecordService /** * 修改出入库记录 - * + * + * 业务约定: + * 1. rk_record.real_qty —— 单条出入库记录的最终数量(绝对值) + * 2. rk_info.real_qty —— 当前库存数量 + * 3. gys_jh.real_qty —— 供应计划累计已入库数量 + * + * 入库(bizType = 0): + * - 使用【新值 - 旧值】差量修正供应计划 real_qty + * - 严禁累计入库数量超过计划数量 jh_qty + * - 自动维护供应计划状态: + * real_qty == 0 → 未到货 + * 0 < real_qty < jh_qty→ 部分到货 + * real_qty == jh_qty → 已入库 + * + * 出库(bizType = 1): + * - 出库数量不得超过库存 + * - 出库数量变化联动库存 real_qty + * - 库存为 0 时 is_chuku = 1,否则 = 0 + * * @param rkRecord 出入库记录 - * @return 结果 + * @return 影响行数 */ @Override @Transactional(rollbackFor = Exception.class) - public int updateRkRecord(RkRecord rkRecord) - { - // 1. 更新时间 + public int updateRkRecord(RkRecord rkRecord) { + + /* ====================== 1. 基础校验 ====================== */ + if (rkRecord == null || rkRecord.getId() == null) { + throw new RuntimeException("出入库记录ID不能为空"); + } + rkRecord.setUpdateTime(DateUtils.getNowDate()); - // 2. 更新 rk_record + /* ====================== 2. 查询原记录(唯一可信来源) ====================== */ + RkRecord oldRecord = rkRecordMapper.selectRkRecordById(rkRecord.getId()); + if (oldRecord == null) { + throw new RuntimeException("原出入库记录不存在"); + } + + /* ====================== 3. 获取并校验库存ID(必须来自 oldRecord) ====================== */ + Long rkInfoId = oldRecord.getRkInfoId(); + if (rkInfoId == null) { + throw new RuntimeException("出入库记录未关联库存,禁止修改"); + } + + RkInfo info = rkInfoMapper.selectRkInfoById(rkInfoId); + if (info == null) { + throw new RuntimeException("关联的库存记录不存在"); + } + + String bizType = rkRecord.getBizType(); + + /* ========================================================= + * 【A】入库记录修改(bizType = 0) + * ========================================================= */ + if ("0".equals(bizType)) { + + BigDecimal oldInQty = oldRecord.getRealQty() == null + ? BigDecimal.ZERO + : oldRecord.getRealQty(); + + BigDecimal newInQty = rkRecord.getRealQty() == null + ? BigDecimal.ZERO + : rkRecord.getRealQty(); + + BigDecimal diff = newInQty.subtract(oldInQty); + + /* ---------- 供应计划处理 ---------- */ + Long gysJhId = oldRecord.getGysJhId(); + if (gysJhId != null) { + + GysJh gysJh = gysJhMapper.selectGysJhById(gysJhId); + if (gysJh == null) { + throw new RuntimeException("关联的供应计划不存在,ID:" + gysJhId); + } + + BigDecimal planQty = gysJh.getJhQty(); + BigDecimal planRealQty = gysJh.getRealQty() == null + ? BigDecimal.ZERO + : gysJh.getRealQty(); + + BigDecimal newPlanRealQty = planRealQty.add(diff); + + if (planQty != null && newPlanRealQty.compareTo(planQty) > 0) { + throw new RuntimeException("调整后已入库数量不能大于供应计划数量"); + } + + gysJh.setRealQty(newPlanRealQty); + + if (newPlanRealQty.compareTo(BigDecimal.ZERO) == 0) { + gysJh.setStatus("0"); // 未到货 + } else if (planQty != null && newPlanRealQty.compareTo(planQty) >= 0) { + gysJh.setStatus("1"); // 已入库 + } else { + gysJh.setStatus("2"); // 部分入库 + } + + gysJh.setWlNo(rkRecord.getWlNo()); + gysJh.setWlMs(rkRecord.getWlMs()); + gysJh.setXmNo(rkRecord.getXmNo()); + gysJh.setXmMs(rkRecord.getXmMs()); + gysJh.setGysNo(rkRecord.getGysNo()); + gysJh.setGysMc(rkRecord.getGysMc()); + gysJh.setDw(rkRecord.getDw()); + gysJh.setHtDj(rkRecord.getHtDj()); + gysJh.setUpdateTime(DateUtils.getNowDate()); + + gysJhMapper.updateGysJh(gysJh); + } + + /* ---------- 同步库存(明确字段,禁止 BeanUtils) ---------- */ + info.setRealQty(newInQty); + info.setWlNo(rkRecord.getWlNo()); + info.setWlMs(rkRecord.getWlMs()); + info.setXmNo(rkRecord.getXmNo()); + info.setXmMs(rkRecord.getXmMs()); + info.setGysNo(rkRecord.getGysNo()); + info.setGysMc(rkRecord.getGysMc()); + info.setDw(rkRecord.getDw()); + info.setHtDj(rkRecord.getHtDj()); + info.setIsChuku("0"); + } + + /* ========================================================= + * 【B】出库记录修改(bizType = 1) + * ========================================================= */ + if ("1".equals(bizType)) { + + BigDecimal oldOutQty = oldRecord.getRealQty(); + BigDecimal newOutQty = rkRecord.getRealQty(); + BigDecimal stockQty = info.getRealQty(); + + if (oldOutQty == null || newOutQty == null || stockQty == null) { + throw new RuntimeException("数量数据异常,无法修改出库记录"); + } + + BigDecimal diff = newOutQty.subtract(oldOutQty); + BigDecimal newStockQty = stockQty.subtract(diff); + + if (newStockQty.compareTo(BigDecimal.ZERO) < 0) { + throw new RuntimeException("库存不足,无法修改出库数量"); + } + + info.setRealQty(newStockQty); + info.setIsChuku(newStockQty.compareTo(BigDecimal.ZERO) == 0 ? "1" : "0"); + + info.setWlNo(rkRecord.getWlNo()); + info.setWlMs(rkRecord.getWlMs()); + info.setXmNo(rkRecord.getXmNo()); + info.setXmMs(rkRecord.getXmMs()); + info.setGysNo(rkRecord.getGysNo()); + info.setGysMc(rkRecord.getGysMc()); + info.setDw(rkRecord.getDw()); + info.setHtDj(rkRecord.getHtDj()); + } + + /* ====================== 4. 更新库存表 ====================== */ + info.setUpdateTime(DateUtils.getNowDate()); + int infoRows = rkInfoMapper.updateRkInfo(info); + if (infoRows <= 0) { + throw new RuntimeException("同步更新库存表失败"); + } + + /* ====================== 5. 最后更新 rk_record ====================== */ + rkRecord.setRkInfoId(rkInfoId); // 强制回填,防止被置空 int rows = rkRecordMapper.updateRkRecord(rkRecord); if (rows <= 0) { throw new RuntimeException("更新出入库记录失败"); } - // 3. 同步更新 rk_info - Long rkInfoId = rkRecord.getRkInfoId(); - if (rkInfoId == null) { - throw new RuntimeException("rkInfoId 为空,无法同步更新库存表"); - } - - // 4. 将 record 中的字段映射到 info - RkInfo rkInfo = new RkInfo(); - BeanUtils.copyProperties(rkRecord, rkInfo); - - // ⚠️ 关键:主键必须用 rk_info.id - rkInfo.setId(rkInfoId); - rkInfo.setUpdateTime(DateUtils.getNowDate()); - - int infoRows = rkInfoMapper.updateRkInfo(rkInfo); - if (infoRows <= 0) { - throw new RuntimeException("同步更新库存表失败"); - } - return rows; } + /** * 批量删除出入库记录 * @@ -126,11 +266,92 @@ public class RkRecordServiceImpl implements IRkRecordService * @return 结果 */ @Override - public int deleteRkRecordByIds(Long[] ids) - { - return rkRecordMapper.deleteRkRecordByIds(ids); + @Transactional(rollbackFor = Exception.class) + public int deleteRkRecordByIds(Long[] ids) { + + if (ids == null || ids.length == 0) { + return 0; + } + + // ================== 1. 查询 record ================== + List records = rkRecordMapper.selectRkRecordByIds(Arrays.asList(ids)); + if (records == null || records.isEmpty()) { + return 0; + } + + // ================== 2. 校验 record 必须全部是预入库 ================== + boolean hasFinishedRecord = records.stream() + .anyMatch(r -> r.getExecStatus() == null || !"0".equals(r.getExecStatus())); + if (hasFinishedRecord) { + throw new ServiceException("仅允许删除预入库/预出库的出入库记录"); + } + + // ================== 3. 回滚供应计划已入库数量 + 状态 ================== + Map rollbackMap = records.stream() + .filter(r -> r.getGysJhId() != null && r.getRealQty() != null) + .collect(Collectors.groupingBy( + RkRecord::getGysJhId, + Collectors.mapping( + RkRecord::getRealQty, + Collectors.reducing(BigDecimal.ZERO, BigDecimal::add) + ) + )); + + for (Map.Entry entry : rollbackMap.entrySet()) { + + Long jhId = entry.getKey(); + BigDecimal rollbackQty = entry.getValue(); + + // 1️⃣ 回滚数量 + gysJhMapper.rollbackInQty(jhId, rollbackQty); + + // 2️⃣ 判断回滚后数量是否为 0 + BigDecimal remainQty = gysJhMapper.selectRealQtyById(jhId); + + if (remainQty == null || remainQty.compareTo(BigDecimal.ZERO) == 0) { + // 3️⃣ 已入库数量为 0,回退状态 + gysJhMapper.updateStatus(jhId, "0"); // 0=未入库 + } + } + + // ================== 4. 收集 rkInfoId ================== + List rkInfoIds = records.stream() + .map(RkRecord::getRkInfoId) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + + // ================== 5. 校验 info 也必须是预入库 ================== + if (!rkInfoIds.isEmpty()) { + int invalidCount = rkInfoMapper.countNotPreByIds(rkInfoIds); + if (invalidCount > 0) { + throw new ServiceException("存在非预入库的库存明细,不允许删除"); + } + + // 先删 info + rkInfoMapper.deleteRkInfoByIds(rkInfoIds.toArray(new Long[0])); + } + + // ================== 6. 删除 record ================== + int deleteCount = rkRecordMapper.deleteRkRecordByIds(ids); + + // ================== 7. 判断是否需要更新 bill 状态 ================== + Set billNos = records.stream() + .map(RkRecord::getBillNo) + .filter(StringUtils::isNotBlank) + .collect(Collectors.toSet()); + + for (String billNo : billNos) { + int remainPreCount = rkRecordMapper.countPreByBillNo(billNo); + if (remainPreCount == 0) { + rkBillMapper.updateExecStatus(billNo, "1"); + } + } + + return deleteCount; } + /** * 删除出入库记录信息 * @@ -151,26 +372,235 @@ public class RkRecordServiceImpl implements IRkRecordService throw new ServiceException("撤销记录ID不能为空"); } - // 1️⃣ 查 records + /* ====================== 1️⃣ 查询入库记录 ====================== */ List recordList = rkRecordMapper.selectRkRecordByIds(recordIds); if (recordList == null || recordList.isEmpty()) { throw new ServiceException("入库记录不存在"); } - // 2️⃣ rk_record 批量回退 exec_status = 0 - rkRecordMapper.updateExecStatusByIds(recordIds, "0"); - - // 3️⃣ rk_info 批量回退 exec_status = 0 + /* ====================== 2️⃣ 校验是否发生过移库 ====================== */ List rkInfoIds = recordList.stream() .map(RkRecord::getRkInfoId) .filter(Objects::nonNull) .distinct() .collect(Collectors.toList()); + + if (!rkInfoIds.isEmpty()) { + + // 查询对应库存 + List infoList = rkInfoMapper.selectRkInfoByIds(rkInfoIds); + + // 只要有一条 has_moved = 1,直接禁止撤销 + boolean hasMoved = infoList.stream() + .anyMatch(info -> "1".equals(info.getHasMoved())); + + if (hasMoved) { + throw new ServiceException("存在已移库的库存数据,禁止撤销入库"); + } + } + + /* ====================== 3️⃣ rk_record 回退为预入库 ====================== */ + rkRecordMapper.updateExecStatusByIds(recordIds, "0"); + + /* ====================== 4️⃣ rk_info 回退为预入库 ====================== */ if (!rkInfoIds.isEmpty()) { rkInfoMapper.updateExecStatusByIds(rkInfoIds, "0"); } - // 4️⃣ rk_bill:保持原 Mapper 方法不变,按 billNo 去重后循环调用 + /* ====================== 5️⃣ rk_bill 回退为预入库 ====================== */ + List billNos = recordList.stream() + .map(RkRecord::getBillNo) + .filter(StringUtils::isNotBlank) + .distinct() + .collect(Collectors.toList()); + + for (String billNo : billNos) { + rkBillMapper.updateExecStatusByBillNo(billNo, "0"); + } + + return recordIds.size(); + } + + + @Override + @Transactional(rollbackFor = Exception.class) + public int finishIn(List recordIds) { + + if (recordIds == null || recordIds.isEmpty()) { + throw new ServiceException("入库记录ID不能为空"); + } + + /* ================== 1. 查询 rk_record ================== */ + List recordList = + rkRecordMapper.selectRkRecordByIds(recordIds); + + if (recordList == null || recordList.isEmpty()) { + throw new ServiceException("入库记录不存在"); + } + + /* ================== 2. 只处理「预入库」记录 ================== */ + List preRecordList = recordList.stream() + .filter(r -> "0".equals(r.getExecStatus())) // 预入库 + .collect(Collectors.toList()); + + if (preRecordList.isEmpty()) { + throw new ServiceException("所选入库记录均已完成,无需重复入库"); + } + + /* ================== 3. ★ 校验:一键入库数量 ≤ 计划交货数量 ================== */ + // 按供应计划ID聚合「本次完成入库数量」 + Map finishQtyMap = new HashMap<>(); + + for (RkRecord record : preRecordList) { + + if (record.getGysJhId() == null || record.getRealQty() == null) { + continue; + } + + finishQtyMap.merge( + record.getGysJhId(), + record.getRealQty(), + BigDecimal::add + ); + } + + // 只和「计划交货数量」做校验 + for (Map.Entry entry : finishQtyMap.entrySet()) { + + Long gysJhId = entry.getKey(); + BigDecimal finishQty = entry.getValue(); + + GysJh gysJh = gysJhMapper.selectGysJhById(gysJhId); + if (gysJh == null) { + throw new ServiceException("供应计划不存在,ID:" + gysJhId); + } + + BigDecimal planQty = gysJh.getJhQty(); + if (planQty != null && finishQty.compareTo(planQty) > 0) { + throw new ServiceException( + "一键入库数量超出供应计划数量,物料号:" + gysJh.getWlNo() + + ",计划数量:" + planQty + + ",本次完成入库:" + finishQty + ); + } + } + + /* ================== 4. 状态推进 ================== */ + + // ① rk_record.exec_status = 1 + List preRecordIds = preRecordList.stream() + .map(RkRecord::getId) + .collect(Collectors.toList()); + + rkRecordMapper.updateExecStatusByIds(preRecordIds, "1"); + + // ② rk_info.exec_status = 1 + List rkInfoIds = preRecordList.stream() + .map(RkRecord::getRkInfoId) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + + if (!rkInfoIds.isEmpty()) { + rkInfoMapper.updateExecStatusByIds(rkInfoIds, "1"); + } + + // ③ rk_bill.exec_status = 1 + List billNos = preRecordList.stream() + .map(RkRecord::getBillNo) + .filter(StringUtils::isNotBlank) + .distinct() + .collect(Collectors.toList()); + + for (String billNo : billNos) { + rkBillMapper.updateExecStatusByBillNo(billNo, "1"); + } + + /* ================== 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; + } + + BigDecimal planQty = gysJh.getJhQty(); + BigDecimal realQty = gysJh.getRealQty(); + + if (realQty == null || realQty.compareTo(BigDecimal.ZERO) == 0) { + gysJhMapper.updateStatusById(gysJhId, "0"); // 未到货 + } else if (planQty != null && realQty.compareTo(planQty) >= 0) { + gysJhMapper.updateStatusById(gysJhId, "1"); // 已入库 + } else { + gysJhMapper.updateStatusById(gysJhId, "2"); // 部分入库 + } + } + + return preRecordIds.size(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int rollbackOut(List recordIds) { + + if (recordIds == null || recordIds.isEmpty()) { + throw new ServiceException("撤销出库记录ID不能为空"); + } + + // ================== 1. 查询出库记录 ================== + List recordList = rkRecordMapper.selectRkRecordByIds(recordIds); + if (recordList == null || recordList.isEmpty()) { + throw new ServiceException("出库记录不存在"); + } + + // 只允许撤销:已出库的出库记录 + for (RkRecord record : recordList) { + if (!"1".equals(record.getBizType())) { + throw new ServiceException("存在非出库记录,禁止撤销"); + } + if (!"1".equals(record.getExecStatus())) { + throw new ServiceException("只能撤销已出库记录"); + } + } + + // ================== 2. 回滚库存 rk_info ================== + for (RkRecord record : recordList) { + + Long rkInfoId = record.getRkInfoId(); + BigDecimal outQty = record.getRealQty(); + + if (rkInfoId == null || outQty == null) { + throw new ServiceException("出库记录数据不完整,无法撤销"); + } + + RkInfo info = rkInfoMapper.selectRkInfoById(rkInfoId); + if (info == null) { + throw new ServiceException("库存不存在,ID:" + rkInfoId); + } + + BigDecimal oldQty = info.getRealQty(); + BigDecimal newQty = oldQty.add(outQty); + + // ① 补回库存数量 + rkInfoMapper.updateRealQtyById(rkInfoId, newQty); + + // ② 如果原库存为 0,说明 is_chuku=1,必须回退 + if (oldQty.compareTo(BigDecimal.ZERO) == 0) { + rkInfoMapper.updateIsChukuById(rkInfoId, "0"); + } + } + + // ================== 3. 回退出库记录状态 ================== + rkRecordMapper.updateExecStatusByIds(recordIds, "0"); + + // ================== 4. 回退主单 rk_bill ================== List billNos = recordList.stream() .map(RkRecord::getBillNo) .filter(StringUtils::isNotBlank) @@ -186,62 +616,142 @@ public class RkRecordServiceImpl implements IRkRecordService @Override @Transactional(rollbackFor = Exception.class) - public int finishIn(List recordIds) { + public int finishOut(List recordIds) { if (recordIds == null || recordIds.isEmpty()) { - throw new ServiceException("入库记录ID不能为空"); + throw new ServiceException("出库记录ID不能为空"); } - // 1️⃣ 查询 rk_record - List recordList = - rkRecordMapper.selectRkRecordByIds(recordIds); - + // ================== 1. 查询 rk_record ================== + List recordList = rkRecordMapper.selectRkRecordByIds(recordIds); if (recordList == null || recordList.isEmpty()) { - throw new ServiceException("入库记录不存在"); + throw new ServiceException("出库记录不存在"); } - // ================== ① 只保留「预入库」记录 ================== - List preRecordList = recordList.stream() - .filter(r -> "0".equals(r.getExecStatus())) + // ================== 2. 只保留「预出库」记录 ================== + List preOutList = recordList.stream() + .filter(r -> "1".equals(r.getBizType())) // 出库 + .filter(r -> "0".equals(r.getExecStatus())) // 预出库 .collect(Collectors.toList()); - // ================== ② 如果没有可完成的数据,才报错 ================== - if (preRecordList.isEmpty()) { - throw new ServiceException("所选入库记录均已完成,无需重复入库"); + if (preOutList.isEmpty()) { + throw new ServiceException("所选出库记录均已完成出库"); } - // ================== ③ 后续逻辑全部基于 preRecordList ================== + // ================== 3. 库存校验 + 扣减 ================== + for (RkRecord record : preOutList) { - List preRecordIds = preRecordList.stream() + Long rkInfoId = record.getRkInfoId(); + BigDecimal outQty = record.getRealQty(); + + if (rkInfoId == null || outQty == null) { + throw new ServiceException("出库记录数据不完整"); + } + + RkInfo info = rkInfoMapper.selectRkInfoById(rkInfoId); + if (info == null) { + throw new ServiceException("库存不存在,ID:" + rkInfoId); + } + + BigDecimal remainQty = info.getRealQty(); + if (remainQty.compareTo(outQty) < 0) { + throw new ServiceException( + "库存不足,库存ID:" + rkInfoId + + ",当前库存:" + remainQty + + ",出库数量:" + outQty + ); + } + + BigDecimal newQty = remainQty.subtract(outQty); + + // 扣库存 + rkInfoMapper.updateRealQtyById(rkInfoId, newQty); + + // 同步出库状态 + if (newQty.compareTo(BigDecimal.ZERO) == 0) { + rkInfoMapper.updateIsChukuById(rkInfoId, "1"); + } + } + + // ================== 4. 推进状态 ================== + + // ① rk_record.exec_status = 1 + List preOutIds = preOutList.stream() .map(RkRecord::getId) .collect(Collectors.toList()); + rkRecordMapper.updateExecStatusByIds(preOutIds, "1"); - // 2️⃣ rk_record.exec_status = 1 - rkRecordMapper.updateExecStatusByIds(preRecordIds, "1"); - - // 3️⃣ rk_info.exec_status = 1 - List rkInfoIds = preRecordList.stream() + // ② rk_info.exec_status = 1 + List rkInfoIds = preOutList.stream() .map(RkRecord::getRkInfoId) .filter(Objects::nonNull) .distinct() .collect(Collectors.toList()); - if (!rkInfoIds.isEmpty()) { rkInfoMapper.updateExecStatusByIds(rkInfoIds, "1"); } - // 4️⃣ rk_bill.exec_status = 1 - List billNos = preRecordList.stream() + // ③ rk_bill.exec_status = 1 + List billNos = preOutList.stream() .map(RkRecord::getBillNo) .filter(StringUtils::isNotBlank) .distinct() .collect(Collectors.toList()); - for (String billNo : billNos) { rkBillMapper.updateExecStatusByBillNo(billNo, "1"); } - return preRecordIds.size(); + return preOutIds.size(); } + @Override + @Transactional(rollbackFor = Exception.class) + public int deletePreOutRecords(Long[] ids) { + + if (ids == null || ids.length == 0) { + return 0; + } + + // ================== 1. 查询 record ================== + List records = + rkRecordMapper.selectRkRecordByIds(Arrays.asList(ids)); + + if (records == null || records.isEmpty()) { + return 0; + } + + // ================== 2. 严格校验:只能删「预出库」 ================== + boolean hasInvalid = records.stream().anyMatch(r -> + !"1".equals(r.getBizType()) // 1 = 出库 + || !"0".equals(r.getExecStatus()) // 0 = 预出库 + ); + + if (hasInvalid) { + throw new ServiceException("仅允许删除预出库记录"); + } + + // ================== 3. 删除 rk_record ================== + int deleteCount = rkRecordMapper.deleteRkRecordByIds(ids); + + // ================== 4. 维护 bill 状态 ================== + Set billNos = records.stream() + .map(RkRecord::getBillNo) + .filter(StringUtils::isNotBlank) + .collect(Collectors.toSet()); + + for (String billNo : billNos) { + // 该单据下是否还存在预出库记录 + int remainPreOut = + rkRecordMapper.countPreOutByBillNo(billNo); + + if (remainPreOut == 0) { + // 没有任何预出库记录了,单据可视情况回退或推进 + rkBillMapper.updateExecStatusByBillNo(billNo, "1"); + } + } + + return deleteCount; + } + + } diff --git a/src/main/resources/mybatis/wisdom/GysJhMapper.xml b/src/main/resources/mybatis/wisdom/GysJhMapper.xml index be2dd75..d1b2b2d 100644 --- a/src/main/resources/mybatis/wisdom/GysJhMapper.xml +++ b/src/main/resources/mybatis/wisdom/GysJhMapper.xml @@ -1,38 +1,66 @@ + PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - select id, index_no, xj, xm_no, xm_ms, wl_no, wl_ms, gys_no, gys_mc, jh_amt, ht_dj, sap_no, xh, jh_qty, ht_qty, dw, status,entity_id, remark, create_by, create_time, update_by, update_time, is_delete from gys_jh + select + id, + index_no, + xj, + xm_no, + xm_ms, + wl_no, + wl_ms, + gys_no, + gys_mc, + jh_amt, + ht_dj, + sap_no, + xh, + jh_qty, + real_qty, + ht_qty, + dw, + status, + entity_id, + remark, + create_by, + create_time, + update_by, + update_time, + is_delete + from gys_jh - + - WHERE sap_no = #{sapNo} - - - - - insert into gys_jh @@ -130,6 +134,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" sap_no, xh, jh_qty, + + real_qty, ht_qty, dw, status, @@ -140,7 +146,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" update_by, update_time, is_delete, - + #{indexNo}, #{xj}, @@ -155,6 +161,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{sapNo}, #{xh}, #{jhQty}, + + #{realQty}, #{htQty}, #{dw}, #{status}, @@ -165,9 +173,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{updateBy}, #{updateTime}, #{isDelete}, - + + + UPDATE gys_jh + SET real_qty = real_qty + #{realQty} + WHERE id = #{gysJhId} + update gys_jh @@ -184,6 +197,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" sap_no = #{sapNo}, xh = #{xh}, jh_qty = #{jhQty}, + + real_qty = #{realQty}, ht_qty = #{htQty}, dw = #{dw}, status = #{status}, @@ -198,6 +213,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where id = #{id} + UPDATE gys_jh SET status = #{status} @@ -233,7 +249,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - delete from gys_jh where id in + delete from gys_jh where id in #{id} @@ -246,10 +262,53 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND is_delete = '0' + + + UPDATE gys_jh SET dw = #{dw} WHERE id = #{id} - + + UPDATE gys_jh + SET real_qty = real_qty - #{rollbackQty} + WHERE id = #{jhId} + + + + + UPDATE gys_jh + SET status = #{status} + WHERE id = #{jhId} + + + + UPDATE gys_jh + SET + real_qty = #{realQty}, + update_time = NOW() + WHERE id = #{gysJhId} + + + diff --git a/src/main/resources/mybatis/wisdom/MoveRecordMapper.xml b/src/main/resources/mybatis/wisdom/MoveRecordMapper.xml index c026414..ce0b78f 100644 --- a/src/main/resources/mybatis/wisdom/MoveRecordMapper.xml +++ b/src/main/resources/mybatis/wisdom/MoveRecordMapper.xml @@ -9,20 +9,31 @@ - + - + + + + + + + + + + + + @@ -43,7 +54,9 @@ SELECT id, rk_info_id, + new_rk_info_id, entity_id, + real_qty, from_cangku, from_pcode, from_tray_code, @@ -68,7 +81,9 @@ keyProperty="id"> INSERT INTO move_record ( rk_info_id, + new_rk_info_id, entity_id, + real_qty, from_cangku, from_pcode, from_tray_code, @@ -85,7 +100,9 @@ is_delete ) VALUES ( #{rkInfoId}, + #{newRkInfoId}, #{entityId}, + #{realQty}, #{fromCangku}, #{fromPcode}, #{fromTrayCode}, @@ -120,6 +137,7 @@ LEFT JOIN warehouse_info wi1 ON mr.from_cangku = wi1.warehouse_code LEFT JOIN warehouse_info wi2 ON mr.to_cangku = wi2.warehouse_code + mr.is_delete = 0 AND mr.entity_id = #{entityId} @@ -153,7 +171,9 @@ UPDATE move_record rk_info_id = #{rkInfoId}, + new_rk_info_id = #{newRkInfoId}, entity_id = #{entityId}, + real_qty = #{realQty}, from_cangku = #{fromCangku}, from_pcode = #{fromPcode}, from_tray_code = #{fromTrayCode}, diff --git a/src/main/resources/mybatis/wisdom/RkBillMapper.xml b/src/main/resources/mybatis/wisdom/RkBillMapper.xml index d2e7453..af1bb47 100644 --- a/src/main/resources/mybatis/wisdom/RkBillMapper.xml +++ b/src/main/resources/mybatis/wisdom/RkBillMapper.xml @@ -7,6 +7,7 @@ + @@ -27,9 +28,22 @@ + + + + + + + + + + + + + @@ -45,15 +59,26 @@ rb.exec_status, rb.operator, rb.team_code, + + rb.borrow_time, + rb.return_time, + rb.xm_no_ck, + rb.xm_ms_ck, + rb.remark, rb.is_delivery, rb.is_delete, + rb.create_by, + rb.create_time, + rb.update_by, + rb.update_time, mt.type_name AS wl_type_name, COALESCE(sit.type_name, sot.type_name) AS operation_type_name, su.nick_name AS operator_name, wh.warehouse_name AS warehouse_name, - wh.parent_warehouse_name AS parent_warehouse_name + wh.parent_warehouse_name AS parent_warehouse_name, + ct.team_name AS team_name FROM rk_bill rb LEFT JOIN warehouse_info wh @@ -70,6 +95,9 @@ AND (sot.is_delete = '0' OR sot.is_delete IS NULL) LEFT JOIN sys_user su ON rb.operator = su.user_id + LEFT JOIN construction_team ct + ON rb.team_code = ct.team_code + AND ct.is_delete = '0' @@ -93,20 +121,23 @@ AND rb.operation_type = #{operationType} - AND rb.biz_type IN - + #{bt} - - AND rb.operation_time = #{operationTime} + + AND rb.borrow_time = #{borrowTime} + + + + AND rb.return_time = #{returnTime} + + + + AND rb.xm_no_ck = #{xmNoCk} @@ -147,9 +178,7 @@ WHERE rb.id = #{id} - SELECT id, wl_type, @@ -161,6 +190,10 @@ exec_status, operator, team_code, + borrow_time, + return_time, + xm_no_ck, + xm_ms_ck, is_delivery, remark, create_by, @@ -187,6 +220,11 @@ operation_type, operation_time, operator, + team_code, + borrow_time, + return_time, + xm_no_ck, + xm_ms_ck, exec_status, is_delivery, remark, @@ -201,6 +239,11 @@ #{operationType}, #{operationTime}, #{operator}, + #{teamCode}, + #{borrowTime}, + #{returnTime}, + #{xmNoCk}, + #{xmMsCk}, #{execStatus}, #{isDelivery}, #{remark}, @@ -220,6 +263,10 @@ operation_type = #{operationType}, biz_type = #{bizType}, operation_time = #{operationTime}, + borrow_time = #{borrowTime}, + return_time = #{returnTime}, + xm_no_ck = #{xmNoCk}, + xm_ms_ck = #{xmMsCk}, exec_status = #{execStatus}, operator = #{operator}, team_code = #{teamCode}, @@ -230,12 +277,19 @@ WHERE id = #{id} + UPDATE rk_bill SET exec_status = #{execStatus} WHERE bill_no = #{billNo} + + UPDATE rk_bill + SET exec_status = #{execStatus} + WHERE bill_no = #{billNo} + + DELETE FROM rk_bill WHERE id = #{id} diff --git a/src/main/resources/mybatis/wisdom/RkInfoMapper.xml b/src/main/resources/mybatis/wisdom/RkInfoMapper.xml index df39edd..f8f7c83 100644 --- a/src/main/resources/mybatis/wisdom/RkInfoMapper.xml +++ b/src/main/resources/mybatis/wisdom/RkInfoMapper.xml @@ -103,6 +103,8 @@ ri.exec_status = 1 + AND ri.is_chuku = 0 + AND ri.is_delete = 0 AND ri.operation_type LIKE CONCAT('%', #{operationType}, '%') @@ -159,16 +161,44 @@ WHERE ri.id = #{id} + + + + + + DELETE FROM rk_info WHERE id = #{id} - + DELETE FROM rk_info WHERE id IN - - #{item} + + #{id} @@ -277,4 +307,27 @@ + + update rk_info + set real_qty = #{realQty}, + update_time = now() + where id = #{id} + + + + update rk_info + set is_chuku = #{isChuku}, + update_time = now() + where id = #{id} + + + + UPDATE rk_info + SET is_borrowed = #{isBorrowed}, + return_time = #{returnTime}, + update_time = NOW() + WHERE id = #{id} + + + diff --git a/src/main/resources/mybatis/wisdom/RkRecordMapper.xml b/src/main/resources/mybatis/wisdom/RkRecordMapper.xml index d240406..affb879 100644 --- a/src/main/resources/mybatis/wisdom/RkRecordMapper.xml +++ b/src/main/resources/mybatis/wisdom/RkRecordMapper.xml @@ -119,9 +119,21 @@ @@ -212,7 +224,7 @@ WHERE rr.bill_no = #{billNo} AND (rr.is_delete = '0' OR rr.is_delete = 0 OR rr.is_delete IS NULL) - ORDER BY rr.operation_time ASC + ORDER BY rr.exec_status = '0' DESC, rr.operation_time DESC + + + + + + +