feat: add bit sample warehouse operations

besure_bit
ck-chenkang 3 weeks ago
parent 14b8df60f6
commit 095b4a19dc

@ -401,3 +401,36 @@ INSERT INTO system_menu
(id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT @menu_seed, '明细导出', 'erp:bit-wms-record:export', 3, 20, @bit_record_menu_id, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
WHERE @bit_record_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM system_menu WHERE permission = 'erp:bit-wms-record:export' AND deleted = b'0');
-- 5. 调拨项库区字段:支持 BIT 样品仓同仓不同库区移库
SET @ddl := IF(
(SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'erp_stock_move_item' AND column_name = 'from_area_id') = 0,
'ALTER TABLE erp_stock_move_item ADD COLUMN from_area_id bigint NULL COMMENT ''调出库区编号'' AFTER from_warehouse_id',
'SELECT 1');
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @ddl := IF(
(SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'erp_stock_move_item' AND column_name = 'from_area_name') = 0,
'ALTER TABLE erp_stock_move_item ADD COLUMN from_area_name varchar(64) NULL COMMENT ''调出库区名称'' AFTER from_area_id',
'SELECT 1');
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @ddl := IF(
(SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'erp_stock_move_item' AND column_name = 'to_area_id') = 0,
'ALTER TABLE erp_stock_move_item ADD COLUMN to_area_id bigint NULL COMMENT ''调入库区编号'' AFTER to_warehouse_id',
'SELECT 1');
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @ddl := IF(
(SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'erp_stock_move_item' AND column_name = 'to_area_name') = 0,
'ALTER TABLE erp_stock_move_item ADD COLUMN to_area_name varchar(64) NULL COMMENT ''调入库区名称'' AFTER to_area_id',
'SELECT 1');
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

@ -0,0 +1,76 @@
package cn.iocoder.yudao.module.erp.controller.admin.bitwms;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsInboundReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLabelRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsMoveReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsOutboundReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsScanRespVO;
import cn.iocoder.yudao.module.erp.service.bitwms.BitWmsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - BIT 样品仓")
@RestController
@RequestMapping("/erp/bit-wms")
@Validated
public class BitWmsController {
@Resource
private BitWmsService bitWmsService;
@PostMapping("/inbound")
@Operation(summary = "BIT 样品入库")
@PreAuthorize("@ss.hasPermission('erp:bit-wms-in:create')")
public CommonResult<Long> inbound(@Valid @RequestBody BitWmsInboundReqVO reqVO) {
return success(bitWmsService.inbound(reqVO));
}
@PostMapping("/outbound")
@Operation(summary = "BIT 样品出库")
@PreAuthorize("@ss.hasPermission('erp:bit-wms-out:create')")
public CommonResult<Long> outbound(@Valid @RequestBody BitWmsOutboundReqVO reqVO) {
return success(bitWmsService.outbound(reqVO));
}
@PostMapping("/move")
@Operation(summary = "BIT 样品移库")
@PreAuthorize("@ss.hasPermission('erp:bit-wms-move:create')")
public CommonResult<Long> move(@Valid @RequestBody BitWmsMoveReqVO reqVO) {
return success(bitWmsService.move(reqVO));
}
@GetMapping("/scan")
@Operation(summary = "BIT 样品仓扫码")
@PreAuthorize("@ss.hasPermission('erp:bit-wms-stock:query')")
public CommonResult<BitWmsScanRespVO> scan(@RequestParam("code") String code) {
return success(bitWmsService.scan(code));
}
@GetMapping("/sample-label")
@Operation(summary = "获得样品标签打印数据")
@PreAuthorize("@ss.hasPermission('erp:bit-wms-sample:print')")
public CommonResult<BitWmsLabelRespVO> getSampleLabel(@RequestParam("productId") Long productId) {
return success(bitWmsService.getSampleLabel(productId));
}
@GetMapping("/location-label")
@Operation(summary = "获得库位标签打印数据")
@PreAuthorize("@ss.hasPermission('erp:bit-wms-location:print')")
public CommonResult<BitWmsLabelRespVO> getLocationLabel(@RequestParam("locationId") Long locationId) {
return success(bitWmsService.getLocationLabel(locationId));
}
}

