feat: 增加ERP日计划推送接口
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
package cn.code.nl.module.lms.controller.erp.dailyplan;
|
||||
|
||||
import cn.code.nl.framework.common.pojo.CommonResult;
|
||||
import cn.code.nl.module.lms.controller.erp.dailyplan.vo.ErpDailyPlanPushReqVO;
|
||||
import cn.code.nl.module.lms.service.dailyplan.DailyPlanService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
import static cn.code.nl.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.code.nl.module.lms.enums.ErrorCodeConstants.DAILY_PLAN_ERP_TOKEN_ERROR;
|
||||
|
||||
/** ERP 日计划对接接口。 */
|
||||
@Tag(name = "ERP - 日计划")
|
||||
@RestController
|
||||
@RequestMapping("/lms/daily-plan")
|
||||
@Validated
|
||||
public class ErpDailyPlanController {
|
||||
|
||||
/** ERP 日计划接口令牌。 */
|
||||
@Value("${erp.daily-plan.token}")
|
||||
private String erpDailyPlanToken;
|
||||
|
||||
/** 日计划服务。 */
|
||||
@Resource
|
||||
private DailyPlanService dailyPlanService;
|
||||
|
||||
/** 接收 ERP 日计划;调用方还必须携带标准 tenant-id 请求头。 */
|
||||
@PostMapping("/erpPush")
|
||||
@Operation(summary = "ERP 推送日计划", description = "除 X-ERP-Token 外,还必须携带项目标准 tenant-id 请求头,数据按租户隔离")
|
||||
public CommonResult<String> erpPush(
|
||||
@Parameter(description = "ERP 接口令牌", required = true)
|
||||
@RequestHeader("X-ERP-Token") @NotBlank(message = "ERP接口令牌不能为空") String token,
|
||||
@Valid @RequestBody ErpDailyPlanPushReqVO reqVO) {
|
||||
if (!MessageDigest.isEqual(token.getBytes(StandardCharsets.UTF_8),
|
||||
erpDailyPlanToken.getBytes(StandardCharsets.UTF_8))) {
|
||||
throw exception(DAILY_PLAN_ERP_TOKEN_ERROR);
|
||||
}
|
||||
return CommonResult.success(dailyPlanService.pushErpDailyPlan(reqVO));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package cn.code.nl.module.lms.controller.erp.dailyplan.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/** ERP 日计划推送请求。 */
|
||||
@Data
|
||||
@Schema(description = "ERP 日计划推送请求")
|
||||
public class ErpDailyPlanPushReqVO {
|
||||
|
||||
/** ERP 订单号。 */
|
||||
@Schema(description = "ERP 订单号", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "ERP订单号不能为空")
|
||||
@Size(max = 64, message = "ERP订单号长度不能超过64个字符")
|
||||
private String erpOrderCode;
|
||||
|
||||
/** 计划日期。 */
|
||||
@Schema(description = "计划日期", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "计划日期不能为空")
|
||||
private LocalDate planDate;
|
||||
|
||||
/** 物料标识。 */
|
||||
@Schema(description = "物料标识", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "物料标识不能为空")
|
||||
@Positive(message = "物料标识必须为正整数")
|
||||
private Long materialId;
|
||||
|
||||
/** 物料编码。 */
|
||||
@Schema(description = "物料编码", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "物料编码不能为空")
|
||||
@Size(max = 64, message = "物料编码长度不能超过64个字符")
|
||||
private String materialCode;
|
||||
|
||||
/** 物料名称。 */
|
||||
@Schema(description = "物料名称", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "物料名称不能为空")
|
||||
@Size(max = 128, message = "物料名称长度不能超过128个字符")
|
||||
private String materialName;
|
||||
|
||||
/** 物料规格。 */
|
||||
@Schema(description = "物料规格")
|
||||
@Size(max = 128, message = "物料规格长度不能超过128个字符")
|
||||
private String materialSpec;
|
||||
|
||||
/** ERP 最新计划总数量。 */
|
||||
@Schema(description = "ERP 最新计划总数量", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "计划数量不能为空")
|
||||
@Positive(message = "计划数量必须为正整数")
|
||||
private Integer planQty;
|
||||
|
||||
/** 包装信息。 */
|
||||
@Schema(description = "包装信息")
|
||||
private String packingInfo;
|
||||
}
|
||||
@@ -56,6 +56,11 @@ public interface DailyPlanMapper extends BaseMapperX<DailyPlanDO> {
|
||||
*/
|
||||
DailyPlanDO selectByErpOrderCode(@Param("erpOrderCode") String erpOrderCode);
|
||||
|
||||
/**
|
||||
* 查询当前租户下一个日计划排序序号。
|
||||
*/
|
||||
Integer selectNextSortSeq();
|
||||
|
||||
/**
|
||||
* 查询待备货日计划。
|
||||
*/
|
||||
|
||||
@@ -105,6 +105,8 @@ public class SecurityConfiguration {
|
||||
.requestMatchers("/webjars/**").permitAll()
|
||||
.requestMatchers("/swagger-ui").permitAll()
|
||||
.requestMatchers("/swagger-ui/**").permitAll();
|
||||
// ERP 日计划接口使用独立令牌鉴权
|
||||
registry.requestMatchers("/lms/daily-plan/erpPush").permitAll();
|
||||
// Spring Boot Actuator 的安全配置
|
||||
registry.requestMatchers("/actuator").permitAll()
|
||||
.requestMatchers("/actuator/**").permitAll();
|
||||
|
||||
@@ -2,11 +2,14 @@ package cn.code.nl.module.lms.service.dailyplan;
|
||||
|
||||
import cn.code.nl.framework.common.pojo.PageResult;
|
||||
import cn.code.nl.module.lms.controller.admin.dailyplan.vo.*;
|
||||
import cn.code.nl.module.lms.controller.erp.dailyplan.vo.ErpDailyPlanPushReqVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** LMS 日计划服务。 */
|
||||
public interface DailyPlanService {
|
||||
/** ERP 幂等推送日计划。 */
|
||||
String pushErpDailyPlan(ErpDailyPlanPushReqVO reqVO);
|
||||
/** 查询日计划管理分页。 */
|
||||
PageResult<DailyPlanRespVO> getManagementPage(DailyPlanPageReqVO reqVO);
|
||||
/** 查询日计划作业分页。 */
|
||||
|
||||
@@ -19,6 +19,7 @@ import cn.code.nl.module.lms.controller.admin.dailyplan.vo.DailyPlanUpdateReqVO;
|
||||
import cn.code.nl.module.lms.controller.admin.dailyplan.vo.DailyPlanVersionReqVO;
|
||||
import cn.code.nl.module.lms.controller.admin.dailyplan.vo.DailyPlanWeightUpdateReqVO;
|
||||
import cn.code.nl.module.lms.controller.admin.dailyplan.vo.DailyPlanWorkPageReqVO;
|
||||
import cn.code.nl.module.lms.controller.erp.dailyplan.vo.ErpDailyPlanPushReqVO;
|
||||
import cn.code.nl.module.lms.dal.dataobject.dailyplan.DailyPlanDO;
|
||||
import cn.code.nl.module.lms.dal.dataobject.dailyplan.DailyPlanOperationLogDO;
|
||||
import cn.code.nl.module.lms.dal.dataobject.dailyplan.DailyPlanStrategyDO;
|
||||
@@ -33,8 +34,12 @@ import cn.code.nl.module.lms.enums.DailyPlanWorkStatusEnum;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
@@ -46,6 +51,8 @@ import java.util.Set;
|
||||
import static cn.code.nl.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.code.nl.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
import static cn.code.nl.module.lms.enums.ErrorCodeConstants.DAILY_PLAN_CODE_GENERATE_FAILED;
|
||||
import static cn.code.nl.module.lms.enums.ErrorCodeConstants.DAILY_PLAN_ERP_FINAL_STATUS_NOT_UPDATE;
|
||||
import static cn.code.nl.module.lms.enums.ErrorCodeConstants.DAILY_PLAN_ERP_PLAN_QTY_NOT_DECREASE;
|
||||
import static cn.code.nl.module.lms.enums.ErrorCodeConstants.DAILY_PLAN_NOT_EXISTS;
|
||||
import static cn.code.nl.module.lms.enums.ErrorCodeConstants.DAILY_PLAN_QTY_EXCEEDS_LIMIT;
|
||||
import static cn.code.nl.module.lms.enums.ErrorCodeConstants.DAILY_PLAN_STATUS_OPERATION_NOT_ALLOWED;
|
||||
@@ -71,6 +78,8 @@ public class DailyPlanServiceImpl implements DailyPlanService {
|
||||
private static final String TYPE_FINISH = "FINISH";
|
||||
private static final String TYPE_STOP = "STOP";
|
||||
private static final String TYPE_UPDATE = "UPDATE";
|
||||
private static final String TYPE_CREATE = "CREATE";
|
||||
private static final String ERP_OPERATOR = "ERP";
|
||||
|
||||
@Resource
|
||||
private DailyPlanMapper dailyPlanMapper;
|
||||
@@ -80,6 +89,104 @@ public class DailyPlanServiceImpl implements DailyPlanService {
|
||||
private DailyPlanOperationLogMapper dailyPlanOperationLogMapper;
|
||||
@Resource
|
||||
private CodeGenApi codeGenApi;
|
||||
@Resource
|
||||
private PlatformTransactionManager transactionManager;
|
||||
|
||||
/** ERP 幂等推送日计划,并在首次推送并发冲突时进行一次独立事务重试。 */
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String pushErpDailyPlan(ErpDailyPlanPushReqVO reqVO) {
|
||||
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
|
||||
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
try {
|
||||
return transactionTemplate.execute(status -> processErpDailyPlan(reqVO));
|
||||
} catch (DuplicateKeyException ex) {
|
||||
return transactionTemplate.execute(status -> processErpDailyPlan(reqVO));
|
||||
}
|
||||
}
|
||||
|
||||
/** 按 ERP 订单号和当前状态创建或更新日计划。 */
|
||||
private String processErpDailyPlan(ErpDailyPlanPushReqVO reqVO) {
|
||||
DailyPlanDO current = dailyPlanMapper.selectByErpOrderCode(reqVO.getErpOrderCode());
|
||||
if (current == null) {
|
||||
return createErpDailyPlan(reqVO);
|
||||
}
|
||||
if (DailyPlanOrderStatusEnum.NOT_STARTED.getCode().equals(current.getOrderStatus())) {
|
||||
return updateNotStartedErpDailyPlan(current, reqVO);
|
||||
}
|
||||
if (DailyPlanOrderStatusEnum.IN_PROGRESS.getCode().equals(current.getOrderStatus())) {
|
||||
return appendInProgressErpDailyPlan(current, reqVO);
|
||||
}
|
||||
throw exception(DAILY_PLAN_ERP_FINAL_STATUS_NOT_UPDATE);
|
||||
}
|
||||
|
||||
/** 创建 ERP 日计划并记录创建日志。 */
|
||||
private String createErpDailyPlan(ErpDailyPlanPushReqVO reqVO) {
|
||||
DailyPlanDO plan = new DailyPlanDO();
|
||||
plan.setDailyPlanId(IdWorker.getId());
|
||||
plan.setPlanCode(generatePlanCode());
|
||||
plan.setSourceType(DailyPlanSourceTypeEnum.ERP.getCode());
|
||||
plan.setErpOrderCode(reqVO.getErpOrderCode());
|
||||
plan.setManualOrderCode(null);
|
||||
setErpFields(plan, reqVO);
|
||||
plan.setSortSeq(dailyPlanMapper.selectNextSortSeq());
|
||||
plan.setWeight(1);
|
||||
plan.setCreator(ERP_OPERATOR);
|
||||
plan.setUpdater(ERP_OPERATOR);
|
||||
checkAffected(dailyPlanMapper.insertDailyPlan(plan));
|
||||
insertLog(plan.getDailyPlanId(), TARGET_ORDER, TYPE_CREATE, null, String.valueOf(plan.getPlanQty()),
|
||||
plan.getPlanQty(), null, ERP_OPERATOR);
|
||||
return plan.getPlanCode();
|
||||
}
|
||||
|
||||
/** 覆盖未开始 ERP 日计划的 ERP 可编辑字段并记录更新日志。 */
|
||||
private String updateNotStartedErpDailyPlan(DailyPlanDO current, ErpDailyPlanPushReqVO reqVO) {
|
||||
DailyPlanDO update = new DailyPlanDO();
|
||||
update.setDailyPlanId(current.getDailyPlanId());
|
||||
update.setVersion(current.getVersion());
|
||||
update.setErpOrderCode(current.getErpOrderCode());
|
||||
update.setManualOrderCode(current.getManualOrderCode());
|
||||
setErpFields(update, reqVO);
|
||||
update.setSortSeq(current.getSortSeq());
|
||||
update.setWeight(current.getWeight());
|
||||
update.setUpdater(ERP_OPERATOR);
|
||||
checkAffected(dailyPlanMapper.updateNotStartedPlan(update));
|
||||
insertLog(current.getDailyPlanId(), TARGET_QUANTITY, TYPE_UPDATE, String.valueOf(current.getPlanQty()),
|
||||
String.valueOf(reqVO.getPlanQty()), null, null, ERP_OPERATOR);
|
||||
return current.getPlanCode();
|
||||
}
|
||||
|
||||
/** 按 ERP 最新需求总量幂等追加进行中日计划数量。 */
|
||||
private String appendInProgressErpDailyPlan(DailyPlanDO current, ErpDailyPlanPushReqVO reqVO) {
|
||||
int currentTotalQty = safeAddQuantity(current.getPlanQty(), current.getAppendQty());
|
||||
if (reqVO.getPlanQty().equals(currentTotalQty)) {
|
||||
return current.getPlanCode();
|
||||
}
|
||||
if (reqVO.getPlanQty() < currentTotalQty) {
|
||||
throw exception(DAILY_PLAN_ERP_PLAN_QTY_NOT_DECREASE);
|
||||
}
|
||||
int changeQty = reqVO.getPlanQty() - currentTotalQty;
|
||||
DailyPlanDO update = new DailyPlanDO();
|
||||
update.setDailyPlanId(current.getDailyPlanId());
|
||||
update.setVersion(current.getVersion());
|
||||
update.setAppendQty(safeAddQuantity(current.getAppendQty(), changeQty));
|
||||
update.setUpdater(ERP_OPERATOR);
|
||||
checkAffected(dailyPlanMapper.updateStateWithVersion(update));
|
||||
insertLog(current.getDailyPlanId(), TARGET_QUANTITY, TYPE_APPEND, String.valueOf(current.getAppendQty()),
|
||||
String.valueOf(update.getAppendQty()), changeQty, null, ERP_OPERATOR);
|
||||
return current.getPlanCode();
|
||||
}
|
||||
|
||||
/** 设置 ERP 推送的日计划字段。 */
|
||||
private void setErpFields(DailyPlanDO plan, ErpDailyPlanPushReqVO reqVO) {
|
||||
plan.setPlanDate(reqVO.getPlanDate());
|
||||
plan.setMaterialId(reqVO.getMaterialId());
|
||||
plan.setMaterialCode(reqVO.getMaterialCode());
|
||||
plan.setMaterialName(reqVO.getMaterialName());
|
||||
plan.setMaterialSpec(reqVO.getMaterialSpec());
|
||||
plan.setPlanQty(reqVO.getPlanQty());
|
||||
plan.setPackingInfo(reqVO.getPackingInfo());
|
||||
}
|
||||
|
||||
/** 查询日计划管理分页。 */
|
||||
@Override
|
||||
@@ -159,6 +266,22 @@ public class DailyPlanServiceImpl implements DailyPlanService {
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createManualPlan(DailyPlanCreateReqVO reqVO) {
|
||||
String planCode = generatePlanCode();
|
||||
String userId = String.valueOf(getLoginUserId());
|
||||
DailyPlanDO plan = new DailyPlanDO();
|
||||
plan.setDailyPlanId(IdWorker.getId());
|
||||
plan.setPlanCode(planCode);
|
||||
plan.setSourceType(DailyPlanSourceTypeEnum.MANUAL.getCode());
|
||||
plan.setManualOrderCode(StrUtil.isEmpty(reqVO.getManualOrderCode()) ? planCode : reqVO.getManualOrderCode());
|
||||
setEditableFields(plan, reqVO);
|
||||
plan.setCreator(userId);
|
||||
plan.setUpdater(userId);
|
||||
checkAffected(dailyPlanMapper.insertDailyPlan(plan));
|
||||
return plan.getDailyPlanId();
|
||||
}
|
||||
|
||||
/** 通过编码服务安全生成日计划编码。 */
|
||||
private String generatePlanCode() {
|
||||
CodeGenerateReqDTO codeReq = new CodeGenerateReqDTO();
|
||||
codeReq.setRuleCode("LMS_DAILY_PLAN_CODE");
|
||||
CommonResult<String> codeResult = codeGenApi.generate(codeReq);
|
||||
@@ -171,17 +294,7 @@ public class DailyPlanServiceImpl implements DailyPlanService {
|
||||
if (StrUtil.isEmpty(codeResult.getData())) {
|
||||
throw exception(DAILY_PLAN_CODE_GENERATE_FAILED);
|
||||
}
|
||||
String userId = String.valueOf(getLoginUserId());
|
||||
DailyPlanDO plan = new DailyPlanDO();
|
||||
plan.setDailyPlanId(IdWorker.getId());
|
||||
plan.setPlanCode(codeResult.getData());
|
||||
plan.setSourceType(DailyPlanSourceTypeEnum.MANUAL.getCode());
|
||||
plan.setManualOrderCode(StrUtil.isEmpty(reqVO.getManualOrderCode()) ? codeResult.getData() : reqVO.getManualOrderCode());
|
||||
setEditableFields(plan, reqVO);
|
||||
plan.setCreator(userId);
|
||||
plan.setUpdater(userId);
|
||||
checkAffected(dailyPlanMapper.insertDailyPlan(plan));
|
||||
return plan.getDailyPlanId();
|
||||
return codeResult.getData();
|
||||
}
|
||||
|
||||
/** 修改人工日计划。 */
|
||||
@@ -532,6 +645,13 @@ public class DailyPlanServiceImpl implements DailyPlanService {
|
||||
/** 新增日计划操作日志,操作时间由数据库生成。 */
|
||||
private void insertLog(Long dailyPlanId, String target, String type, String beforeValue, String afterValue,
|
||||
Integer changeQty, String reason) {
|
||||
insertLog(dailyPlanId, target, type, beforeValue, afterValue, changeQty, reason,
|
||||
String.valueOf(getLoginUserId()));
|
||||
}
|
||||
|
||||
/** 新增指定操作人的日计划操作日志,供无管理端登录态的外部接口使用。 */
|
||||
private void insertLog(Long dailyPlanId, String target, String type, String beforeValue, String afterValue,
|
||||
Integer changeQty, String reason, String operator) {
|
||||
DailyPlanOperationLogDO log = new DailyPlanOperationLogDO();
|
||||
log.setOperationLogId(IdWorker.getId());
|
||||
log.setDailyPlanId(dailyPlanId);
|
||||
@@ -541,7 +661,7 @@ public class DailyPlanServiceImpl implements DailyPlanService {
|
||||
log.setAfterValue(afterValue);
|
||||
log.setChangeQty(changeQty);
|
||||
log.setOperationReason(reason);
|
||||
log.setOperator(String.valueOf(getLoginUserId()));
|
||||
log.setOperator(operator);
|
||||
checkAffected(dailyPlanOperationLogMapper.insertOperationLog(log));
|
||||
}
|
||||
|
||||
|
||||
@@ -79,6 +79,12 @@
|
||||
AND deleted = b'0'
|
||||
</select>
|
||||
|
||||
<select id="selectNextSortSeq" resultType="java.lang.Integer">
|
||||
SELECT COALESCE(MAX(sort_seq), 0) + 1
|
||||
FROM lms_daily_plan
|
||||
WHERE deleted = b'0'
|
||||
</select>
|
||||
|
||||
<select id="selectStockingCandidates"
|
||||
resultType="cn.code.nl.module.lms.dal.dataobject.dailyplan.DailyPlanDO">
|
||||
SELECT <include refid="dailyPlanColumns"/>
|
||||
|
||||
Reference in New Issue
Block a user