feat:出库单修改功能
This commit is contained in:
@@ -70,10 +70,10 @@ public class IostorInvController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/update")
|
@PutMapping("/update")
|
||||||
@Operation(summary = "更新出入库单主表")
|
@Operation(summary = "更新出库单主表及明细")
|
||||||
@PreAuthorize("@ss.hasPermission('wms:iostor-inv:update')")
|
@PreAuthorize("@ss.hasPermission('wms:iostor-inv:update')")
|
||||||
public CommonResult<Boolean> updateIostorInv(@Valid @RequestBody IostorInvSaveReqVO updateReqVO) {
|
public CommonResult<Boolean> updateIostorInv(@Valid @RequestBody IostorInvUpdateReqVO reqVO) {
|
||||||
iostorInvService.updateIostorInv(updateReqVO);
|
iostorInvService.updateOutbound(reqVO);
|
||||||
return success(true);
|
return success(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,9 +99,20 @@ public class IostorInvController {
|
|||||||
@Operation(summary = "获得出入库单主表")
|
@Operation(summary = "获得出入库单主表")
|
||||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||||
@PreAuthorize("@ss.hasPermission('wms:iostor-inv:query')")
|
@PreAuthorize("@ss.hasPermission('wms:iostor-inv:query')")
|
||||||
public CommonResult<IostorInvRespVO> getIostorInv(@RequestParam("id") String id) {
|
public CommonResult<IostorInvRespVO> getIostorInv(@RequestParam("id") Long id) {
|
||||||
IostorInvDO iostorInv = iostorInvService.getIostorInv(id);
|
IostorInvDO iostorInv = iostorInvService.getIostorInv(id);
|
||||||
return success(BeanUtils.toBean(iostorInv, IostorInvRespVO.class));
|
IostorInvRespVO respVO = BeanUtils.toBean(iostorInv, IostorInvRespVO.class);
|
||||||
|
respVO.setDetails(iostorInvService.getIostorInvDetails(id));
|
||||||
|
return success(respVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/details")
|
||||||
|
@Operation(summary = "获得出入库单全部明细")
|
||||||
|
@Parameter(name = "iostorinvId", description = "出入库单主表标识", required = true)
|
||||||
|
@PreAuthorize("@ss.hasPermission('wms:iostor-inv:query')")
|
||||||
|
public CommonResult<List<IostorInvDetailRespVO>> getIostorInvDetails(
|
||||||
|
@RequestParam("iostorinvId") Long iostorinvId) {
|
||||||
|
return success(iostorInvService.getIostorInvDetails(iostorinvId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/page")
|
@GetMapping("/page")
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ public class IostorInvCreateReqVO {
|
|||||||
@NotEmpty(message = "单据类型不能为空")
|
@NotEmpty(message = "单据类型不能为空")
|
||||||
private String billType;
|
private String billType;
|
||||||
|
|
||||||
|
@Schema(description = "单据状态", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotEmpty(message = "单据状态不能为空")
|
||||||
|
private String billStatus;
|
||||||
|
|
||||||
@Schema(description = "仓库标识", requiredMode = Schema.RequiredMode.REQUIRED)
|
@Schema(description = "仓库标识", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
@NotEmpty(message = "仓库标识不能为空")
|
@NotEmpty(message = "仓库标识不能为空")
|
||||||
private String storId;
|
private String storId;
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package cn.code.nl.module.wms.controller.admin.iostorinv.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 出入库单编辑明细 Response VO")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class IostorInvDetailRespVO extends IostorInvCreateReqVO.Detail {
|
||||||
|
|
||||||
|
@Schema(description = "物料名称")
|
||||||
|
private String materialName;
|
||||||
|
}
|
||||||
@@ -130,4 +130,7 @@ public class IostorInvRespVO implements VO {
|
|||||||
@ExcelProperty("回传时间")
|
@ExcelProperty("回传时间")
|
||||||
private String uploadTime;
|
private String uploadTime;
|
||||||
|
|
||||||
|
@Schema(description = "出入库单明细")
|
||||||
|
private List<IostorInvDetailRespVO> details;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package cn.code.nl.module.wms.controller.admin.iostorinv.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 出库单修改 Request VO")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class IostorInvUpdateReqVO extends IostorInvCreateReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "出入库单主表标识", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotNull(message = "出入库单主表标识不能为空")
|
||||||
|
private Long iostorinvId;
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import org.apache.ibatis.annotations.Mapper;
|
|||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
import cn.code.nl.module.wms.controller.admin.iostorinv.vo.AvailableInventoryPageReqVO;
|
import cn.code.nl.module.wms.controller.admin.iostorinv.vo.AvailableInventoryPageReqVO;
|
||||||
import cn.code.nl.module.wms.controller.admin.iostorinv.vo.AvailableInventoryRespVO;
|
import cn.code.nl.module.wms.controller.admin.iostorinv.vo.AvailableInventoryRespVO;
|
||||||
|
import cn.code.nl.module.wms.controller.admin.iostorinv.vo.IostorInvDetailRespVO;
|
||||||
import cn.code.nl.module.wms.controller.admin.iostorinvdtl.vo.*;
|
import cn.code.nl.module.wms.controller.admin.iostorinvdtl.vo.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,6 +48,9 @@ public interface IostorinvDtlMapper extends BaseMapperX<IostorinvDtlDO> {
|
|||||||
List<AvailableInventoryRespVO> selectAvailableInventoryByVehicleCodesForUpdate(
|
List<AvailableInventoryRespVO> selectAvailableInventoryByVehicleCodesForUpdate(
|
||||||
@Param("storId") String storId, @Param("vehicleCodes") Collection<String> vehicleCodes);
|
@Param("storId") String storId, @Param("vehicleCodes") Collection<String> vehicleCodes);
|
||||||
|
|
||||||
|
/** 查询指定出入库单的编辑明细,并带出物料名称。 */
|
||||||
|
List<IostorInvDetailRespVO> selectEditDetails(@Param("iostorinvId") Long iostorinvId);
|
||||||
|
|
||||||
default PageResult<IostorinvDtlDO> selectPage(IostorinvDtlPageReqVO reqVO) {
|
default PageResult<IostorinvDtlDO> selectPage(IostorinvDtlPageReqVO reqVO) {
|
||||||
return selectPage(reqVO, new LambdaQueryWrapperX<IostorinvDtlDO>()
|
return selectPage(reqVO, new LambdaQueryWrapperX<IostorinvDtlDO>()
|
||||||
.eqIfPresent(IostorinvDtlDO::getIostorinvId, reqVO.getIostorinvId())
|
.eqIfPresent(IostorinvDtlDO::getIostorinvId, reqVO.getIostorinvId())
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.util.*;
|
|||||||
import jakarta.validation.*;
|
import jakarta.validation.*;
|
||||||
import cn.code.nl.module.wms.controller.admin.iostorinv.vo.*;
|
import cn.code.nl.module.wms.controller.admin.iostorinv.vo.*;
|
||||||
import cn.code.nl.module.wms.dal.dataobject.iostorinv.IostorInvDO;
|
import cn.code.nl.module.wms.dal.dataobject.iostorinv.IostorInvDO;
|
||||||
|
import cn.code.nl.module.wms.dal.dataobject.iostorinvdtl.IostorinvDtlDO;
|
||||||
import cn.code.nl.framework.common.pojo.PageResult;
|
import cn.code.nl.framework.common.pojo.PageResult;
|
||||||
import cn.code.nl.framework.common.pojo.PageParam;
|
import cn.code.nl.framework.common.pojo.PageParam;
|
||||||
|
|
||||||
@@ -53,6 +54,11 @@ public interface IostorInvService {
|
|||||||
*/
|
*/
|
||||||
void updateIostorInv(@Valid IostorInvSaveReqVO updateReqVO);
|
void updateIostorInv(@Valid IostorInvSaveReqVO updateReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新出库单主表并替换全部明细。
|
||||||
|
*/
|
||||||
|
void updateOutbound(@Valid IostorInvUpdateReqVO reqVO);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除出入库单主表
|
* 删除出入库单主表
|
||||||
*
|
*
|
||||||
@@ -73,7 +79,12 @@ public interface IostorInvService {
|
|||||||
* @param id 编号
|
* @param id 编号
|
||||||
* @return 出入库单主表
|
* @return 出入库单主表
|
||||||
*/
|
*/
|
||||||
IostorInvDO getIostorInv(String id);
|
IostorInvDO getIostorInv(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得出入库单的全部明细。
|
||||||
|
*/
|
||||||
|
List<IostorInvDetailRespVO> getIostorInvDetails(Long id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得出入库单主表分页
|
* 获得出入库单主表分页
|
||||||
|
|||||||
@@ -90,16 +90,24 @@ public class IostorInvServiceImpl implements IostorInvService {
|
|||||||
iostorInv.setBillType(reqVO.getBillType());
|
iostorInv.setBillType(reqVO.getBillType());
|
||||||
iostorInv.setBizDate(reqVO.getBizDate());
|
iostorInv.setBizDate(reqVO.getBizDate());
|
||||||
iostorInv.setStorId(reqVO.getStorId());
|
iostorInv.setStorId(reqVO.getStorId());
|
||||||
iostorInv.setBillStatus("10");
|
iostorInv.setBillStatus(reqVO.getBillStatus());
|
||||||
iostorInv.setRemark(reqVO.getRemark());
|
iostorInv.setRemark(reqVO.getRemark());
|
||||||
iostorInv.setDetailCount(reqVO.getDetails().size());
|
iostorInv.setDetailCount(reqVO.getDetails().size());
|
||||||
iostorInv.setTotalWeight(totalWeight);
|
iostorInv.setTotalWeight(totalWeight);
|
||||||
iostorInvMapper.insert(iostorInv);
|
iostorInvMapper.insert(iostorInv);
|
||||||
|
|
||||||
for (int index = 0; index < reqVO.getDetails().size(); index++) {
|
insertOutboundDetails(iostorInv.getIostorinvId(), reqVO.getBillStatus(),
|
||||||
IostorInvCreateReqVO.Detail detail = reqVO.getDetails().get(index);
|
reqVO.getDetails(), inventorySnapshot);
|
||||||
|
return iostorInv.getIostorinvId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertOutboundDetails(Long iostorinvId, String billStatus,
|
||||||
|
List<IostorInvCreateReqVO.Detail> details,
|
||||||
|
Map<Long, AvailableInventoryRespVO> inventorySnapshot) {
|
||||||
|
for (int index = 0; index < details.size(); index++) {
|
||||||
|
IostorInvCreateReqVO.Detail detail = details.get(index);
|
||||||
IostorinvDtlDO detailDO = new IostorinvDtlDO();
|
IostorinvDtlDO detailDO = new IostorinvDtlDO();
|
||||||
detailDO.setIostorinvId(iostorInv.getIostorinvId());
|
detailDO.setIostorinvId(iostorinvId);
|
||||||
detailDO.setSeqNo(index + 1);
|
detailDO.setSeqNo(index + 1);
|
||||||
AvailableInventoryRespVO inventory = inventorySnapshot.get(detail.getGroupId());
|
AvailableInventoryRespVO inventory = inventorySnapshot.get(detail.getGroupId());
|
||||||
detailDO.setMaterialCode(inventory == null ? detail.getMaterialCode() : inventory.getMaterialCode());
|
detailDO.setMaterialCode(inventory == null ? detail.getMaterialCode() : inventory.getMaterialCode());
|
||||||
@@ -108,7 +116,7 @@ public class IostorInvServiceImpl implements IostorInvService {
|
|||||||
detailDO.setPlanQty(detail.getPlanQty());
|
detailDO.setPlanQty(detail.getPlanQty());
|
||||||
detailDO.setUnassignQty(detail.getPlanQty());
|
detailDO.setUnassignQty(detail.getPlanQty());
|
||||||
detailDO.setAssignQty(BigDecimal.ZERO);
|
detailDO.setAssignQty(BigDecimal.ZERO);
|
||||||
detailDO.setBillStatus("10");
|
detailDO.setBillStatus(billStatus);
|
||||||
detailDO.setQtyUnitId(inventory == null ? detail.getQtyUnitId() : inventory.getQtyUnitId());
|
detailDO.setQtyUnitId(inventory == null ? detail.getQtyUnitId() : inventory.getQtyUnitId());
|
||||||
detailDO.setSourceBillCode(inventory == null ? detail.getSourceBillCode() : inventory.getExtCode());
|
detailDO.setSourceBillCode(inventory == null ? detail.getSourceBillCode() : inventory.getExtCode());
|
||||||
detailDO.setSourceBillType(inventory == null ? detail.getSourceBillType() : inventory.getExtType());
|
detailDO.setSourceBillType(inventory == null ? detail.getSourceBillType() : inventory.getExtType());
|
||||||
@@ -116,7 +124,6 @@ public class IostorInvServiceImpl implements IostorInvService {
|
|||||||
detailDO.setRemark(detail.getRemark());
|
detailDO.setRemark(detail.getRemark());
|
||||||
iostorinvDtlMapper.insert(detailDO);
|
iostorinvDtlMapper.insert(detailDO);
|
||||||
}
|
}
|
||||||
return iostorInv.getIostorinvId();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<Long, AvailableInventoryRespVO> validateAndLockInventory(IostorInvCreateReqVO reqVO) {
|
private Map<Long, AvailableInventoryRespVO> validateAndLockInventory(IostorInvCreateReqVO reqVO) {
|
||||||
@@ -199,6 +206,32 @@ public class IostorInvServiceImpl implements IostorInvService {
|
|||||||
iostorInvMapper.updateById(updateObj);
|
iostorInvMapper.updateById(updateObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void updateOutbound(IostorInvUpdateReqVO reqVO) {
|
||||||
|
validateIostorInvExists(String.valueOf(reqVO.getIostorinvId()));
|
||||||
|
Map<Long, AvailableInventoryRespVO> inventorySnapshot = validateAndLockInventory(reqVO);
|
||||||
|
BigDecimal totalWeight = reqVO.getDetails().stream()
|
||||||
|
.map(IostorInvCreateReqVO.Detail::getPlanQty)
|
||||||
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
|
||||||
|
IostorInvDO updateObj = new IostorInvDO();
|
||||||
|
updateObj.setIostorinvId(reqVO.getIostorinvId());
|
||||||
|
updateObj.setBillType(reqVO.getBillType());
|
||||||
|
updateObj.setBillStatus(reqVO.getBillStatus());
|
||||||
|
updateObj.setBizDate(reqVO.getBizDate());
|
||||||
|
updateObj.setStorId(reqVO.getStorId());
|
||||||
|
updateObj.setRemark(reqVO.getRemark());
|
||||||
|
updateObj.setDetailCount(reqVO.getDetails().size());
|
||||||
|
updateObj.setTotalWeight(totalWeight);
|
||||||
|
iostorInvMapper.updateById(updateObj);
|
||||||
|
|
||||||
|
iostorinvDtlMapper.delete(new LambdaQueryWrapperX<IostorinvDtlDO>()
|
||||||
|
.eq(IostorinvDtlDO::getIostorinvId, reqVO.getIostorinvId()));
|
||||||
|
insertOutboundDetails(reqVO.getIostorinvId(), reqVO.getBillStatus(),
|
||||||
|
reqVO.getDetails(), inventorySnapshot);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteIostorInv(String id) {
|
public void deleteIostorInv(String id) {
|
||||||
// 校验存在
|
// 校验存在
|
||||||
@@ -221,10 +254,15 @@ public class IostorInvServiceImpl implements IostorInvService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IostorInvDO getIostorInv(String id) {
|
public IostorInvDO getIostorInv(Long id) {
|
||||||
return iostorInvMapper.selectById(id);
|
return iostorInvMapper.selectById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<IostorInvDetailRespVO> getIostorInvDetails(Long id) {
|
||||||
|
return iostorinvDtlMapper.selectEditDetails(id);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageResult<IostorInvDO> getIostorInvPage(IostorInvPageReqVO pageReqVO) {
|
public PageResult<IostorInvDO> getIostorInvPage(IostorInvPageReqVO pageReqVO) {
|
||||||
PageResult<IostorInvDO> pageResult = iostorInvMapper.selectPage(pageReqVO);
|
PageResult<IostorInvDO> pageResult = iostorInvMapper.selectPage(pageReqVO);
|
||||||
|
|||||||
@@ -92,4 +92,25 @@
|
|||||||
FOR UPDATE
|
FOR UPDATE
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectEditDetails"
|
||||||
|
resultType="cn.code.nl.module.wms.controller.admin.iostorinv.vo.IostorInvDetailRespVO">
|
||||||
|
SELECT d.material_code,
|
||||||
|
d.material_id,
|
||||||
|
mb.material_name,
|
||||||
|
d.pcsn,
|
||||||
|
d.plan_qty,
|
||||||
|
d.qty_unit_id,
|
||||||
|
d.source_bill_code,
|
||||||
|
d.source_bill_type,
|
||||||
|
d.source_billdtl_id,
|
||||||
|
d.remark
|
||||||
|
FROM wms_iostorinvdtl d
|
||||||
|
LEFT JOIN base_materialbase mb
|
||||||
|
ON mb.material_id = d.material_id
|
||||||
|
AND mb.deleted = 0
|
||||||
|
WHERE d.iostorinv_id = #{iostorinvId}
|
||||||
|
AND d.deleted = 0
|
||||||
|
ORDER BY d.seq_no, d.iostorinvdtl_id
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@@ -64,10 +64,9 @@ class IostorInvServiceLocalSpringTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldCreateOutboundUsingLockedInventoryIdentityInsteadOfClientUnitAndSource() {
|
void shouldCreateOutboundUsingLockedInventoryIdentityInsteadOfClientUnitAndSource() {
|
||||||
String id = iostorInvService.createOutbound(buildRequest());
|
Long id = iostorInvService.createOutbound(buildRequest());
|
||||||
|
|
||||||
assertNotNull(id);
|
assertNotNull(id);
|
||||||
assertFalse(id.isBlank());
|
|
||||||
jdbcTemplate.queryForObject("SELECT * FROM wms_iostorinv WHERE iostorinv_id = ?", (rs, rowNum) -> {
|
jdbcTemplate.queryForObject("SELECT * FROM wms_iostorinv WHERE iostorinv_id = ?", (rs, rowNum) -> {
|
||||||
assertEquals("OUT-TEST-0001", rs.getString("bill_code"));
|
assertEquals("OUT-TEST-0001", rs.getString("bill_code"));
|
||||||
assertEquals("OUT", rs.getString("io_type"));
|
assertEquals("OUT", rs.getString("io_type"));
|
||||||
@@ -97,12 +96,12 @@ class IostorInvServiceLocalSpringTest {
|
|||||||
assertFalse(details.get(0).detailId().isBlank());
|
assertFalse(details.get(0).detailId().isBlank());
|
||||||
assertFalse(details.get(1).detailId().isBlank());
|
assertFalse(details.get(1).detailId().isBlank());
|
||||||
assertNotEquals(details.get(0).detailId(), details.get(1).detailId());
|
assertNotEquals(details.get(0).detailId(), details.get(1).detailId());
|
||||||
assertEquals(id, details.get(0).headerId());
|
assertEquals(id.toString(), details.get(0).headerId());
|
||||||
assertEquals(id, details.get(1).headerId());
|
assertEquals(id.toString(), details.get(1).headerId());
|
||||||
assertEquals(new DetailRow(details.get(0).detailId(), id, 1, "MAT-01", "MID-01", "PCSN-01",
|
assertEquals(new DetailRow(details.get(0).detailId(), id.toString(), 1, "MAT-01", "MID-01", "PCSN-01",
|
||||||
new BigDecimal("12.500"), BigDecimal.ZERO.setScale(3), new BigDecimal("12.500"),
|
new BigDecimal("12.500"), BigDecimal.ZERO.setScale(3), new BigDecimal("12.500"),
|
||||||
"DB-KG", "数据库千克", "DB-SRC-01", "DB-TYPE", "DB-DTL-01", "明细一"), details.get(0));
|
"DB-KG", "数据库千克", "DB-SRC-01", "DB-TYPE", "DB-DTL-01", "明细一"), details.get(0));
|
||||||
assertEquals(new DetailRow(details.get(1).detailId(), id, 2, "MAT-02", "MID-02", "PCSN-02",
|
assertEquals(new DetailRow(details.get(1).detailId(), id.toString(), 2, "MAT-02", "MID-02", "PCSN-02",
|
||||||
new BigDecimal("7.250"), BigDecimal.ZERO.setScale(3), new BigDecimal("7.250"),
|
new BigDecimal("7.250"), BigDecimal.ZERO.setScale(3), new BigDecimal("7.250"),
|
||||||
"DB-KG", "数据库千克", "DB-SRC-02", "DB-TYPE", "DB-DTL-02", "明细二"), details.get(1));
|
"DB-KG", "数据库千克", "DB-SRC-02", "DB-TYPE", "DB-DTL-02", "明细二"), details.get(1));
|
||||||
}
|
}
|
||||||
@@ -218,6 +217,7 @@ class IostorInvServiceLocalSpringTest {
|
|||||||
private IostorInvCreateReqVO buildRequest() {
|
private IostorInvCreateReqVO buildRequest() {
|
||||||
IostorInvCreateReqVO request = new IostorInvCreateReqVO();
|
IostorInvCreateReqVO request = new IostorInvCreateReqVO();
|
||||||
request.setBillType("销售出库");
|
request.setBillType("销售出库");
|
||||||
|
request.setBillStatus("生成");
|
||||||
request.setStorId("STOR-01");
|
request.setStorId("STOR-01");
|
||||||
request.setBizDate(LocalDateTime.of(2026, 7, 22, 0, 0));
|
request.setBizDate(LocalDateTime.of(2026, 7, 22, 0, 0));
|
||||||
request.setRemark("主表备注");
|
request.setRemark("主表备注");
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export namespace WmsIostorInvApi {
|
|||||||
creator?: string; // 创建者标识
|
creator?: string; // 创建者标识
|
||||||
creatorName?: string; // 创建者名称
|
creatorName?: string; // 创建者名称
|
||||||
createTime?: Dayjs | string; // 创建时间
|
createTime?: Dayjs | string; // 创建时间
|
||||||
|
details?: OutboundDetail[]; // 出入库单明细
|
||||||
disOptid: string; // 分配人
|
disOptid: string; // 分配人
|
||||||
disTime?: Dayjs | string; // 分配时间
|
disTime?: Dayjs | string; // 分配时间
|
||||||
confirmOptid: string; // 确认人
|
confirmOptid: string; // 确认人
|
||||||
@@ -64,12 +65,18 @@ export namespace WmsIostorInvApi {
|
|||||||
/** 出库单新增请求 */
|
/** 出库单新增请求 */
|
||||||
export interface OutboundCreateReq {
|
export interface OutboundCreateReq {
|
||||||
billType: string; // 单据类型
|
billType: string; // 单据类型
|
||||||
|
billStatus: string; // 单据状态
|
||||||
storId: string; // 仓库标识
|
storId: string; // 仓库标识
|
||||||
bizDate: number; // 业务日期(毫秒时间戳)
|
bizDate: number; // 业务日期(毫秒时间戳)
|
||||||
remark?: string; // 备注
|
remark?: string; // 备注
|
||||||
details: OutboundDetail[]; // 出库单明细
|
details: OutboundDetail[]; // 出库单明细
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 出库单修改请求 */
|
||||||
|
export interface OutboundUpdateReq extends OutboundCreateReq {
|
||||||
|
iostorinvId: string;
|
||||||
|
}
|
||||||
|
|
||||||
/** 可用库存 */
|
/** 可用库存 */
|
||||||
export interface AvailableInventory {
|
export interface AvailableInventory {
|
||||||
groupId: number | string; // 组盘记录标识
|
groupId: number | string; // 组盘记录标识
|
||||||
@@ -119,6 +126,13 @@ export function getIostorInv(id: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 查询出入库单全部明细 */
|
||||||
|
export function getIostorInvDetails(iostorinvId: string) {
|
||||||
|
return requestClient.get<WmsIostorInvApi.OutboundDisplayDetail[]>(
|
||||||
|
`/wms/iostor-inv/details?iostorinvId=${encodeURIComponent(iostorinvId)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** 新增出入库单主表 */
|
/** 新增出入库单主表 */
|
||||||
export function createIostorInv(data: WmsIostorInvApi.IostorInv) {
|
export function createIostorInv(data: WmsIostorInvApi.IostorInv) {
|
||||||
return requestClient.post('/wms/iostor-inv/create', data);
|
return requestClient.post('/wms/iostor-inv/create', data);
|
||||||
@@ -129,6 +143,11 @@ export function updateIostorInv(data: WmsIostorInvApi.IostorInv) {
|
|||||||
return requestClient.put('/wms/iostor-inv/update', data);
|
return requestClient.put('/wms/iostor-inv/update', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 修改出库单主表及明细 */
|
||||||
|
export function updateOutbound(data: WmsIostorInvApi.OutboundUpdateReq) {
|
||||||
|
return requestClient.put('/wms/iostor-inv/update', data);
|
||||||
|
}
|
||||||
|
|
||||||
/** 删除出入库单主表 */
|
/** 删除出入库单主表 */
|
||||||
export function deleteIostorInv(id: number) {
|
export function deleteIostorInv(id: number) {
|
||||||
return requestClient.delete(`/wms/iostor-inv/delete?id=${id}`);
|
return requestClient.delete(`/wms/iostor-inv/delete?id=${id}`);
|
||||||
|
|||||||
@@ -9,37 +9,37 @@ import { Button, Input, InputNumber, message, Space, Table } from 'antdv-next';
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
import { useVbenForm } from '#/adapter/form';
|
||||||
import { getSimpleDictDataList } from '#/api/system/dict/data';
|
|
||||||
import { getBsrealStorAttrSimpleList } from '#/api/wms/bsrealstorattr';
|
import { getBsrealStorAttrSimpleList } from '#/api/wms/bsrealstorattr';
|
||||||
import {
|
import {
|
||||||
createOutbound,
|
createOutbound,
|
||||||
getIostorInv,
|
getIostorInv,
|
||||||
updateIostorInv,
|
getIostorInvDetails,
|
||||||
|
updateOutbound,
|
||||||
} from '#/api/wms/iostorinv';
|
} from '#/api/wms/iostorinv';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import { useFormSchema } from '../data';
|
|
||||||
import { summarizeDetails } from './detail-summary';
|
import { summarizeDetails } from './detail-summary';
|
||||||
import InventorySelectComponent from './inventory-select.vue';
|
import InventorySelectComponent from './inventory-select.vue';
|
||||||
import ManualDetailComponent from './manual-detail.vue';
|
import ManualDetailComponent from './manual-detail.vue';
|
||||||
import {
|
import {
|
||||||
buildOutboundPayload,
|
buildOutboundPayload,
|
||||||
filterOutboundBillTypeOptions,
|
|
||||||
mergeOutboundInventory,
|
mergeOutboundInventory,
|
||||||
type OutboundFormDetail,
|
type OutboundFormDetail,
|
||||||
shouldApplyEditResult,
|
shouldApplyEditResult,
|
||||||
shouldClearDetailsForWarehouseChange,
|
shouldClearDetailsForWarehouseChange,
|
||||||
} from './outbound-form';
|
} from './outbound-form';
|
||||||
|
import { DICT_TYPE } from '@vben/constants';
|
||||||
|
import { getDictOptions } from '@vben/hooks';
|
||||||
|
|
||||||
const emit = defineEmits(['success']);
|
const emit = defineEmits(['success']);
|
||||||
const formData = ref<WmsIostorInvApi.IostorInv>();
|
const formData = ref<WmsIostorInvApi.IostorInv>();
|
||||||
const details = ref<OutboundFormDetail[]>([]);
|
const details = ref<OutboundFormDetail[]>([]);
|
||||||
const currentStorId = ref<string>();
|
const currentStorId = ref<string>();
|
||||||
let manualRowSequence = 0;
|
let manualRowSequence = 0;
|
||||||
let billTypeRequest: null | ReturnType<typeof getSimpleDictDataList> = null;
|
|
||||||
let mainModalOpen = false;
|
let mainModalOpen = false;
|
||||||
let mainOpenToken = 0;
|
let mainOpenToken = 0;
|
||||||
let activeEditId: string | undefined;
|
let activeEditId: string | undefined;
|
||||||
|
let suppressWarehouseChange = false;
|
||||||
|
|
||||||
const isEdit = computed(() => Boolean(formData.value?.iostorinvId));
|
const isEdit = computed(() => Boolean(formData.value?.iostorinvId));
|
||||||
const getTitle = computed(() =>
|
const getTitle = computed(() =>
|
||||||
@@ -48,17 +48,6 @@ const getTitle = computed(() =>
|
|||||||
: '出库单新增',
|
: '出库单新增',
|
||||||
);
|
);
|
||||||
|
|
||||||
const [LegacyForm, legacyFormApi] = useVbenForm({
|
|
||||||
commonConfig: {
|
|
||||||
componentProps: { class: 'w-full' },
|
|
||||||
formItemClass: 'col-span-2',
|
|
||||||
labelWidth: 80,
|
|
||||||
},
|
|
||||||
layout: 'horizontal',
|
|
||||||
schema: useFormSchema(),
|
|
||||||
showDefaultActions: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
async function handleWarehouseChange(nextStorId?: string) {
|
async function handleWarehouseChange(nextStorId?: string) {
|
||||||
const shouldClear = shouldClearDetailsForWarehouseChange(
|
const shouldClear = shouldClearDetailsForWarehouseChange(
|
||||||
currentStorId.value,
|
currentStorId.value,
|
||||||
@@ -78,7 +67,7 @@ const [OutboundForm, outboundFormApi] = useVbenForm({
|
|||||||
labelWidth: 88,
|
labelWidth: 88,
|
||||||
},
|
},
|
||||||
handleValuesChange: (values) => {
|
handleValuesChange: (values) => {
|
||||||
if (Object.hasOwn(values, 'storId')) {
|
if (!suppressWarehouseChange && Object.hasOwn(values, 'storId')) {
|
||||||
void handleWarehouseChange(values.storId);
|
void handleWarehouseChange(values.storId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -108,7 +97,7 @@ const [OutboundForm, outboundFormApi] = useVbenForm({
|
|||||||
component: 'Select',
|
component: 'Select',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
options: [],
|
options: getDictOptions(DICT_TYPE.WMS_OUT_BILL_TYPE),
|
||||||
placeholder: '请选择业务类型',
|
placeholder: '请选择业务类型',
|
||||||
},
|
},
|
||||||
fieldName: 'billType',
|
fieldName: 'billType',
|
||||||
@@ -116,11 +105,15 @@ const [OutboundForm, outboundFormApi] = useVbenForm({
|
|||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'Input',
|
component: 'Select',
|
||||||
componentProps: { disabled: true },
|
componentProps: {
|
||||||
defaultValue: '生成',
|
allowClear: true,
|
||||||
|
options: getDictOptions(DICT_TYPE.WMS_IO_BILL_STATUS),
|
||||||
|
placeholder: '请选择单据状态',
|
||||||
|
},
|
||||||
fieldName: 'billStatus',
|
fieldName: 'billStatus',
|
||||||
label: '单据状态',
|
label: '单据状态',
|
||||||
|
rules: 'required',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'InputNumber',
|
component: 'InputNumber',
|
||||||
@@ -164,28 +157,6 @@ const [ManualDetail, manualDetailApi] = useVbenModal({
|
|||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
async function loadBillTypeOptions() {
|
|
||||||
billTypeRequest ??= getSimpleDictDataList();
|
|
||||||
const request = billTypeRequest;
|
|
||||||
try {
|
|
||||||
const dictItems = await request;
|
|
||||||
outboundFormApi.updateSchema([
|
|
||||||
{
|
|
||||||
componentProps: {
|
|
||||||
allowClear: true,
|
|
||||||
options: filterOutboundBillTypeOptions(dictItems),
|
|
||||||
placeholder: '请选择业务类型',
|
|
||||||
},
|
|
||||||
fieldName: 'billType',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
} catch {
|
|
||||||
message.error('出库业务类型加载失败,请稍后重试');
|
|
||||||
} finally {
|
|
||||||
if (billTypeRequest === request) billTypeRequest = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ key: 'index', title: '序号', width: 60 },
|
{ key: 'index', title: '序号', width: 60 },
|
||||||
{ dataIndex: 'materialCode', key: 'materialCode', title: '物料编码', width: 130 },
|
{ dataIndex: 'materialCode', key: 'materialCode', title: '物料编码', width: 130 },
|
||||||
@@ -265,34 +236,23 @@ function validateDetails() {
|
|||||||
|
|
||||||
const [Modal, modalApi] = useVbenModal({
|
const [Modal, modalApi] = useVbenModal({
|
||||||
async onConfirm() {
|
async onConfirm() {
|
||||||
if (isEdit.value) {
|
|
||||||
const { valid } = await legacyFormApi.validate();
|
|
||||||
if (!valid) return;
|
|
||||||
modalApi.lock();
|
|
||||||
try {
|
|
||||||
await updateIostorInv(
|
|
||||||
(await legacyFormApi.getValues()) as WmsIostorInvApi.IostorInv,
|
|
||||||
);
|
|
||||||
await modalApi.close();
|
|
||||||
emit('success');
|
|
||||||
message.success($t('ui.actionMessage.operationSuccess'));
|
|
||||||
} finally {
|
|
||||||
modalApi.unlock();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { valid } = await outboundFormApi.validate();
|
const { valid } = await outboundFormApi.validate();
|
||||||
if (!valid || !validateDetails()) return;
|
if (!valid || !validateDetails()) return;
|
||||||
const values = await outboundFormApi.getValues();
|
const values = await outboundFormApi.getValues();
|
||||||
modalApi.lock();
|
modalApi.lock();
|
||||||
try {
|
try {
|
||||||
await createOutbound(
|
const payload = buildOutboundPayload(
|
||||||
buildOutboundPayload(
|
|
||||||
{ ...values, bizDate: dayjs(values.bizDate).valueOf() },
|
{ ...values, bizDate: dayjs(values.bizDate).valueOf() },
|
||||||
details.value,
|
details.value,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
if (isEdit.value) {
|
||||||
|
await updateOutbound({
|
||||||
|
...payload,
|
||||||
|
iostorinvId: formData.value!.iostorinvId,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await createOutbound(payload);
|
||||||
|
}
|
||||||
await modalApi.close();
|
await modalApi.close();
|
||||||
emit('success');
|
emit('success');
|
||||||
message.success($t('ui.actionMessage.operationSuccess'));
|
message.success($t('ui.actionMessage.operationSuccess'));
|
||||||
@@ -318,17 +278,32 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
formData.value = undefined;
|
formData.value = undefined;
|
||||||
details.value = [];
|
details.value = [];
|
||||||
currentStorId.value = undefined;
|
currentStorId.value = undefined;
|
||||||
|
suppressWarehouseChange = true;
|
||||||
|
try {
|
||||||
await outboundFormApi.resetForm();
|
await outboundFormApi.resetForm();
|
||||||
|
await outboundFormApi.updateSchema([
|
||||||
|
{
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
disabled: false,
|
||||||
|
options: getDictOptions(DICT_TYPE.WMS_IO_BILL_STATUS),
|
||||||
|
placeholder: '请选择单据状态',
|
||||||
|
},
|
||||||
|
fieldName: 'billStatus',
|
||||||
|
},
|
||||||
|
]);
|
||||||
if (!mainModalOpen || mainOpenToken !== token || activeEditId) return;
|
if (!mainModalOpen || mainOpenToken !== token || activeEditId) return;
|
||||||
await outboundFormApi.setValues({
|
await outboundFormApi.setValues({
|
||||||
billCode: '保存时自动生成',
|
billCode: '保存时自动生成',
|
||||||
billStatus: '生成',
|
billStatus: '10',
|
||||||
bizDate: dayjs(),
|
bizDate: dayjs(),
|
||||||
detailCount: 0,
|
detailCount: 0,
|
||||||
totalWeight: '0.000',
|
totalWeight: '0.000',
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
|
suppressWarehouseChange = false;
|
||||||
|
}
|
||||||
if (!mainModalOpen || mainOpenToken !== token || activeEditId) return;
|
if (!mainModalOpen || mainOpenToken !== token || activeEditId) return;
|
||||||
await loadBillTypeOptions();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const editId = data.iostorinvId;
|
const editId = data.iostorinvId;
|
||||||
@@ -336,7 +311,10 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
formData.value = data;
|
formData.value = data;
|
||||||
modalApi.lock();
|
modalApi.lock();
|
||||||
try {
|
try {
|
||||||
const editResult = await getIostorInv(editId);
|
const [editResult, editDetails] = await Promise.all([
|
||||||
|
getIostorInv(editId),
|
||||||
|
getIostorInvDetails(editId),
|
||||||
|
]);
|
||||||
if (
|
if (
|
||||||
!shouldApplyEditResult(
|
!shouldApplyEditResult(
|
||||||
mainModalOpen,
|
mainModalOpen,
|
||||||
@@ -349,7 +327,38 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
formData.value = editResult;
|
formData.value = editResult;
|
||||||
await legacyFormApi.setValues(editResult);
|
suppressWarehouseChange = true;
|
||||||
|
try {
|
||||||
|
await outboundFormApi.resetForm();
|
||||||
|
await outboundFormApi.updateSchema([
|
||||||
|
{
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
disabled: true,
|
||||||
|
options: getDictOptions(DICT_TYPE.WMS_IO_BILL_STATUS),
|
||||||
|
placeholder: '请选择单据状态',
|
||||||
|
},
|
||||||
|
fieldName: 'billStatus',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
details.value = editDetails.map((detail, index) => ({
|
||||||
|
...detail,
|
||||||
|
rowKey: `edit-${editId}-${index}`,
|
||||||
|
}));
|
||||||
|
await outboundFormApi.setValues({
|
||||||
|
billCode: editResult.billCode,
|
||||||
|
billStatus: editResult.billStatus,
|
||||||
|
billType: editResult.billType,
|
||||||
|
bizDate: editResult.bizDate ? dayjs(editResult.bizDate) : undefined,
|
||||||
|
detailCount: details.value.length,
|
||||||
|
remark: editResult.remark,
|
||||||
|
storId: editResult.storId,
|
||||||
|
totalWeight: Number(editResult.totalWeight || 0).toFixed(3),
|
||||||
|
});
|
||||||
|
currentStorId.value = editResult.storId;
|
||||||
|
} finally {
|
||||||
|
suppressWarehouseChange = false;
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (
|
if (
|
||||||
shouldApplyEditResult(
|
shouldApplyEditResult(
|
||||||
@@ -369,8 +378,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Modal class="w-[1400px] max-w-[96vw]" :title="getTitle">
|
<Modal class="w-[1400px] max-w-[96vw]" :title="getTitle">
|
||||||
<LegacyForm v-if="isEdit" class="mx-4" />
|
<div class="px-4">
|
||||||
<div v-else class="px-4">
|
|
||||||
<OutboundForm />
|
<OutboundForm />
|
||||||
<div class="mb-3 mt-1 flex items-center justify-between">
|
<div class="mb-3 mt-1 flex items-center justify-between">
|
||||||
<span class="text-base font-medium">出库明细</span>
|
<span class="text-base font-medium">出库明细</span>
|
||||||
|
|||||||
@@ -34,7 +34,13 @@ describe('buildOutboundPayload', () => {
|
|||||||
it('将业务日期转换成毫秒,并剥离所有仅展示字段和汇总字段', () => {
|
it('将业务日期转换成毫秒,并剥离所有仅展示字段和汇总字段', () => {
|
||||||
const bizDate = new Date('2026-07-22T08:30:00+08:00');
|
const bizDate = new Date('2026-07-22T08:30:00+08:00');
|
||||||
const payload = buildOutboundPayload(
|
const payload = buildOutboundPayload(
|
||||||
{ billType: 'SALE', bizDate, remark: '主备注', storId: 'S-1' },
|
{
|
||||||
|
billStatus: '10',
|
||||||
|
billType: 'SALE',
|
||||||
|
bizDate,
|
||||||
|
remark: '主备注',
|
||||||
|
storId: 'S-1',
|
||||||
|
},
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
groupId: 11,
|
groupId: 11,
|
||||||
@@ -79,7 +85,7 @@ describe('buildOutboundPayload', () => {
|
|||||||
|
|
||||||
it('手工汇总行不附带库存引用字段', () => {
|
it('手工汇总行不附带库存引用字段', () => {
|
||||||
const payload = buildOutboundPayload(
|
const payload = buildOutboundPayload(
|
||||||
{ billType: 'SALE', bizDate: 1, storId: 'S-1' },
|
{ billStatus: '10', billType: 'SALE', bizDate: 1, storId: 'S-1' },
|
||||||
[{ materialCode: 'M-1', planQty: 2 }],
|
[{ materialCode: 'M-1', planQty: 2 }],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export function shouldApplyEditResult(
|
|||||||
|
|
||||||
export interface OutboundFormValues {
|
export interface OutboundFormValues {
|
||||||
billType?: string;
|
billType?: string;
|
||||||
|
billStatus?: string;
|
||||||
bizDate?: number | string | { valueOf: () => number };
|
bizDate?: number | string | { valueOf: () => number };
|
||||||
remark?: string;
|
remark?: string;
|
||||||
storId?: string;
|
storId?: string;
|
||||||
@@ -48,6 +49,7 @@ export function buildOutboundPayload(
|
|||||||
): WmsIostorInvApi.OutboundCreateReq {
|
): WmsIostorInvApi.OutboundCreateReq {
|
||||||
return {
|
return {
|
||||||
billType: values.billType!,
|
billType: values.billType!,
|
||||||
|
billStatus: values.billStatus!,
|
||||||
bizDate: Number(values.bizDate?.valueOf()),
|
bizDate: Number(values.bizDate?.valueOf()),
|
||||||
details: details.map((detail) => ({
|
details: details.map((detail) => ({
|
||||||
groupId: detail.groupId,
|
groupId: detail.groupId,
|
||||||
|
|||||||
Reference in New Issue
Block a user