Commit c3c25073 张少辉

1.SO1374-PokaNon入库通知对接

1 个父辈 1a544c4e
......@@ -19,6 +19,10 @@ import java.util.Locale;
@Data
public class ResultBean<T> {
public static ResultBean newErrorResult(int code, String msg) {
return newErrorResult(code, "", msg);
}
public static ResultBean newErrorResult(int code, String msgKey, String msg ) {
return newErrorResult(code, msgKey, msg, new String[]{},true);
}
......
......@@ -17,4 +17,6 @@ public interface IBarcodeManager extends IBaseManager<Barcode> {
void download(List<Barcode> list, HttpServletResponse response) throws IOException;
void deleteBarcodes(Set<String> ids);
Barcode findOneByLockName(String lockName);
}
......@@ -96,6 +96,11 @@ public class BarcodeManagerImpl implements IBarcodeManager {
}
@Override
public Barcode findOneByLockName(String lockName) {
return barcodeDao.findOne(new Query(Criteria.where("lockName").is(lockName)));
}
@Override
public Barcode saveBarcode(Barcode resources) {
validateSave(resources);
......
package com.neotel.smfcore.custom.so1374;
import com.alibaba.fastjson.JSON;
import com.neotel.smfcore.common.bean.ResultBean;
import com.neotel.smfcore.common.utils.DateUtil;
import com.neotel.smfcore.common.utils.StringUtils;
import com.neotel.smfcore.core.barcode.service.manager.IBarcodeManager;
import com.neotel.smfcore.core.barcode.service.manager.IComponentManager;
import com.neotel.smfcore.core.barcode.service.po.Barcode;
import com.neotel.smfcore.core.barcode.service.po.Component;
import com.neotel.smfcore.core.storage.service.manager.IStoragePosManager;
import com.neotel.smfcore.core.storage.service.po.StoragePos;
import com.neotel.smfcore.core.system.util.TaskService;
import com.neotel.smfcore.custom.so1374.bean.PokaNonInboundReturnRequest;
import com.neotel.smfcore.security.annotation.AnonymousAccess;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Api(tags = "入库对接")
@Slf4j
@RestController
public class PokaNonInboundAndReturnController {
@Autowired
private IStoragePosManager storagePosManager;
@Autowired
private IComponentManager componentManager;
@Autowired
private IBarcodeManager barcodeManager;
@Autowired
private TaskService taskService;
@ApiOperation("入库/退库通知")
@RequestMapping("/api/v2/inbound/register")
@AnonymousAccess
public synchronized ResultBean register(@RequestBody PokaNonInboundReturnRequest request) {
// 新增:请求接收日志(拼接关键参数,替换原全量JSON)
log.info("收到入库/退库通知:"+ JSON.toJSONString(request));
//判断是否为空
//1001 VALIDATION_ERROR 缺少 / 无效的必填字段(如 serial、qty、locationId 等),直接拒绝请求
if (requestIsEmpty(request)) {
// 新增:参数空日志
log.error("请求参数为空,拒绝请求,clientTxnId:" + request.getClientTxnId());
return ResultBean.newErrorResult(1001, "INVALID_REQUEST");
}
//判断库存是否存在
//1004 INVALID_LOCATION 库位地址(locationId)未找到 / 格式无效,拒绝入库
StoragePos pos = checkLocationExist(request);
if (pos == null) {
// 新增:库位不存在日志
log.error("库位不存在,locationId:" + request.getLocation().getLocationId() + ",clientTxnId:" + request.getClientTxnId());
return ResultBean.newErrorResult(1004, "INVALID_LOCATION");
}
//判断库位上是否有物料
//1005 LOCATION_FULL 目标库位无法存储物料(库位已满 / 被锁定),拒绝入库
if (pos.getBarcode() != null) {
// 新增:库位占用日志
log.error("库位已占用,locationId:" + pos.getPosName() + ",clientTxnId:" + request.getClientTxnId());
return ResultBean.newErrorResult(1005, "LOCATION_FULL");
}
//根据类型判断是入库还是退库
Barcode barcode = null;
String operation = request.getOperation();
PokaNonInboundReturnRequest.Material material = request.getMaterial();
PokaNonInboundReturnRequest.Label label = request.getLabel();
if ("INBOUND".equals(operation)) {
//新入库(operation=INBOUND):serial 在 SMF 在库中需保持唯一,重复则拒绝入库,成功后 SMF 生成并返回 uid
barcode = barcodeManager.findOneByLockName(material.getSerial());
if (barcode != null) {
// 新增:serial重复日志
log.error("新入库序列号重复,serial:" + material.getSerial() + ",clientTxnId:" + request.getClientTxnId());
//1002 DUPLICATE_SERIAL 新入库:序列号已存在于 SMF 在库中,拒绝入库;再入库:仅当未生成新 uid 时触发该错误
return ResultBean.newErrorResult(1002, "DUPLICATE_SERIAL");
}
barcode = createBarcode(material, label);
// 新增:新入库条码创建完成日志
log.info("新入库条码创建完成,serial:" + material.getSerial() + ",uid:" + barcode.getBarcode());
} else if ("RETURN".equals(operation)) {
barcode = createBarcode(material, label);
// 新增:退库条码创建完成日志
log.info("退库条码创建完成,serial:" + material.getSerial() + ",uid:" + barcode.getBarcode());
} else {
// 新增:操作类型非法日志
log.error("操作类型非法,operation:" + operation + ",clientTxnId:" + request.getClientTxnId());
//1001 VALIDATION_ERROR 缺少 / 无效的必填字段(如 serial、qty、locationId 等),直接拒绝请求
return ResultBean.newErrorResult(1006, "VALIDATION_ERROR");
}
//创建元器件信息
createComponent(material);
// 新增:元器件创建完成日志
log.info("元器件创建完成,partNo:" + material.getPartNo() + ",makerPartNo:" + material.getMakerPartNo());
//生成一个入库任务
//操作人
PokaNonInboundReturnRequest.Operator operator = request.getOperator();
String operatorId = operator.getOperatorId();
taskService.addTaskToFinished(pos, barcode, operatorId);
// 新增:入库任务创建完成日志
log.info("入库任务创建完成,operatorId:" + operatorId + ",locationId:" + pos.getPosName());
//增加返回值
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("smfTxnId", request.getClientTxnId());
resultMap.put("serial", material.getSerial());
resultMap.put("uid", barcode.getBarcode());
resultMap.put("status", "COMPLETED");
resultMap.put("serverTime", DateUtil.toDateString(new Date(), "yyyy-MM-dd HH:mm:ss"));
//构建registered信息
Map<String, Object> registeredMap = new HashMap<>();
registeredMap.put("partNo", material.getPartNo());
registeredMap.put("qty", material.getQty());
registeredMap.put("locationId", pos.getPosName());
resultMap.put("registered", registeredMap);
// 新增:请求处理完成日志
log.info("入库/退库请求处理完成,clientTxnId:" + request.getClientTxnId() + ",uid:" + barcode.getBarcode());
return ResultBean.newOkResult(resultMap);
}
private Component createComponent(PokaNonInboundReturnRequest.Material material) {
//判断元器件是否存在,如果不存在,则自动创建一个
String partNo = material.getPartNo();
String makerPartNo = material.getMakerPartNo();
Integer qty = material.getQty();
Component component = componentManager.findByPartNumberAndProvider(partNo, makerPartNo);
if (component == null) {
// 新增:元器件新建日志
log.info("元器件不存在,新建元器件,partNo:" + partNo + ",makerPartNo:" + makerPartNo);
component = new Component();
component.setPartNumber(partNo);
component.setProvider(makerPartNo);
component.setPlateSize(2);
component.setHeight(2);
component.setAmount(qty);
componentManager.saveComponent(component);
} else {
// 新增:元器件已存在日志
log.info("元器件已存在,partNo:" + partNo + ",makerPartNo:" + makerPartNo);
}
return component;
}
//判断是否为空
private boolean requestIsEmpty(PokaNonInboundReturnRequest request) {
String clientTxnId = request.getClientTxnId();
if (StringUtils.isEmpty(clientTxnId)) {
log.warn("参数为空:clientTxnId");
return true;
}
String operation = request.getOperation();
if (StringUtils.isEmpty(operation)) {
log.warn("参数为空:operation");
return true;
}
PokaNonInboundReturnRequest.Operator operator = request.getOperator();
if (operator == null || StringUtils.isEmpty(operator.getOperatorId())) {
log.warn("参数为空:operatorId");
return true;
}
PokaNonInboundReturnRequest.Material material = request.getMaterial();
if (material == null) {
log.warn("参数为空:material");
return true;
}
if (StringUtils.isEmpty(material.getSerial())) {
log.warn("参数为空:material.serial");
return true;
}
if (StringUtils.isEmpty(material.getPartNo())) {
log.warn("参数为空:material.partNo");
return true;
}
Integer qty = material.getQty();
if (qty == null || qty == 0) {
log.warn("参数为空/无效:material.qty");
return true;
}
PokaNonInboundReturnRequest.Location location = request.getLocation();
if (location == null || StringUtils.isEmpty(location.getLocationId())) {
log.warn("参数为空:locationId");
return true;
}
return false;
}
//判断序列号是否存在
public StoragePos checkLocationExist(PokaNonInboundReturnRequest request) {
//判断库位是否存在
//1004 INVALID_LOCATION 库位地址(locationId)未找到 / 格式无效,拒绝入库
PokaNonInboundReturnRequest.Location location = request.getLocation();
String locationId = location.getLocationId();
StoragePos pos = storagePosManager.getByPosName(locationId);
// 新增:库位校验日志
log.info("库位校验,locationId:" + locationId + ",是否存在:" + (pos != null));
return pos;
}
private Barcode createBarcode(PokaNonInboundReturnRequest.Material material, PokaNonInboundReturnRequest.Label label) {
String uid = material.getSerial() + "_" + DateUtil.toDateString(new Date(), "yyyyMMddHHmmss");
Barcode barcode = new Barcode();
barcode.setBarcode(uid);
barcode.setPartNumber(material.getPartNo());
barcode.setProvider(material.getMakerPartNo());
barcode.setBatch(material.getMakerLot());
barcode.setMemo(material.getLotNo());
barcode.setAmount(material.getQty());
//设置生产日期和过期日期
String receiveDate = material.getReceiveDate();
if (StringUtils.isNotEmpty(receiveDate)) {
barcode.setProduceDate(DateUtil.toDate(receiveDate, "yyyy-MM-dd"));
}
String expireDate = material.getExpireDate();
if (StringUtils.isNotEmpty(expireDate)) {
barcode.setExpireDate(DateUtil.toDate(expireDate, "yyyy-MM-dd"));
}
//设置完整的条码信息
if (label != null) {
String fullCode = label.getFullCode();
barcode.setFullCode(fullCode);
Integer qrQty = label.getQrQty();
if (qrQty != null) {
barcode.setLabelAmount(qrQty);
}
}
barcode.setLockName(material.getSerial());
barcode = barcodeManager.save(barcode);
return barcode;
}
}
\ No newline at end of file
package com.neotel.smfcore.custom.so1374.bean;
import lombok.Data;
/**
* 入库/退货请求实体类 (PokaNon INBOUND/RETURN Request)
* 对应I/F 定義表的字段规范
*/
@Data // Lombok注解,自动生成getter/setter/toString/equals/hashCode
public class PokaNonInboundReturnRequest {
/**
* PokaNon交易ID(幂等键)
* 类型:string | 必填:Y
* 示例:POKANON-INB-20260124-000123
*/
private String clientTxnId;
/**
* 客户编码(Panasonic可能固定)
* 类型:string | 必填:Y
* 示例:PANASONIC
*/
private String customerCode;
/**
* 操作类型:INBOUND(新品入库) / RETURN(退货重新入库)
* 类型:string | 必填:Y
* 示例:INBOUND
*/
private String operation;
/**
* 操作人员信息
* 类型:object | 必填:Y(子字段operatorId必填)
*/
private Operator operator;
/**
* 物料信息
* 类型:object | 必填:Y(核心子字段必填)
*/
private Material material;
/**
* 库位信息
* 类型:object | 必填:Y(子字段locationId必填)
*/
private Location location;
/**
* 标签信息
* 类型:object | 必填:N
*/
private Label label;
/**
* 客户端时间戳(审计用)
* 类型:string | 必填:N
* 格式:YYYY-MM-DD HH:mm:ss
* 示例:2026-01-24 10:12:30
*/
private String timestamp;
/**
* 操作人员子实体
*/
@Data
public static class Operator {
/**
* 操作人员ID
* 类型:string | 必填:Y
* 示例:U001
*/
private String operatorId;
}
/**
* 物料子实体
*/
@Data
public static class Material {
/**
* PokaNon序列号(标签序列号)
* 类型:string | 必填:Y
* 示例:ID-2026012400001-001
*/
private String serial;
/**
* 主物料编号(建议必填)
* 类型:string | 必填:Y*
* 示例:GLOBAL-PN-001
*/
private String partNo;
/**
* 制造商物料编号
* 类型:string | 必填:N
* 示例:ABC-123
*/
private String makerPartNo;
/**
* 内部批次号(如有)
* 类型:string | 必填:N
* 示例:LOT202601
*/
private String lotNo;
/**
* 制造商批次号
* 类型:string | 必填:N
* 示例:MLOT-5566
*/
private String makerLot;
/**
* 实际入库数量(已确认)
* 类型:number | 必填:Y
* 示例:500
*/
private Integer qty;
/**
* 计量单位
* 类型:string | 必填:N
* 示例:PCS
*/
private String uom;
/**
* 接收日期
* 类型:date | 必填:N
* 格式:YYYY-MM-DD
* 示例:2026-01-24
*/
private String receiveDate; // 用String避免日期序列化问题,也可改用LocalDate
/**
* 有效期
* 类型:date | 必填:N
* 格式:YYYY-MM-DD
* 示例:2027-01-31
*/
private String expireDate; // 同上
}
/**
* 库位子实体
*/
@Data
public static class Location {
/**
* SMF库位地址(扫描的QR文本)
* 类型:string | 必填:Y
* 示例:R01-S03
*/
private String locationId;
/**
* 库位类型(固定字面量,避免混淆)
* 类型:string | 必填:N
* 示例:SMF_ADDRESS
*/
private String locationType;
}
/**
* 标签子实体
*/
@Data
public static class Label {
/**
* 原始标签QR文本(可选存储/校验)
* 类型:string | 必填:N
* 示例:(raw QR text)
*/
private String fullCode;
/**
* QR标签上打印的数量(参考用)
* 类型:number | 必填:N
* 示例:500
*/
private Integer qrQty;
}
}
\ No newline at end of file
支持 Markdown 格式
你添加了 0 到此讨论。请谨慎行事。
Finish editing this message first!