This commit is contained in:
2026-08-08 10:17:50 +08:00
6 changed files with 254 additions and 1 deletions
@@ -34,6 +34,17 @@ public class FulfillmentTaskRepository {
.eq(FulfillmentTaskEntity::getOrderId, orderId));
}
public FulfillmentTaskEntity findByOrderForUpdate(long tenantId, long orderId) {
return tasks.selectOne(new LambdaQueryWrapper<FulfillmentTaskEntity>()
.eq(FulfillmentTaskEntity::getTenantId, tenantId)
.eq(FulfillmentTaskEntity::getOrderId, orderId)
.last("FOR UPDATE"));
}
public void insert(FulfillmentTaskEntity task) {
tasks.insert(task);
}
public PageResult<FulfillmentTaskEntity> page(long tenantId, FulfillmentTaskQuery query) {
LambdaQueryWrapper<FulfillmentTaskEntity> wrapper = new LambdaQueryWrapper<FulfillmentTaskEntity>()
.eq(FulfillmentTaskEntity::getTenantId, tenantId)
@@ -0,0 +1,74 @@
package com.baiyue.firstleg.service;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.dto.OrderApprovedInboundCommand;
import com.baiyue.firstleg.entity.FulfillmentTaskEntity;
import com.baiyue.firstleg.repository.FulfillmentTaskRepository;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Objects;
/** 将订单审核快照幂等转换为待分配履约任务。 */
@Service
public class FulfillmentTaskProvisioningService {
private final FulfillmentTaskRepository repository;
private final ObjectMapper objectMapper;
public FulfillmentTaskProvisioningService(FulfillmentTaskRepository repository, ObjectMapper objectMapper) {
this.repository = repository;
this.objectMapper = objectMapper;
}
public FulfillmentTaskEntity provision(OrderApprovedInboundCommand command, LocalDateTime now) {
FulfillmentTaskEntity existing = repository.findByOrder(command.tenantId(), command.orderId());
if (existing != null) {
return requireMatchingOrder(existing, command.orderNo());
}
FulfillmentTaskEntity task = new FulfillmentTaskEntity();
task.setId(IdWorker.getId());
task.setTenantId(command.tenantId());
task.setOrderId(command.orderId());
task.setOrderNo(command.orderNo());
task.setOrderSnapshotJson(snapshot(command));
task.setStatus("PENDING_ASSIGNMENT");
task.setCreatedBy(0L);
task.setUpdatedBy(0L);
task.setCreatedAt(now);
task.setUpdatedAt(now);
task.setDeleted(false);
task.setVersion(0);
try {
repository.insert(task);
return task;
} catch (DuplicateKeyException duplicate) {
FulfillmentTaskEntity winner = repository.findByOrderForUpdate(
command.tenantId(), command.orderId());
if (winner == null) {
throw new BizException(ResultCode.CONFLICT, "履约任务已被并发创建但无法读取");
}
return requireMatchingOrder(winner, command.orderNo());
}
}
private String snapshot(OrderApprovedInboundCommand command) {
try {
return objectMapper.writeValueAsString(command);
} catch (JsonProcessingException exception) {
throw new BizException(ResultCode.INTERNAL_ERROR, "订单履约快照序列化失败");
}
}
private FulfillmentTaskEntity requireMatchingOrder(FulfillmentTaskEntity task, String orderNo) {
if (!Objects.equals(task.getOrderNo(), orderNo)) {
throw new BizException(ResultCode.CONFLICT, "订单号与已有履约任务不一致");
}
return task;
}
}
@@ -7,6 +7,7 @@ import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.dto.OrderApprovedInboundCommand;
import com.baiyue.firstleg.dto.InboundOrderReference;
import com.baiyue.firstleg.entity.FulfillmentTaskEntity;
import com.baiyue.firstleg.entity.InboundOrderEntity;
import com.baiyue.firstleg.entity.InboundStatusHistoryEntity;
import com.baiyue.firstleg.mapper.InboundOrderMapper;
@@ -25,18 +26,25 @@ public class InboundOrderCreationService {
private InboundOrderMapper orders;
@Resource
private InboundStatusHistoryMapper histories;
@Resource
private FulfillmentTaskProvisioningService fulfillmentTasks;
@Transactional
public long createFromApprovedOrder(OrderApprovedInboundCommand command) {
InboundOrderEntity existing = findByOrder(command.tenantId(), command.orderId(), false);
if (existing != null) {
return requireMatchingInboundCode(existing, command.inboundCode());
long inboundId = requireMatchingInboundCode(existing, command.inboundCode());
FulfillmentTaskEntity task = fulfillmentTasks.provision(command, LocalDateTime.now());
linkTask(existing, task.getId());
return inboundId;
}
LocalDateTime now = LocalDateTime.now();
FulfillmentTaskEntity task = fulfillmentTasks.provision(command, now);
InboundOrderEntity inbound = new InboundOrderEntity();
inbound.setId(IdWorker.getId());
inbound.setTenantId(command.tenantId());
inbound.setTaskId(task.getId());
inbound.setOrderId(command.orderId());
inbound.setOrderNo(command.orderNo());
inbound.setInboundCode(command.inboundCode());
@@ -116,6 +124,29 @@ public class InboundOrderCreationService {
return existing.getId();
}
private void linkTask(InboundOrderEntity inbound, long taskId) {
if (inbound.getTaskId() != null) {
if (!inbound.getTaskId().equals(taskId)) {
throw new BizException(ResultCode.CONFLICT, "入库单已关联其他履约任务");
}
return;
}
int updated = orders.update(null,
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<InboundOrderEntity>()
.eq(InboundOrderEntity::getTenantId, inbound.getTenantId())
.eq(InboundOrderEntity::getId, inbound.getId())
.isNull(InboundOrderEntity::getTaskId)
.set(InboundOrderEntity::getTaskId, taskId));
if (updated == 1) {
inbound.setTaskId(taskId);
return;
}
InboundOrderEntity current = findByOrder(inbound.getTenantId(), inbound.getOrderId(), true);
if (current == null || !Long.valueOf(taskId).equals(current.getTaskId())) {
throw new BizException(ResultCode.CONFLICT, "入库单履约任务关联已发生变化");
}
}
public List<InboundOrderReference.Item> findReferences(long tenantId, List<Long> orderIds) {
if (orderIds.isEmpty()) return List.of();
return orders.selectList(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<InboundOrderEntity>()