@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@Schema(description = "管理后台 - BIT 样品仓入库 Request VO")
@Data
public class BitWmsInboundReqVO {
@Schema(description = "样品产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@NotNull(message = "样品产品ID不能为空")
private Long productId;
@Schema(description = "仓库ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
@NotNull(message = "仓库ID不能为空")
private Long warehouseId;
@Schema(description = "库区ID", example = "30")
private Long areaId;
@Schema(description = "入库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@NotNull(message = "入库数量不能为空")
@DecimalMin(value = "0.000001", message = "入库数量必须大于 0")
private BigDecimal count;
@Schema(description = "供应商ID", example = "1001")
private Long supplierId;
@Schema(description = "备注", example = "first shelf")
private String remark;
}

@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Map;
@Schema(description = "管理后台 - BIT 样品仓标签打印 Response VO")
@Data
public class BitWmsLabelRespVO {
@Schema(description = "标签对象类型SAMPLE 样品LOCATION 库位", example = "SAMPLE")
private String type;
@Schema(description = "业务ID", example = "10")
private Long id;
@Schema(description = "编码", example = "SP202606220001")
private String code;
@Schema(description = "显示名称", example = "样品 A")
private String name;
@Schema(description = "二维码地址")
private String qrcodeUrl;
@Schema(description = "打印模板 JSON")
private String templateJson;
@Schema(description = "打印数据")
private Map<String, Object> printData;
}

@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@Schema(description = "管理后台 - BIT 样品仓移库 Request VO")
@Data
public class BitWmsMoveReqVO {
@Schema(description = "样品产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@NotNull(message = "样品产品ID不能为空")
private Long productId;
@Schema(description = "调出仓库ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
@NotNull(message = "调出仓库ID不能为空")
private Long fromWarehouseId;
@Schema(description = "调出库区ID", example = "30")
private Long fromAreaId;
@Schema(description = "调入仓库ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
@NotNull(message = "调入仓库ID不能为空")
private Long toWarehouseId;
@Schema(description = "调入库区ID", example = "31")
private Long toAreaId;
@Schema(description = "移库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "移库数量不能为空")
@DecimalMin(value = "0.000001", message = "移库数量必须大于 0")
private BigDecimal count;
@Schema(description = "备注", example = "上架调整")
private String remark;
}

@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@Schema(description = "管理后台 - BIT 样品仓出库 Request VO")
@Data
public class BitWmsOutboundReqVO {
@Schema(description = "样品产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@NotNull(message = "样品产品ID不能为空")
private Long productId;
@Schema(description = "仓库ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
@NotNull(message = "仓库ID不能为空")
private Long warehouseId;
@Schema(description = "库区ID", example = "30")
private Long areaId;
@Schema(description = "出库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@NotNull(message = "出库数量不能为空")
@DecimalMin(value = "0.000001", message = "出库数量必须大于 0")
private BigDecimal count;
@Schema(description = "出库原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "TEST")
@NotBlank(message = "出库原因不能为空")
private String outReason;
@Schema(description = "备注", example = "测试出库")
private String remark;
}

@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - BIT 样品仓扫码 Response VO")
@Data
public class BitWmsScanRespVO {
@Schema(description = "扫码对象类型SAMPLE 样品LOCATION 库位", example = "SAMPLE")
private String type;
@Schema(description = "业务ID", example = "10")
private Long id;
@Schema(description = "编码", example = "SP202606220001")
private String code;
@Schema(description = "名称", example = "样品 A")
private String name;
}

@ -48,10 +48,22 @@ public class ErpStockMoveSaveReqVO {
@NotNull(message = "调出仓库编号不能为空")
private Long fromWarehouseId;
@Schema(description = "调出库区编号", example = "30")
private Long fromAreaId;
@Schema(description = "调出库区名称", example = "A1")
private String fromAreaName;
@Schema(description = "调入仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888")
@NotNull(message = "调入仓库编号不能为空")
private Long toWarehouseId;
@Schema(description = "调入库区编号", example = "31")
private Long toAreaId;
@Schema(description = "调入库区名称", example = "A2")
private String toAreaName;
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
@NotNull(message = "产品编号不能为空")
private Long productId;
@ -69,9 +81,10 @@ public class ErpStockMoveSaveReqVO {
@AssertTrue(message = "调出、调仓仓库不能相同")
@JsonIgnore
public boolean isWarehouseValid() {
return ObjectUtil.notEqual(fromWarehouseId, toWarehouseId);
return ObjectUtil.notEqual(fromWarehouseId, toWarehouseId)
|| ObjectUtil.notEqual(fromAreaId, toAreaId);
}
}
}
}

@ -41,12 +41,28 @@ public class ErpStockMoveItemDO extends BaseDO {
* {@link ErpWarehouseDO#getId()}
*/
private Long fromWarehouseId;
/**
*
*/
private Long fromAreaId;
/**
*
*/
private String fromAreaName;
/**
*
*
* {@link ErpWarehouseDO#getId()}
*/
private Long toWarehouseId;
/**
*
*/
private Long toAreaId;
/**
*
*/
private String toAreaName;
/**
*
*
@ -76,4 +92,4 @@ public class ErpStockMoveItemDO extends BaseDO {
*/
private String remark;
}
}

@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.erp.dal.dataobject.warehouselocation.WarehouseLocationDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import cn.iocoder.yudao.module.erp.controller.admin.warehouselocation.vo.*;
/**
@ -58,4 +59,6 @@ public interface WarehouseLocationMapper extends BaseMapperX<WarehouseLocationDO
default Long selectCountByWarehouseId(Long warehouseId) {
return selectCount(WarehouseLocationDO::getWarehouseId, warehouseId);
}
String selectPrintTemplate(@Param("templateType") Integer templateType);
}

@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.erp.service.bitwms;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsInboundReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLabelRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsMoveReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsOutboundReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsScanRespVO;
import javax.validation.Valid;
public interface BitWmsService {
Long inbound(@Valid BitWmsInboundReqVO reqVO);
Long outbound(@Valid BitWmsOutboundReqVO reqVO);
Long move(@Valid BitWmsMoveReqVO reqVO);
BitWmsScanRespVO scan(String code);
BitWmsLabelRespVO getSampleLabel(Long productId);
BitWmsLabelRespVO getLocationLabel(Long locationId);
}

@ -0,0 +1,236 @@
package cn.iocoder.yudao.module.erp.service.bitwms;
import cn.iocoder.yudao.module.common.enums.QrcodeBizTypeEnum;
import cn.iocoder.yudao.module.common.service.qrcordrecord.QrcodeRecordService;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsInboundReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsLabelRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsMoveReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsOutboundReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsScanRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInSaveReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveSaveReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out.ErpStockOutSaveReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out.ErpStockOutSaveReqVOItem;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.warehouselocation.WarehouseLocationDO;
import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.warehouselocation.WarehouseLocationMapper;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
import cn.iocoder.yudao.module.erp.service.stock.ErpStockInService;
import cn.iocoder.yudao.module.erp.service.stock.ErpStockMoveService;
import cn.iocoder.yudao.module.erp.service.stock.ErpStockOutService;
import cn.iocoder.yudao.module.erp.service.stock.ErpStockService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.PRODUCT_CODE_NOT_EXISTS;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.PRODUCT_NOT_EXISTS;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.STOCK_COUNT_NEGATIVE2;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.WAREHOUSE_LOCATION_NOT_EXISTS;
@Service
@Validated
public class BitWmsServiceImpl implements BitWmsService {
private static final String BIT_BIZ_UNIT = "BIT";
private static final String SAMPLE_TYPE = "SAMPLE";
private static final String LOCATION_TYPE = "LOCATION";
private static final String SAMPLE_IN_TYPE = "样品入库";
private static final String SAMPLE_OUT_TYPE = "样品出库";
private static final String SAMPLE_MOVE_TYPE = "样品移库";
private static final int SAMPLE_PRINT_TEMPLATE_TYPE = 6;
private static final int LOCATION_PRINT_TEMPLATE_TYPE = 7;
@Resource
private ErpProductService productService;
@Resource
private ErpProductMapper productMapper;
@Resource
private WarehouseLocationMapper warehouseLocationMapper;
@Resource
private ErpStockInService stockInService;
@Resource
private ErpStockOutService stockOutService;
@Resource
private ErpStockMoveService stockMoveService;
@Resource
private ErpStockService stockService;
@Resource
private QrcodeRecordService qrcodeRecordService;
@Override
@Transactional(rollbackFor = Exception.class)
public Long inbound(BitWmsInboundReqVO reqVO) {
ErpProductRespVO product = validateBitSample(reqVO.getProductId());
ErpStockInSaveReqVO stockIn = new ErpStockInSaveReqVO();
stockIn.setSupplierId(reqVO.getSupplierId());
stockIn.setInTime(LocalDateTime.now());
stockIn.setInType(SAMPLE_IN_TYPE);
stockIn.setRemark(reqVO.getRemark());
stockIn.setWarehouseId(reqVO.getWarehouseId());
ErpStockInSaveReqVO.Item item = new ErpStockInSaveReqVO.Item();
item.setProductId(reqVO.getProductId());
item.setWarehouseId(reqVO.getWarehouseId());
item.setAreaId(reqVO.getAreaId());
item.setCount(reqVO.getCount());
item.setProductPrice(product.getPurchasePrice() != null ? product.getPurchasePrice() : BigDecimal.ZERO);
item.setRemark(reqVO.getRemark());
stockIn.setItems(Collections.singletonList(item));
return stockInService.createStockIn(stockIn);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long outbound(BitWmsOutboundReqVO reqVO) {
ErpProductRespVO product = validateBitSample(reqVO.getProductId());
validateStockEnough(reqVO.getProductId(), reqVO.getWarehouseId(), reqVO.getAreaId(), reqVO.getCount());
ErpStockOutSaveReqVO stockOut = new ErpStockOutSaveReqVO();
stockOut.setOutType(SAMPLE_OUT_TYPE);
stockOut.setOutTime(LocalDateTime.now());
stockOut.setRemark(buildOutboundRemark(reqVO.getOutReason(), reqVO.getRemark()));
ErpStockOutSaveReqVOItem item = new ErpStockOutSaveReqVOItem();
item.setProductId(reqVO.getProductId());
item.setWarehouseId(reqVO.getWarehouseId());
item.setAreaId(reqVO.getAreaId());
item.setCount(reqVO.getCount());
item.setProductPrice(product.getPurchasePrice() != null ? product.getPurchasePrice() : BigDecimal.ZERO);
item.setRemark(reqVO.getRemark());
stockOut.setItems(Collections.singletonList(item));
return stockOutService.createStockOut(stockOut);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long move(BitWmsMoveReqVO reqVO) {
ErpProductRespVO product = validateBitSample(reqVO.getProductId());
validateStockEnough(reqVO.getProductId(), reqVO.getFromWarehouseId(), reqVO.getFromAreaId(), reqVO.getCount());
ErpStockMoveSaveReqVO stockMove = new ErpStockMoveSaveReqVO();
stockMove.setMoveTime(LocalDateTime.now());
stockMove.setRemark(reqVO.getRemark() != null ? reqVO.getRemark() : SAMPLE_MOVE_TYPE);
ErpStockMoveSaveReqVO.Item item = new ErpStockMoveSaveReqVO.Item();
item.setProductId(reqVO.getProductId());
item.setFromWarehouseId(reqVO.getFromWarehouseId());
item.setFromAreaId(reqVO.getFromAreaId());
item.setToWarehouseId(reqVO.getToWarehouseId());
item.setToAreaId(reqVO.getToAreaId());
item.setCount(reqVO.getCount());
item.setProductPrice(product.getPurchasePrice() != null ? product.getPurchasePrice() : BigDecimal.ZERO);
item.setRemark(reqVO.getRemark());
stockMove.setItems(Collections.singletonList(item));
Long id = stockMoveService.createStockMove(stockMove);
stockMoveService.updateStockMoveStatus(id, ErpAuditStatus.APPROVE.getStatus());
return id;
}
@Override
public BitWmsScanRespVO scan(String code) {
ErpProductDO product = productMapper.selectOne(ErpProductDO::getBarCode, code);
if (product != null && Boolean.TRUE.equals(product.getIsSample()) && BIT_BIZ_UNIT.equals(product.getBizUnit())) {
BitWmsScanRespVO respVO = new BitWmsScanRespVO();
respVO.setType(SAMPLE_TYPE);
respVO.setId(product.getId());
respVO.setCode(product.getBarCode());
respVO.setName(product.getName());
return respVO;
}
WarehouseLocationDO location = warehouseLocationMapper.selectByNo(code);
if (location != null) {
BitWmsScanRespVO respVO = new BitWmsScanRespVO();
respVO.setType(LOCATION_TYPE);
respVO.setId(location.getId());
respVO.setCode(location.getCode());
respVO.setName(location.getName());
return respVO;
}
throw exception(PRODUCT_CODE_NOT_EXISTS);
}
@Override
public BitWmsLabelRespVO getSampleLabel(Long productId) {
ErpProductRespVO product = validateBitSample(productId);
String qrcodeUrl = qrcodeRecordService.selectQrcodeUrlByIdAndCode(
QrcodeBizTypeEnum.PRODUCT.getCode(), product.getId(), product.getBarCode());
Map<String, Object> printData = new LinkedHashMap<>();
printData.put("id", product.getId());
printData.put("code", product.getBarCode());
printData.put("name", product.getName());
printData.put("customerName", product.getCustomerName());
printData.put("sampleCategory", product.getSampleCategory());
printData.put("qrcodeUrl", qrcodeUrl);
return buildLabel(SAMPLE_TYPE, product.getId(), product.getBarCode(), product.getName(), qrcodeUrl,
productMapper.selectPrintTemplate(SAMPLE_PRINT_TEMPLATE_TYPE), printData);
}
@Override
public BitWmsLabelRespVO getLocationLabel(Long locationId) {
WarehouseLocationDO location = warehouseLocationMapper.selectById(locationId);
if (location == null) {
throw exception(WAREHOUSE_LOCATION_NOT_EXISTS);
}
String qrcodeUrl = qrcodeRecordService.selectQrcodeUrlByIdAndCode(
QrcodeBizTypeEnum.WAREHOUSE_LOCATION.getCode(), location.getId(), location.getCode());
Map<String, Object> printData = new LinkedHashMap<>();
printData.put("id", location.getId());
printData.put("code", location.getCode());
printData.put("name", location.getName());
printData.put("warehouseId", location.getWarehouseId());
printData.put("areaId", location.getAreaId());
printData.put("qrcodeUrl", qrcodeUrl);
return buildLabel(LOCATION_TYPE, location.getId(), location.getCode(), location.getName(), qrcodeUrl,
warehouseLocationMapper.selectPrintTemplate(LOCATION_PRINT_TEMPLATE_TYPE), printData);
}
private ErpProductRespVO validateBitSample(Long productId) {
ErpProductRespVO product = productService.getProduct(productId);
if (product == null || !Boolean.TRUE.equals(product.getIsSample()) || !BIT_BIZ_UNIT.equals(product.getBizUnit())) {
throw exception(PRODUCT_NOT_EXISTS);
}
return product;
}
private void validateStockEnough(Long productId, Long warehouseId, Long areaId, BigDecimal count) {
ErpStockDO stock = stockService.getStock(productId, warehouseId, areaId);
BigDecimal currentCount = stock != null && stock.getCount() != null ? stock.getCount() : BigDecimal.ZERO;
if (currentCount.compareTo(count) < 0) {
throw exception(STOCK_COUNT_NEGATIVE2, productId, warehouseId);
}
}
private String buildOutboundRemark(String outReason, String remark) {
if (remark == null || remark.isEmpty()) {
return "出库原因:" + outReason;
}
return "出库原因:" + outReason + "" + remark;
}
private BitWmsLabelRespVO buildLabel(String type, Long id, String code, String name, String qrcodeUrl,
String templateJson, Map<String, Object> printData) {
BitWmsLabelRespVO respVO = new BitWmsLabelRespVO();
respVO.setType(type);
respVO.setId(id);
respVO.setCode(code);
respVO.setName(name);
respVO.setQrcodeUrl(qrcodeUrl);
respVO.setTemplateJson(templateJson);
respVO.setPrintData(printData);
return respVO;
}
}

@ -131,11 +131,15 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
ErpProductDO productDO = productService.getProduct(stockMoveItem.getProductId());
stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
stockMoveItem.getProductId(),productDO.getCategoryId(), stockMoveItem.getFromWarehouseId(), fromCount,
fromBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo(),stockMove.getMoveTime()));
stockMoveItem.getProductId(), productDO.getCategoryId(), null,
stockMoveItem.getFromWarehouseId(), stockMoveItem.getFromAreaId(), stockMoveItem.getFromAreaName(),
fromCount, fromBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo(),
stockMove.getMoveTime()));
stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
stockMoveItem.getProductId(),productDO.getCategoryId(), stockMoveItem.getToWarehouseId(), toCount,
toBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo(),stockMove.getMoveTime()));
stockMoveItem.getProductId(), productDO.getCategoryId(), null,
stockMoveItem.getToWarehouseId(), stockMoveItem.getToAreaId(), stockMoveItem.getToAreaName(),
toCount, toBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo(),
stockMove.getMoveTime()));
});
}
@ -228,4 +232,4 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
return stockMoveItemMapper.selectListByMoveIds(moveIds);
}
}
}

@ -9,4 +9,12 @@
文档可见https://www.iocoder.cn/MyBatis/x-plugins/
-->
</mapper>
<select id="selectPrintTemplate" resultType="java.lang.String">
select template_json
from mes_print_template
where deleted = 0
and template_type = #{templateType}
and template_biz_type = 1
</select>
</mapper>

@ -0,0 +1,129 @@
package cn.iocoder.yudao.module.erp.service.bitwms;
import cn.iocoder.yudao.framework.test.core.util.AssertUtils;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsInboundReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsMoveReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.bitwms.vo.BitWmsOutboundReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInSaveReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
import cn.iocoder.yudao.module.erp.service.stock.ErpStockInService;
import cn.iocoder.yudao.module.erp.service.stock.ErpStockMoveService;
import cn.iocoder.yudao.module.erp.service.stock.ErpStockService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.STOCK_COUNT_NEGATIVE2;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* {@link BitWmsServiceImpl}
*/
@ExtendWith(MockitoExtension.class)
class BitWmsServiceImplTest {
@InjectMocks
private BitWmsServiceImpl bitWmsService;
@Mock
private ErpProductService productService;
@Mock
private ErpStockInService stockInService;
@Mock
private ErpStockMoveService stockMoveService;
@Mock
private ErpStockService stockService;
@Test
void inbound_createsSampleStockInWithBitType() {
BitWmsInboundReqVO reqVO = new BitWmsInboundReqVO();
reqVO.setProductId(10L);
reqVO.setWarehouseId(20L);
reqVO.setAreaId(30L);
reqVO.setCount(new BigDecimal("2"));
reqVO.setRemark("first shelf");
ErpProductRespVO product = buildBitSampleProduct();
when(productService.getProduct(10L)).thenReturn(product);
when(stockInService.createStockIn(org.mockito.ArgumentMatchers.any(ErpStockInSaveReqVO.class))).thenReturn(100L);
Long id = bitWmsService.inbound(reqVO);
assertEquals(100L, id);
ArgumentCaptor<ErpStockInSaveReqVO> captor = ArgumentCaptor.forClass(ErpStockInSaveReqVO.class);
verify(stockInService).createStockIn(captor.capture());
ErpStockInSaveReqVO stockIn = captor.getValue();
assertEquals("样品入库", stockIn.getInType());
assertEquals(1, stockIn.getItems().size());
assertEquals(10L, stockIn.getItems().get(0).getProductId());
assertEquals(20L, stockIn.getItems().get(0).getWarehouseId());
assertEquals(30L, stockIn.getItems().get(0).getAreaId());
assertEquals(new BigDecimal("2"), stockIn.getItems().get(0).getCount());
}
@Test
void outbound_rejectsCountGreaterThanLocationStock() {
BitWmsOutboundReqVO reqVO = new BitWmsOutboundReqVO();
reqVO.setProductId(10L);
reqVO.setWarehouseId(20L);
reqVO.setAreaId(30L);
reqVO.setCount(new BigDecimal("999"));
reqVO.setOutReason("TEST");
when(productService.getProduct(10L)).thenReturn(buildBitSampleProduct());
when(stockService.getStock(10L, 20L, 30L)).thenReturn(new ErpStockDO().setCount(BigDecimal.ONE));
AssertUtils.assertServiceException(() -> bitWmsService.outbound(reqVO), STOCK_COUNT_NEGATIVE2, 10L, 20L);
}
@Test
void move_keepsTotalStockAndChangesLocation() {
BitWmsMoveReqVO reqVO = new BitWmsMoveReqVO();
reqVO.setProductId(10L);
reqVO.setFromWarehouseId(20L);
reqVO.setFromAreaId(30L);
reqVO.setToWarehouseId(20L);
reqVO.setToAreaId(31L);
reqVO.setCount(new BigDecimal("1"));
ErpProductRespVO product = buildBitSampleProduct();
when(productService.getProduct(10L)).thenReturn(product);
when(stockService.getStock(10L, 20L, 30L)).thenReturn(new ErpStockDO().setCount(new BigDecimal("2")));
when(stockMoveService.createStockMove(org.mockito.ArgumentMatchers.any(ErpStockMoveSaveReqVO.class))).thenReturn(300L);
Long id = bitWmsService.move(reqVO);
assertEquals(300L, id);
ArgumentCaptor<ErpStockMoveSaveReqVO> captor = ArgumentCaptor.forClass(ErpStockMoveSaveReqVO.class);
verify(stockMoveService).createStockMove(captor.capture());
ErpStockMoveSaveReqVO.Item item = captor.getValue().getItems().get(0);
assertEquals(20L, item.getFromWarehouseId());
assertEquals(30L, item.getFromAreaId());
assertEquals(20L, item.getToWarehouseId());
assertEquals(31L, item.getToAreaId());
assertEquals(new BigDecimal("1"), item.getCount());
verify(stockMoveService).updateStockMoveStatus(eq(300L), eq(ErpAuditStatus.APPROVE.getStatus()));
}
private ErpProductRespVO buildBitSampleProduct() {
ErpProductRespVO product = new ErpProductRespVO();
product.setId(10L);
product.setName("BIT 样品");
product.setIsSample(true);
product.setBizUnit("BIT");
product.setCategoryId(50L);
return product;
}
}
Loading…
Cancel
Save